Skip to main content

js_component_bindgen/
transpile_bindgen.rs

1use std::cell::RefCell;
2use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
3use std::fmt::Write;
4use std::mem;
5use std::ops::Index;
6
7use base64::Engine as _;
8use base64::engine::general_purpose;
9use heck::{ToKebabCase, ToLowerCamelCase, ToUpperCamelCase};
10use semver::Version;
11use wasmtime_environ::component::{
12    CanonicalOptions, CanonicalOptionsDataModel, Component, ComponentExtern, ComponentTranslation,
13    ComponentTypes, CoreDef, CoreExport, Export, ExportItem, FixedEncoding, GlobalInitializer,
14    InstantiateModule, InterfaceType, LinearMemoryOptions, LoweredIndex, ResourceIndex,
15    RuntimeComponentInstanceIndex, RuntimeImportIndex, RuntimeInstanceIndex, StaticModuleIndex,
16    Trampoline, TrampolineIndex, TypeDef, TypeFuncIndex, TypeFutureTableIndex,
17    TypeResourceTableIndex, TypeStreamTableIndex,
18};
19use wasmtime_environ::component::{
20    ExtractCallback, ImportIndex, NameMapNoIntern, Transcode,
21    TypeComponentLocalErrorContextTableIndex,
22};
23use wasmtime_environ::{EntityIndex, PrimaryMap};
24use wit_bindgen_core::abi::{self, LiftLower};
25use wit_component::StringEncoding;
26use wit_parser::abi::AbiVariant;
27use wit_parser::{
28    Function, FunctionKind, Handle, Resolve, Result_, SizeAlign, Type, TypeDefKind, TypeId,
29    WorldId, WorldItem, WorldKey,
30};
31
32use crate::esm_bindgen::EsmBindgen;
33use crate::files::Files;
34use crate::function_bindgen::{
35    ErrHandling, FunctionBindgen, FunctionBindgenComponentState, PayloadTypeMetadata, ResourceData,
36    ResourceExtraData, ResourceMap, ResourceTable,
37};
38use crate::intrinsics::component::ComponentIntrinsic;
39use crate::intrinsics::js_helper::JsHelperIntrinsic;
40use crate::intrinsics::lift::LiftIntrinsic;
41use crate::intrinsics::lower::LowerIntrinsic;
42use crate::intrinsics::p3::async_future::AsyncFutureIntrinsic;
43use crate::intrinsics::p3::async_stream::AsyncStreamIntrinsic;
44use crate::intrinsics::p3::async_task::AsyncTaskIntrinsic;
45use crate::intrinsics::p3::error_context::ErrCtxIntrinsic;
46use crate::intrinsics::p3::host::HostIntrinsic;
47use crate::intrinsics::p3::waitable::WaitableIntrinsic;
48use crate::intrinsics::resource::ResourceIntrinsic;
49use crate::intrinsics::string::StringIntrinsic;
50use crate::intrinsics::webidl::WebIdlIntrinsic;
51use crate::intrinsics::{
52    AsyncDeterminismProfile, Intrinsic, RenderIntrinsicsArgs, render_intrinsics,
53};
54use crate::names::{LocalNames, is_js_reserved_word, maybe_quote_id, maybe_quote_member};
55use crate::{
56    FunctionIdentifier, ManagesIntrinsics, core, get_thrown_type, is_async_fn,
57    requires_async_porcelain, source, uwrite, uwriteln,
58};
59
60/// Size of flat parameters that can be sent, for example via the `task.return`
61/// intrinsic, when returning from an async func
62const MAX_FLAT_PARAMS: usize = 16;
63/// Maximum direct flat results for sync canonical lowering.
64const MAX_FLAT_RESULTS: usize = 1;
65
66#[derive(Debug, Default, Clone, bon::Builder)]
67pub struct TranspileOpts {
68    pub name: String,
69    /// Disables generation of `*.d.ts` files and instead only generates `*.js`
70    /// source files.
71    #[builder(default)]
72    pub no_typescript: bool,
73    /// Provide a custom JS instantiation API for the component instead
74    /// of the direct importable native ESM output.
75    pub instantiation_mode: Option<InstantiationMode>,
76    /// Configure how import bindings are provided, as high-level JS bindings,
77    /// or as hybrid optimized bindings.
78    pub import_bindings: Option<BindingsMode>,
79    /// Comma-separated list of "from-specifier=./to-specifier.js" mappings of
80    /// component import specifiers to JS import specifiers.
81    pub map: Option<HashMap<String, String>>,
82    /// Disables compatibility in Node.js without a fetch global.
83    #[builder(default)]
84    pub nodejs_compat_disabled: bool,
85    /// Set the cutoff byte size for base64 inlining core Wasm in instantiation mode
86    /// (set to 0 to disable all base64 inlining)
87    #[builder(default)]
88    pub base64_cutoff: usize,
89    /// Enables compatibility for JS environments without top-level await support
90    /// via an async $init promise export to wait for instead.
91    #[builder(default)]
92    pub tla_compat: bool,
93    /// Disable verification of component Wasm data structures when
94    /// lifting as a production optimization
95    #[builder(default)]
96    pub valid_lifting_optimization: bool,
97    /// Whether or not to emit `tracing` calls on function entry/exit.
98    #[builder(default)]
99    pub tracing: bool,
100    /// Whether to generate namespaced exports like `foo as "local:package/foo"`.
101    /// These exports can break typescript builds.
102    #[builder(default)]
103    pub no_namespaced_exports: bool,
104    /// Whether to output core Wasm utilizing multi-memory or to polyfill
105    /// this handling.
106    #[builder(default)]
107    pub multi_memory: bool,
108    /// Whether to generate types for a guest module using module declarations.
109    #[builder(default)]
110    pub guest: bool,
111    /// Configure whether to use `async` imports or exports with
112    /// JavaScript Promise Integration (JSPI).
113    pub async_mode: Option<AsyncMode>,
114    /// Configure whether to generate code that includes strict type checks
115    #[builder(default)]
116    pub strict: bool,
117    /// Whether the core module(s) to be wrapped were actually transpiled from Wasm to JS (asm.js) and thus need shimming for i64
118    #[builder(default)]
119    pub asmjs: bool,
120    /// Whether the target JS engine supports the exception handling proposal
121    /// (`try_table`/exnref).
122    ///
123    /// When disabled (the default), the exceptions feature is masked off
124    /// during component validation so that wasmtime-environ's FACT-generated
125    /// adapters do not wrap calls in exception barriers, which would only run
126    /// behind a flag in today's JS engines.
127    #[builder(default)]
128    pub supports_wasm_exnref: bool,
129}
130
131#[derive(Default, Clone, Debug)]
132#[non_exhaustive]
133pub enum AsyncMode {
134    #[default]
135    Sync,
136    JavaScriptPromiseIntegration {
137        imports: Vec<String>,
138        exports: Vec<String>,
139    },
140}
141
142#[derive(Default, Clone, Debug)]
143#[non_exhaustive]
144pub enum InstantiationMode {
145    #[default]
146    Async,
147    Sync,
148}
149
150/// Internal Bindgen calling convention
151enum CallType {
152    /// Standard calls - inner function is called directly with parameters
153    Standard,
154    /// Standard calls that are async (p3)
155    AsyncStandard,
156    /// Exported resource method calls - this is passed as the first argument
157    FirstArgIsThis,
158    /// Exported resource method calls that are async (p3)
159    AsyncFirstArgIsThis,
160    /// Imported resource method calls - callee is a member of the parameter
161    CalleeResourceDispatch,
162    /// Imported resource method calls that are async (p3)
163    AsyncCalleeResourceDispatch,
164}
165
166#[derive(Default, Clone, Debug)]
167#[non_exhaustive]
168pub enum BindingsMode {
169    Hybrid,
170    #[default]
171    Js,
172    Optimized,
173    DirectOptimized,
174}
175
176struct JsBindgen<'a> {
177    local_names: LocalNames,
178
179    esm_bindgen: EsmBindgen,
180
181    /// The source code for the "main" file that's going to be created for the
182    /// component we're generating bindings for. This is incrementally added to
183    /// over time and primarily contains the main `instantiate` function as well
184    /// as a type-description of the input/output interfaces.
185    src: Source,
186
187    /// Core module count
188    core_module_cnt: usize,
189
190    /// Various options for code generation.
191    opts: &'a TranspileOpts,
192
193    /// List of all intrinsics emitted to `src` so far.
194    all_intrinsics: BTreeSet<Intrinsic>,
195
196    /// List of all core Wasm exported functions (and if is async) referenced in
197    /// `src` so far.
198    ///
199    /// The second boolean is true when async procelain is required *or* if the
200    /// export itself is async.
201    all_core_exported_funcs: Vec<(String, bool)>,
202}
203
204/// Arguments provided to `JSBindgen::bindgen`, normally called to perform bindgen on a given function
205struct JsFunctionBindgenArgs<'a> {
206    /// Number of params that the function expects
207    nparams: usize,
208    /// Internal convention for function calls (ex. whether the first argument is known to be 'this')
209    call_type: CallType,
210    /// Interface name (if inside an interface)
211    iface_name: Option<&'a str>,
212    /// Callee of the function
213    callee: &'a str,
214    /// Canon opts provided for the functions
215    opts: &'a CanonicalOptions,
216    /// Parsed function metadata
217    func: &'a Function,
218    resource_map: &'a ResourceMap,
219    /// ABI variant of the function
220    abi: AbiVariant,
221    /// Whether the function in question is a host async function (i.e. JSPI)
222    requires_async_porcelain: bool,
223    /// Whether the function in question is a guest async function (i.e. WASI P3)
224    is_async: bool,
225    /// Whether the function in question is being generated for an import
226    /// (false implies generation is happening for an export)
227    for_import: bool,
228}
229
230impl<'a> ManagesIntrinsics for JsBindgen<'a> {
231    fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
232        self.intrinsic(intrinsic);
233    }
234}
235
236#[derive(PartialEq, Eq, Clone)]
237#[non_exhaustive]
238pub enum ExportKind {
239    /// Maps to `wasmtime_environ::export::LiftedFunction`
240    LiftedFunction,
241    /// Maps to `wasmtime_environ::export::Instance`
242    Instance,
243}
244
245#[allow(clippy::too_many_arguments)]
246pub fn transpile_bindgen(
247    name: &str,
248    component: &ComponentTranslation,
249    modules: &PrimaryMap<StaticModuleIndex, core::Translation<'_>>,
250    types: &ComponentTypes,
251    resolve: &Resolve,
252    id: WorldId,
253    opts: TranspileOpts,
254    files: &mut Files,
255) -> (Vec<String>, Vec<(String, ExportKind)>) {
256    let (async_imports, async_exports) = match opts.async_mode.clone() {
257        None | Some(AsyncMode::Sync) => (Default::default(), Default::default()),
258        Some(AsyncMode::JavaScriptPromiseIntegration { imports, exports }) => {
259            (imports.into_iter().collect(), exports.into_iter().collect())
260        }
261    };
262
263    let mut bindgen = JsBindgen {
264        local_names: LocalNames::default(),
265        src: Source::default(),
266        esm_bindgen: EsmBindgen::default(),
267        core_module_cnt: 0,
268        opts: &opts,
269        all_intrinsics: BTreeSet::new(),
270        all_core_exported_funcs: Vec::new(),
271    };
272    bindgen.local_names.exclude_globals(
273        &Intrinsic::get_global_names()
274            .into_iter()
275            .collect::<Vec<_>>(),
276    );
277    bindgen.core_module_cnt = modules.len();
278
279    // Generate mapping of stream tables to components that are related
280    let mut stream_tables = BTreeMap::new();
281    for idx in 0..component.component.num_stream_tables {
282        let stream_table_idx = TypeStreamTableIndex::from_u32(idx as u32);
283        let stream_table_ty = &types[stream_table_idx];
284        stream_tables.insert(stream_table_idx, stream_table_ty.instance);
285    }
286
287    // Generate mapping of future tables to components that are related
288    let mut future_tables = BTreeMap::new();
289    for idx in 0..component.component.num_future_tables {
290        let future_table_idx = TypeFutureTableIndex::from_u32(idx as u32);
291        let future_table_ty = &types[future_table_idx];
292        future_tables.insert(future_table_idx, future_table_ty.instance);
293    }
294
295    // Generate mapping of err_ctx tables to components that are related
296    let mut err_ctx_tables = BTreeMap::new();
297    for idx in 0..component.component.num_error_context_tables {
298        let err_ctx_table_idx = TypeComponentLocalErrorContextTableIndex::from_u32(idx as u32);
299        let err_ctx_table_ty = &types[err_ctx_table_idx];
300        err_ctx_tables.insert(err_ctx_table_idx, err_ctx_table_ty.instance);
301    }
302
303    // Bindings are generated when the `instantiate` method is called on the
304    // Instantiator structure created below
305    let mut instantiator = Instantiator {
306        src: Source::default(),
307        sizes: SizeAlign::default(),
308        bindgen: &mut bindgen,
309        modules,
310        instances: Default::default(),
311        error_context_component_initialized: (0..component
312            .component
313            .num_runtime_component_instances)
314            .map(|_| false)
315            .collect(),
316        error_context_component_table_initialized: (0..component
317            .component
318            .num_error_context_tables)
319            .map(|_| false)
320            .collect(),
321        resolve,
322        world: id,
323        translation: component,
324        component: &component.component,
325        types,
326        async_imports,
327        async_exports,
328        imports: Default::default(),
329        exports: Default::default(),
330        lowering_options: Default::default(),
331        used_instance_flags: Default::default(),
332        defined_resource_classes: Default::default(),
333        imports_resource_types: Default::default(),
334        imports_resource_index_types: Default::default(),
335        exports_resource_types: Default::default(),
336        exports_resource_index_types: Default::default(),
337        resource_exports: Default::default(),
338        resource_imports: Default::default(),
339        resources_initialized: BTreeMap::new(),
340        resource_tables_initialized: BTreeMap::new(),
341        stream_tables,
342        future_tables,
343        err_ctx_tables,
344        init_current_module: None,
345    };
346    instantiator.sizes.fill(resolve);
347    instantiator.initialize();
348    instantiator.instantiate();
349
350    instantiator.resource_definitions();
351    instantiator.instance_flags();
352
353    instantiator.bindgen.src.js(&instantiator.src.js);
354    instantiator.bindgen.src.js_init(&instantiator.src.js_init);
355
356    instantiator
357        .bindgen
358        .finish_component(name, files, &opts, source::Source::default());
359
360    let exports = instantiator
361        .bindgen
362        .esm_bindgen
363        .exports()
364        .iter()
365        .map(|(export_name, canon_export_name)| {
366            let expected_export_name =
367                if canon_export_name.contains(':') || canon_export_name.starts_with("[async]") {
368                    canon_export_name.to_string()
369                } else {
370                    canon_export_name.to_kebab_case()
371                };
372            let (export_idx, _extern_data) = instantiator
373                .component
374                .exports
375                .get(&expected_export_name, &NameMapNoIntern)
376                .unwrap_or_else(|| panic!("failed to find component export [{expected_export_name}] (original '{canon_export_name}')"));
377
378            let export_kind = match &instantiator.component.export_items[*export_idx] {
379                wasmtime_environ::component::Export::LiftedFunction { .. } => {
380                    ExportKind::LiftedFunction
381                }
382                wasmtime_environ::component::Export::Instance { .. } => {
383                    ExportKind::Instance
384                }
385                _ => panic!("unexpected export kind"),
386            };
387
388
389            (
390                export_name.to_string(),
391export_kind,
392            )
393        })
394        .collect();
395
396    (bindgen.esm_bindgen.import_specifiers(), exports)
397}
398
399impl JsBindgen<'_> {
400    fn finish_component(
401        &mut self,
402        name: &str,
403        files: &mut Files,
404        opts: &TranspileOpts,
405        intrinsic_definitions: source::Source,
406    ) {
407        let mut output = source::Source::default();
408        let mut compilation_promises = source::Source::default();
409        let mut core_exported_funcs = source::Source::default();
410
411        for (core_export_fn, is_async) in self.all_core_exported_funcs.iter() {
412            let local_name = self.local_names.get(core_export_fn);
413            if *is_async {
414                uwriteln!(
415                    core_exported_funcs,
416                    "{local_name} = WebAssembly.promising({core_export_fn});",
417                );
418            } else {
419                uwriteln!(core_exported_funcs, "{local_name} = {core_export_fn};",);
420            }
421        }
422
423        // adds a default implementation of `getCoreModule`
424        if matches!(self.opts.instantiation_mode, Some(InstantiationMode::Async)) {
425            uwriteln!(
426                compilation_promises,
427                "if (!getCoreModule) getCoreModule = (name) => {}(new URL(`./${{name}}`, import.meta.url));",
428                self.intrinsic(Intrinsic::FetchCompile)
429            );
430        }
431
432        // Setup the compilation data and compilation promises
433        let mut removed = BTreeSet::new();
434        for i in 0..self.core_module_cnt {
435            let local_name = format!("module{i}");
436            let mut name_idx = core_file_name(name, i as u32);
437            if self.opts.instantiation_mode.is_some() {
438                uwriteln!(
439                    compilation_promises,
440                    "const {local_name} = getCoreModule('{name_idx}');"
441                );
442            } else if files.get_size(&name_idx).unwrap() < self.opts.base64_cutoff {
443                assert!(removed.insert(i));
444                let data = files.remove(&name_idx).unwrap();
445                uwriteln!(
446                    compilation_promises,
447                    "const {local_name} = {}('{}');",
448                    self.intrinsic(Intrinsic::Base64Compile),
449                    general_purpose::STANDARD_NO_PAD.encode(&data),
450                );
451            } else {
452                // Maintain numerical file orderings when a previous file was
453                // inlined
454                if let Some(&replacement) = removed.iter().next() {
455                    assert!(removed.remove(&replacement) && removed.insert(i));
456                    let data = files.remove(&name_idx).unwrap();
457                    name_idx = core_file_name(name, replacement as u32);
458                    files.push(&name_idx, &data);
459                }
460                uwriteln!(
461                    compilation_promises,
462                    "const {local_name} = {}(new URL('./{name_idx}', import.meta.url));",
463                    self.intrinsic(Intrinsic::FetchCompile)
464                );
465            }
466        }
467
468        // Render the telemery directive
469        uwriteln!(output, r#""use components";"#);
470
471        let render_args = RenderIntrinsicsArgs::builder()
472            .intrinsics(&mut self.all_intrinsics)
473            .instantiation_occurred(self.opts.instantiation_mode.is_some())
474            .determinism_profile(AsyncDeterminismProfile::default())
475            .transpile_opts(opts)
476            .build();
477        let js_intrinsics = render_intrinsics(render_args);
478
479        if let Some(instantiation) = &self.opts.instantiation_mode {
480            uwrite!(
481                output,
482                "\
483                    export function instantiate(getCoreModule, imports, instantiateCore = {}) {{
484                        {}
485                        {}
486                        {}
487                ",
488                match instantiation {
489                    InstantiationMode::Async => "WebAssembly.instantiate",
490                    InstantiationMode::Sync =>
491                        "(module, importObject) => new WebAssembly.Instance(module, importObject)",
492                },
493                &js_intrinsics as &str,
494                &intrinsic_definitions as &str,
495                &compilation_promises as &str,
496            );
497        }
498
499        // Render all imports
500        let imports_object = if self.opts.instantiation_mode.is_some() {
501            Some("imports")
502        } else {
503            None
504        };
505        self.esm_bindgen
506            .render_imports(&mut output, imports_object, &mut self.local_names);
507
508        // Create instantiation code
509        if self.opts.instantiation_mode.is_some() {
510            uwrite!(&mut self.src.js, "{}", &core_exported_funcs as &str);
511            self.esm_bindgen.render_exports(
512                &mut self.src.js,
513                self.opts.instantiation_mode.is_some(),
514                &mut self.local_names,
515                opts,
516            );
517            uwrite!(
518                output,
519                "\
520                        let gen = (function* _initGenerator () {{
521                            {}\
522                            {};
523                        }})();
524                        let promise, resolve, reject;
525                        function runNext (value) {{
526                            try {{
527                                let done;
528                                do {{
529                                    ({{ value, done }} = gen.next(value));
530                                }} while (!(value instanceof Promise) && !done);
531                                if (done) {{
532                                    if (resolve) return resolve(value);
533                                    else return value;
534                                }}
535                                if (!promise) promise = new Promise((_resolve, _reject) => (resolve = _resolve, reject = _reject));
536                                value.then(nextVal => done ? resolve() : runNext(nextVal), reject);
537                            }}
538                            catch (e) {{
539                                if (reject) reject(e);
540                                else throw e;
541                            }}
542                        }}
543                        const maybeSyncReturn = runNext(null);
544                        return promise || maybeSyncReturn;
545                    }};
546                ",
547                &self.src.js_init as &str,
548                &self.src.js as &str,
549            );
550        } else {
551            let (maybe_init_export, maybe_init) =
552                if self.opts.tla_compat && opts.instantiation_mode.is_none() {
553                    uwriteln!(self.src.js_init, "_initialized = true;");
554                    (
555                        "\
556                        let _initialized = false;
557                        export ",
558                        "",
559                    )
560                } else {
561                    (
562                        "",
563                        "
564                        await $init;
565                    ",
566                    )
567                };
568
569            uwrite!(
570                output,
571                "\
572                    {}
573                    {}
574                    {}
575                    {maybe_init_export}const $init = (() => {{
576                        let gen = (function* _initGenerator () {{
577                            {}\
578                            {}\
579                            {}\
580                        }})();
581                        let promise, resolve, reject;
582                        function runNext (value) {{
583                            try {{
584                                let done;
585                                do {{
586                                    ({{ value, done }} = gen.next(value));
587                                }} while (!(value instanceof Promise) && !done);
588                                if (done) {{
589                                    if (resolve) resolve(value);
590                                    else return value;
591                                }}
592                                if (!promise) promise = new Promise((_resolve, _reject) => (resolve = _resolve, reject = _reject));
593                                value.then(runNext, reject);
594                            }}
595                            catch (e) {{
596                                if (reject) reject(e);
597                                else throw e;
598                            }}
599                        }}
600                        const maybeSyncReturn = runNext(null);
601                        return promise || maybeSyncReturn;
602                    }})();
603                    {maybe_init}\
604                ",
605                &js_intrinsics as &str,
606                &intrinsic_definitions as &str,
607                &self.src.js as &str,
608                &compilation_promises as &str,
609                &self.src.js_init as &str,
610                &core_exported_funcs as &str,
611            );
612
613            self.esm_bindgen.render_exports(
614                &mut output,
615                self.opts.instantiation_mode.is_some(),
616                &mut self.local_names,
617                opts,
618            );
619        }
620
621        let mut bytes = output.as_bytes();
622        // strip leading newline
623        if bytes[0] == b'\n' {
624            bytes = &bytes[1..];
625        }
626        files.push(&format!("{name}.js"), bytes);
627    }
628
629    fn intrinsic(&mut self, intrinsic: Intrinsic) -> String {
630        self.all_intrinsics.insert(intrinsic);
631        intrinsic.name().to_string()
632    }
633}
634
635/// Helper structure used to generate the `instantiate` method of a component.
636///
637/// This is the main structure for parsing the output of Wasmtime.
638pub(crate) struct Instantiator<'a, 'b> {
639    src: Source,
640    bindgen: &'a mut JsBindgen<'b>,
641    modules: &'a PrimaryMap<StaticModuleIndex, core::Translation<'a>>,
642    instances: PrimaryMap<RuntimeInstanceIndex, StaticModuleIndex>,
643    types: &'a ComponentTypes,
644    resolve: &'a Resolve,
645    world: WorldId,
646    sizes: SizeAlign,
647    component: &'a Component,
648
649    /// Map of error contexts tables for a given component & error context index pair
650    /// that have been initialized
651    error_context_component_initialized: PrimaryMap<RuntimeComponentInstanceIndex, bool>,
652    error_context_component_table_initialized:
653        PrimaryMap<TypeComponentLocalErrorContextTableIndex, bool>,
654
655    /// Component-level translation information, including trampolines
656    translation: &'a ComponentTranslation,
657
658    /// Lookup of exported types to resource indices
659    exports_resource_types: BTreeMap<TypeId, ResourceIndex>,
660    /// Lookup of resource indices to exported types
661    exports_resource_index_types: BTreeMap<ResourceIndex, TypeId>,
662
663    /// Lookup of imported types to resource indices
664    imports_resource_types: BTreeMap<TypeId, ResourceIndex>,
665    /// Lookup of resource indices to imported types
666    #[allow(unused)]
667    imports_resource_index_types: BTreeMap<ResourceIndex, TypeId>,
668
669    resources_initialized: BTreeMap<ResourceIndex, bool>,
670    resource_tables_initialized: BTreeMap<TypeResourceTableIndex, bool>,
671
672    exports: BTreeMap<String, WorldKey>,
673    imports: BTreeMap<String, WorldKey>,
674    /// Instance flags which references have been emitted externally at least once.
675    used_instance_flags: RefCell<BTreeSet<RuntimeComponentInstanceIndex>>,
676    defined_resource_classes: BTreeSet<String>,
677    async_imports: HashSet<String>,
678    async_exports: HashSet<String>,
679    lowering_options:
680        PrimaryMap<LoweredIndex, (&'a CanonicalOptions, TrampolineIndex, TypeFuncIndex)>,
681
682    /// Mapping of stream table indices to component indices
683    stream_tables: BTreeMap<TypeStreamTableIndex, RuntimeComponentInstanceIndex>,
684
685    /// Mapping of future table indices to component indices
686    future_tables: BTreeMap<TypeFutureTableIndex, RuntimeComponentInstanceIndex>,
687
688    /// Mapping of err ctx indices to component indices
689    err_ctx_tables:
690        BTreeMap<TypeComponentLocalErrorContextTableIndex, RuntimeComponentInstanceIndex>,
691
692    /// Map of exported resources built during export bindgen
693    resource_exports: ResourceMap,
694    /// Map of imported resources built during export bindgen
695    resource_imports: ResourceMap,
696
697    /// Component index of the module that is currentlty being initialized
698    ///
699    /// This is only populated after processing of `GlobalInitializer::InstantiateModule`
700    /// blocks has started, and is likely to be stale if read too late (i.e. it will be set
701    /// to the last module processed).
702    init_current_module: Option<RuntimeComponentInstanceIndex>,
703}
704
705impl<'a> ManagesIntrinsics for Instantiator<'a, '_> {
706    fn add_intrinsic(&mut self, intrinsic: Intrinsic) {
707        self.bindgen.intrinsic(intrinsic);
708    }
709}
710
711impl<'a> Instantiator<'a, '_> {
712    fn initialize(&mut self) {
713        // Populate reverse map from import and export names to world items
714        for (key, _) in &self.resolve.worlds[self.world].imports {
715            let name = &self.resolve.name_world_key(key);
716            self.imports.insert(name.to_string(), key.clone());
717        }
718        for (key, _) in &self.resolve.worlds[self.world].exports {
719            let name = &self.resolve.name_world_key(key);
720            self.exports.insert(name.to_string(), key.clone());
721        }
722
723        // Populate reverse map from TypeId to ResourceIndex
724        // Populate the resource type to resource index map
725        for (key, item) in &self.resolve.worlds[self.world].imports {
726            let name = &self.resolve.name_world_key(key);
727            let Some((_, (_, import))) = self
728                .component
729                .import_types
730                .iter()
731                .find(|(_, (impt_name, _))| impt_name == name)
732            else {
733                match item {
734                    WorldItem::Interface { .. } => {
735                        unreachable!("unexpected interface in import types during initialization")
736                    }
737                    WorldItem::Function(_) => {
738                        unreachable!("unexpected function in import types during initialization")
739                    }
740                    WorldItem::Type { id, .. } => {
741                        assert!(!matches!(
742                            self.resolve.types[*id].kind,
743                            TypeDefKind::Resource
744                        ))
745                    }
746                }
747                continue;
748            };
749            match item {
750                WorldItem::Interface { id, .. } => {
751                    let TypeDef::ComponentInstance(instance) = &import.ty else {
752                        unreachable!("unexpectedly non-component instance import in interface")
753                    };
754                    let import_ty = &self.types[*instance];
755                    let iface = &self.resolve.interfaces[*id];
756                    for (ty_name, ty) in &iface.types {
757                        match &import_ty.exports.get(ty_name) {
758                            None => {}
759                            Some(ComponentExtern {
760                                ty: TypeDef::Resource(resource_table_idx),
761                                ..
762                            }) => {
763                                let ty = crate::dealias(self.resolve, *ty);
764                                let resource_table_ty = &self.types[*resource_table_idx];
765                                let concrete_ty = resource_table_ty.unwrap_concrete_ty();
766                                self.imports_resource_types.insert(ty, concrete_ty);
767                                self.imports_resource_index_types.insert(concrete_ty, ty);
768                            }
769                            Some(ComponentExtern {
770                                ty: TypeDef::Interface(_),
771                                ..
772                            }) => {}
773                            Some(_) => unreachable!("unexpected type in interface"),
774                        }
775                    }
776                }
777                WorldItem::Function(_) => {}
778                WorldItem::Type { id, .. } => match import {
779                    ComponentExtern {
780                        ty: TypeDef::Resource(resource),
781                        ..
782                    } => {
783                        let ty = crate::dealias(self.resolve, *id);
784                        let resource_table_ty = &self.types[*resource];
785                        let concrete_ty = resource_table_ty.unwrap_concrete_ty();
786                        self.imports_resource_types.insert(ty, concrete_ty);
787                        self.imports_resource_index_types.insert(concrete_ty, ty);
788                    }
789                    ComponentExtern {
790                        ty: TypeDef::Interface(_),
791                        ..
792                    } => {}
793                    _ => unreachable!("unexpected type in import world item"),
794                },
795            }
796        }
797        self.exports_resource_types = self.imports_resource_types.clone();
798        self.exports_resource_index_types = self.imports_resource_index_types.clone();
799
800        for (key, item) in &self.resolve.worlds[self.world].exports {
801            let name = &self.resolve.name_world_key(key);
802            let (_, (export_idx, _extern_data)) = self
803                .component
804                .exports
805                .raw_iter()
806                .find(|(expt_name, _)| ***expt_name == **name)
807                .unwrap();
808            let export = &self.component.export_items[*export_idx];
809            match item {
810                WorldItem::Interface { id, .. } => {
811                    let iface = &self.resolve.interfaces[*id];
812                    let Export::Instance { exports, .. } = &export else {
813                        unreachable!("unexpectedly non export instance item")
814                    };
815                    for (ty_name, ty) in &iface.types {
816                        let (export_idx, _exern_data) =
817                            exports.get(ty_name, &NameMapNoIntern).unwrap();
818                        match self.component.export_items[*export_idx] {
819                            Export::Type(TypeDef::Resource(resource)) => {
820                                let ty = crate::dealias(self.resolve, *ty);
821                                let resource_table_ty = &self.types[resource];
822                                let concrete_ty = resource_table_ty.unwrap_concrete_ty();
823                                self.exports_resource_types.insert(ty, concrete_ty);
824                                self.exports_resource_index_types.insert(concrete_ty, ty);
825                            }
826                            Export::Type(_) => {}
827                            _ => unreachable!(
828                                "unexpected type in component export items on iface [{iface_name}]",
829                                iface_name = iface.name.as_deref().unwrap_or("<unknown>"),
830                            ),
831                        }
832                    }
833                }
834                WorldItem::Function(_) => {}
835                WorldItem::Type { .. } => unreachable!("unexpected exported world item type"),
836            }
837        }
838    }
839
840    fn instantiate(&mut self) {
841        // Handle all built in trampolines
842        for (i, trampoline) in self.translation.trampolines.iter() {
843            let Trampoline::LowerImport {
844                index,
845                lower_ty,
846                options,
847            } = trampoline
848            else {
849                continue;
850            };
851
852            let options = self
853                .component
854                .options
855                .get(*options)
856                .expect("failed to find canon options");
857
858            let i = self.lowering_options.push((options, i, *lower_ty));
859            assert_eq!(i, *index);
860        }
861
862        if let Some(InstantiationMode::Async) = self.bindgen.opts.instantiation_mode {
863            // To avoid uncaught promise rejection errors, we attach an intermediate
864            // Promise.all with a rejection handler, if there are multiple promises.
865            if self.modules.len() > 1 {
866                self.src.js_init.push_str("Promise.all([");
867                for i in 0..self.modules.len() {
868                    if i > 0 {
869                        self.src.js_init.push_str(", ");
870                    }
871                    self.src.js_init.push_str(&format!("module{i}"));
872                }
873                uwriteln!(self.src.js_init, "]).catch(() => {{}});");
874            }
875        }
876
877        // Set up global stream map, which is used by intrinsics like stream.transfer.
878        // Register the intrinsic so the `const STREAM_TABLES = {};` declaration
879        // is emitted before these per-table assignments — without that, the
880        // generated module references an undeclared identifier and fails to
881        // load (ReferenceError: STREAM_TABLES is not defined).
882        if !self.stream_tables.is_empty() {
883            let global_stream_table_map = self.bindgen.intrinsic(Intrinsic::AsyncStream(
884                AsyncStreamIntrinsic::GlobalStreamTableMap,
885            ));
886            let rep_table_class = Intrinsic::RepTableClass.name();
887            for (table_idx, component_idx) in self.stream_tables.iter() {
888                self.src.js.push_str(&format!(
889                    "{global_stream_table_map}[{}] = {{ componentIdx: {}, table: new {rep_table_class}() }};\n",
890                    table_idx.as_u32(),
891                    component_idx.as_u32(),
892                ));
893            }
894        }
895
896        // Set up global future map, which is used by intrinsics like future.transfer.
897        // Same registration fix as above for FUTURE_TABLES.
898        if !self.future_tables.is_empty() {
899            let global_future_table_map = self.bindgen.intrinsic(Intrinsic::AsyncFuture(
900                AsyncFutureIntrinsic::GlobalFutureTableMap,
901            ));
902            let rep_table_class = Intrinsic::RepTableClass.name();
903            for (table_idx, component_idx) in self.future_tables.iter() {
904                self.src.js.push_str(&format!(
905                    "{global_future_table_map}[{}] = {{ componentIdx: {}, table: new {rep_table_class}() }};\n",
906                    table_idx.as_u32(),
907                    component_idx.as_u32(),
908                ));
909            }
910        }
911
912        // Set up global error context map, which is used by intrinsics like err_ctx.transfer.
913        // Same registration fix as above for ERR_CTX_TABLES.
914        if !self.err_ctx_tables.is_empty() {
915            let global_err_ctx_table_map = self
916                .bindgen
917                .intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::GlobalErrCtxTableMap));
918            let rep_table_class = Intrinsic::RepTableClass.name();
919            for (table_idx, component_idx) in self.err_ctx_tables.iter() {
920                self.src.js.push_str(&format!(
921                    "{global_err_ctx_table_map}[{}] = {{ componentIdx: {}, table: new {rep_table_class}() }};\n",
922                    table_idx.as_u32(),
923                    component_idx.as_u32(),
924                ));
925            }
926        }
927
928        // Process global initializers
929        //
930        // The order of initialization is unfortunately quite fragile.
931        //
932        // We take care in processing module instantiations because we must ensure that
933        // $wit-component.fixups must be instantiated directly after $wit-component.shim
934        //
935        let mut lower_import_initializers = Vec::new();
936
937        // Process first n lower import initializers until the first instantiate module initializer
938        for init in self.component.initializers.iter() {
939            match init {
940                GlobalInitializer::InstantiateModule(_m, _maybe_idx) => {
941                    // Ensure lower import initializers are processed before the first module instantiation
942                    for lower_import_init in lower_import_initializers.drain(..) {
943                        self.instantiation_global_initializer(lower_import_init);
944                    }
945                }
946
947                // We push lower import initializers down to right before instantiate, so that the
948                // memory, realloc and postReturn functions are available to the import lowerings
949                // for optimized bindgen
950                GlobalInitializer::LowerImport { .. } => {
951                    lower_import_initializers.push(init);
952                    continue;
953                }
954                _ => {}
955            }
956
957            self.instantiation_global_initializer(init);
958        }
959
960        // Process lower import initializers that were discovered after the last module instantiation
961        for init in lower_import_initializers.drain(..) {
962            self.instantiation_global_initializer(init);
963        }
964
965        // Process imports and build mappings
966        self.process_imports();
967
968        // Process exports and build mappings
969        self.process_exports();
970
971        // Some trampolines that correspond to host-provided imports need to be defined before the
972        // instantiation bits since they are referred to.
973        for (i, trampoline) in self
974            .translation
975            .trampolines
976            .iter()
977            .filter(|(_, t)| Instantiator::is_early_trampoline(t))
978        {
979            self.trampoline(i, trampoline);
980        }
981
982        if self.bindgen.opts.instantiation_mode.is_some() {
983            let js_init = mem::take(&mut self.src.js_init);
984            self.src.js.push_str(&js_init);
985        }
986
987        // Trampolines here so we have static module indices, and resource maps populated
988        // (both imports and exports may still be populting resource map)
989        for (i, trampoline) in self
990            .translation
991            .trampolines
992            .iter()
993            .filter(|(_, t)| !Instantiator::is_early_trampoline(t))
994        {
995            self.trampoline(i, trampoline);
996        }
997    }
998
999    fn ensure_local_resource_class(&mut self, local_name: String) {
1000        if !self.defined_resource_classes.contains(&local_name) {
1001            uwriteln!(
1002                self.src.js,
1003                "\nclass {local_name} {{
1004                constructor () {{
1005                    throw new Error('\"{local_name}\" resource does not define a constructor');
1006                }}
1007            }}"
1008            );
1009            self.defined_resource_classes.insert(local_name.to_string());
1010        }
1011    }
1012
1013    fn resource_definitions(&mut self) {
1014        // It is theoretically possible for locally defined resources used in no functions
1015        // to still be exported
1016        for resource in 0..self.component.num_resources {
1017            let resource = ResourceIndex::from_u32(resource);
1018            let is_imported = self.component.defined_resource_index(resource).is_none();
1019            if is_imported {
1020                continue;
1021            }
1022            if let Some(local_name) = self.bindgen.local_names.try_get(resource) {
1023                self.ensure_local_resource_class(local_name.to_string());
1024            }
1025        }
1026
1027        // TODO(feat): In the past, we could eagerly build a mapping of resources defined to the tables they correspond to
1028        // to make runtime checks of which table a resource must have been created into first (i.e. the component that implements
1029        // creation of a given resource) a particular resource faster.
1030        //
1031        // This logic was based on component.num_resources and was broken for composed components --
1032        // wasmtime-environ reported a smaller number of resources, and the check was geared towards a *single* component,
1033        // not tying a particular component to a particular table (it is possible for *sub components* to originate different
1034        // resources).
1035        //
1036        // In theory, it should be posible to build this knowledge statically rather than at resource creation,
1037        // so in the future we should attempt to rebulid that code, if possible.
1038        //
1039        // Note that at runtime wasmtime maintains this information and looks it up during a transfer operation, see:
1040        //   - https://github.com/bytecodealliance/wasmtime/blob/5f3b67ea055857020bd1ac1f2c3f7fa2e6c31ec0/crates/wasmtime/src/runtime/component/instance.rs#L424
1041        //   - https://github.com/bytecodealliance/wasmtime/blob/9c49989a2e71382fd8639288088472f150f2f534/crates/wasmtime/src/runtime/vm/component.rs#L847
1042    }
1043
1044    /// Ensure a component-local `error-context` table has been created
1045    ///
1046    /// # Arguments
1047    ///
1048    /// * `component_idx` - component index
1049    /// * `err_ctx_tbl_idx` - The component-local error-context table index
1050    ///
1051    fn ensure_error_context_local_table(
1052        &mut self,
1053        component_idx: RuntimeComponentInstanceIndex,
1054        err_ctx_tbl_idx: TypeComponentLocalErrorContextTableIndex,
1055    ) {
1056        if self.error_context_component_initialized[component_idx]
1057            && self.error_context_component_table_initialized[err_ctx_tbl_idx]
1058        {
1059            return;
1060        }
1061        let err_ctx_local_tables = self
1062            .bindgen
1063            .intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ComponentLocalTable));
1064        let rep_table_class = Intrinsic::RepTableClass.name();
1065        let c = component_idx.as_u32();
1066        if !self.error_context_component_initialized[component_idx] {
1067            uwriteln!(self.src.js, "{err_ctx_local_tables}.set({c}, new Map());");
1068            self.error_context_component_initialized[component_idx] = true;
1069        }
1070        if !self.error_context_component_table_initialized[err_ctx_tbl_idx] {
1071            let t = err_ctx_tbl_idx.as_u32();
1072            uwriteln!(
1073                self.src.js,
1074                "{err_ctx_local_tables}.get({c}).set({t}, new {rep_table_class}({{ target: `component [{c}] local error ctx table [{t}]` }}));"
1075            );
1076            self.error_context_component_table_initialized[err_ctx_tbl_idx] = true;
1077        }
1078    }
1079
1080    /// Ensure that a resource table has been initialized
1081    ///
1082    /// For the relevant resource table, this function will generate initialization
1083    /// blocks, exactly once.
1084    ///
1085    /// This is not done for *all* resources, but instead for those that are explicitly used.
1086    fn ensure_resource_table(&mut self, resource_table_idx: TypeResourceTableIndex) {
1087        if self
1088            .resource_tables_initialized
1089            .contains_key(&resource_table_idx)
1090        {
1091            return;
1092        }
1093
1094        let resource_table_ty = &self.types[resource_table_idx];
1095        let resource_idx = resource_table_ty.unwrap_concrete_ty();
1096
1097        let (is_imported, maybe_dtor) =
1098            if let Some(resource_idx) = self.component.defined_resource_index(resource_idx) {
1099                let resource_def = self
1100                    .component
1101                    .initializers
1102                    .iter()
1103                    .find_map(|i| match i {
1104                        GlobalInitializer::Resource(r) if r.index == resource_idx => Some(r),
1105                        _ => None,
1106                    })
1107                    .unwrap();
1108
1109                if let Some(dtor) = &resource_def.dtor {
1110                    (false, format!("\n{}(rep);", self.core_def(dtor)))
1111                } else {
1112                    (false, "".into())
1113                }
1114            } else {
1115                (true, "".into())
1116            };
1117
1118        let handle_tables = self.bindgen.intrinsic(Intrinsic::HandleTables);
1119        let rsc_table_flag = self
1120            .bindgen
1121            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
1122        let rsc_table_remove = self
1123            .bindgen
1124            .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
1125
1126        // Create the relevant handle table
1127        let rtid = resource_table_idx.as_u32();
1128        if is_imported {
1129            // imported
1130            uwriteln!(
1131                self.src.js,
1132                r#"
1133                  const handleTable{rtid} = [{rsc_table_flag}, 0];
1134                  handleTable{rtid}._createdReps = new Set();
1135                "#,
1136            );
1137            if !self.resources_initialized.contains_key(&resource_idx) {
1138                let ridx = resource_idx.as_u32();
1139                uwriteln!(
1140                    self.src.js,
1141                    r#"
1142                      const captureTable{ridx} = new Map();
1143                      let captureCnt{ridx} = 0;
1144                    "#
1145                );
1146                self.resources_initialized.insert(resource_idx, true);
1147            }
1148        } else {
1149            // non imported
1150            let finalization_registry_create = self
1151                .bindgen
1152                .intrinsic(Intrinsic::FinalizationRegistryCreate);
1153            uwriteln!(
1154                self.src.js,
1155                r#"
1156                   const handleTable{rtid} = [{rsc_table_flag}, 0];
1157                   handleTable{rtid}._createdReps = new Set();
1158                   const finalizationRegistry{rtid} = {finalization_registry_create}((handle) => {{
1159                       const {{ rep }} = {rsc_table_remove}(handleTable{rtid}, handle);{maybe_dtor}
1160                   }});
1161                "#,
1162            );
1163        }
1164
1165        // Add the handle table to the global list
1166        uwriteln!(self.src.js, "{handle_tables}[{rtid}] = handleTable{rtid};");
1167        self.resource_tables_initialized
1168            .insert(resource_table_idx, true);
1169    }
1170
1171    fn instance_flags(&mut self) {
1172        // SAFETY: short-lived borrow, and the refcell isn't mutably borrowed in the loop's body.
1173        let mut instance_flag_defs = String::new();
1174        for used in self.used_instance_flags.borrow().iter() {
1175            let i = used.as_u32();
1176            // As of wasmtime-environ 47 the per-instance flags global holds a
1177            // single boolean `may_leave` flag, so initialize it to `1`.
1178            uwriteln!(
1179                &mut instance_flag_defs,
1180                "const instanceFlags{i} = new WebAssembly.Global({{ value: \"i32\", mutable: true }}, 1);",
1181            );
1182        }
1183        self.src.js_init.prepend_str(&instance_flag_defs);
1184    }
1185
1186    // Trampolines defined in is_early_trampoline() below that use:
1187    //   const trampoline{} = ...
1188    // require early initialization since their bindings aren't auto-hoisted
1189    // like JS functions are in the JS runtime.
1190    fn is_early_trampoline(trampoline: &Trampoline) -> bool {
1191        matches!(
1192            trampoline,
1193            Trampoline::AsyncStartCall { .. }
1194                | Trampoline::BackpressureDec { .. }
1195                | Trampoline::BackpressureInc { .. }
1196                | Trampoline::EnterSyncCall
1197                | Trampoline::ErrorContextDebugMessage { .. }
1198                | Trampoline::ErrorContextDrop { .. }
1199                | Trampoline::ErrorContextNew { .. }
1200                | Trampoline::ErrorContextTransfer
1201                | Trampoline::ExitSyncCall
1202                | Trampoline::FutureCancelRead { .. }
1203                | Trampoline::FutureCancelWrite { .. }
1204                | Trampoline::FutureDropReadable { .. }
1205                | Trampoline::FutureDropWritable { .. }
1206                | Trampoline::FutureNew { .. }
1207                | Trampoline::FutureRead { .. }
1208                | Trampoline::FutureWrite { .. }
1209                | Trampoline::LowerImport { .. }
1210                | Trampoline::PrepareCall { .. }
1211                | Trampoline::ResourceDrop { .. }
1212                | Trampoline::ResourceNew { .. }
1213                | Trampoline::ResourceRep { .. }
1214                | Trampoline::ResourceTransferBorrow
1215                | Trampoline::ResourceTransferOwn
1216                | Trampoline::StreamCancelRead { .. }
1217                | Trampoline::StreamCancelWrite { .. }
1218                | Trampoline::StreamDropReadable { .. }
1219                | Trampoline::StreamDropWritable { .. }
1220                | Trampoline::StreamNew { .. }
1221                | Trampoline::StreamRead { .. }
1222                | Trampoline::StreamTransfer
1223                | Trampoline::StreamWrite { .. }
1224                | Trampoline::SubtaskCancel { .. }
1225                | Trampoline::SubtaskDrop { .. }
1226                | Trampoline::SyncStartCall { .. }
1227                | Trampoline::TaskCancel { .. }
1228                | Trampoline::TaskReturn { .. }
1229                | Trampoline::ThreadYield { .. }
1230                | Trampoline::ThreadYieldToSuspended { .. }
1231                | Trampoline::WaitableJoin { .. }
1232                | Trampoline::WaitableSetDrop { .. }
1233                | Trampoline::WaitableSetNew { .. }
1234                | Trampoline::WaitableSetPoll { .. }
1235                | Trampoline::WaitableSetWait { .. }
1236        )
1237    }
1238
1239    fn trampoline(&mut self, i: TrampolineIndex, trampoline: &'a Trampoline) {
1240        let i = i.as_u32();
1241        match trampoline {
1242            Trampoline::TaskCancel { instance } => {
1243                let task_cancel_fn = self
1244                    .bindgen
1245                    .intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::TaskCancel));
1246                uwriteln!(
1247                    self.src.js,
1248                    "const trampoline{i} = {task_cancel_fn}.bind(null, {instance_idx});\n",
1249                    instance_idx = instance.as_u32(),
1250                );
1251            }
1252
1253            Trampoline::SubtaskCancel { instance, async_ } => {
1254                let task_cancel_fn = self
1255                    .bindgen
1256                    .intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::SubtaskCancel));
1257                uwriteln!(
1258                    self.src.js,
1259                    "const trampoline{i} = {task_cancel_fn}.bind(null, {instance_idx}, {async_});\n",
1260                    instance_idx = instance.as_u32(),
1261                );
1262            }
1263
1264            Trampoline::SubtaskDrop { instance } => {
1265                let component_idx = instance.as_u32();
1266                let subtask_drop_fn = self
1267                    .bindgen
1268                    .intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::SubtaskDrop));
1269                uwriteln!(
1270                    self.src.js,
1271                    "const trampoline{i} = {subtask_drop_fn}.bind(
1272                         null,
1273                         {component_idx},
1274                     );"
1275                );
1276            }
1277
1278            Trampoline::WaitableSetNew { instance } => {
1279                let waitable_set_new_fn = self
1280                    .bindgen
1281                    .intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetNew));
1282                uwriteln!(
1283                    self.src.js,
1284                    "const trampoline{i} = {waitable_set_new_fn}.bind(null, {});\n",
1285                    instance.as_u32(),
1286                );
1287            }
1288
1289            Trampoline::WaitableSetWait { instance, options } => {
1290                let options = self
1291                    .component
1292                    .options
1293                    .get(*options)
1294                    .expect("failed to find options");
1295                assert_eq!(
1296                    instance.as_u32(),
1297                    options.instance.as_u32(),
1298                    "options index instance must match trampoline"
1299                );
1300
1301                let CanonicalOptions {
1302                    instance,
1303                    async_,
1304                    data_model:
1305                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, .. }),
1306                    ..
1307                } = options
1308                else {
1309                    panic!("unexpected/missing memory data model during waitable-set.wait");
1310                };
1311
1312                let instance_idx = instance.as_u32();
1313                let memory_idx = memory
1314                    .expect("missing memory idx for waitable-set.wait")
1315                    .as_u32();
1316                let waitable_set_wait_fn = self
1317                    .bindgen
1318                    .intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetWait));
1319
1320                uwriteln!(
1321                    self.src.js,
1322                    r#"
1323                    const trampoline{i} = new WebAssembly.Suspending({waitable_set_wait_fn}.bind(null, {{
1324                        componentIdx: {instance_idx},
1325                        isAsync: {async_},
1326                        memoryIdx: {memory_idx},
1327                        getMemoryFn: () => memory{memory_idx},
1328                    }}));
1329                    "#,
1330                );
1331            }
1332
1333            Trampoline::WaitableSetPoll { options, .. } => {
1334                let CanonicalOptions {
1335                    instance,
1336                    async_,
1337                    data_model:
1338                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, .. }),
1339                    cancellable,
1340                    ..
1341                } = self
1342                    .component
1343                    .options
1344                    .get(*options)
1345                    .expect("failed to find options")
1346                else {
1347                    panic!("unexpected memory data model during waitable-set.poll");
1348                };
1349
1350                let instance_idx = instance.as_u32();
1351                let memory_idx = memory
1352                    .expect("missing memory idx for waitable-set.poll")
1353                    .as_u32();
1354                let waitable_set_poll_fn = self
1355                    .bindgen
1356                    .intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetPoll));
1357
1358                uwriteln!(
1359                    self.src.js,
1360                    r#"
1361                    const trampoline{i} = {waitable_set_poll_fn}.bind(
1362                        null,
1363                        {{
1364                            componentIdx: {instance_idx},
1365                            isAsync: {async_},
1366                            isCancellable: {cancellable},
1367                            memoryIdx: {memory_idx},
1368                            getMemoryFn: () => memory{memory_idx},
1369                        }}
1370                    );
1371                    "#,
1372                );
1373            }
1374
1375            Trampoline::WaitableSetDrop { instance } => {
1376                let waitable_set_drop_fn = self
1377                    .bindgen
1378                    .intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableSetDrop));
1379                uwriteln!(
1380                    self.src.js,
1381                    "const trampoline{i} = {waitable_set_drop_fn}.bind(null, {instance_idx});\n",
1382                    instance_idx = instance.as_u32(),
1383                );
1384            }
1385
1386            Trampoline::WaitableJoin { instance } => {
1387                let waitable_join_fn = self
1388                    .bindgen
1389                    .intrinsic(Intrinsic::Waitable(WaitableIntrinsic::WaitableJoin));
1390                uwriteln!(
1391                    self.src.js,
1392                    "const trampoline{i} = {waitable_join_fn}.bind(null, {instance_idx});\n",
1393                    instance_idx = instance.as_u32(),
1394                );
1395            }
1396
1397            Trampoline::StreamNew { ty, instance } => {
1398                let stream_new_fn = self
1399                    .bindgen
1400                    .intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamNew));
1401                let instance_idx = instance.as_u32();
1402                let stream_table_idx = ty.as_u32();
1403
1404                // Get to the payload type for the given stream table idx
1405                let table_ty = &self.types[*ty];
1406                let stream_ty_idx = table_ty.ty;
1407                let stream_ty = &self.types[stream_ty_idx];
1408
1409                // TODO(???): do we have no way to go from interface type to in-component type idx?
1410                // TODO(???): does this work under type aliases?? we need the type def?
1411                // TODO(???): can the stream type be treated as a unique indicator of the payload type? maybe not?
1412                // need a way to go from iface type + stream type -> payload type idx?
1413                let payload_ty_name_js = stream_ty
1414                    .payload
1415                    .map(|iface_ty| format!("'{iface_ty:?}'"))
1416                    .unwrap_or_else(|| "null".into());
1417
1418                // Gather type metadata
1419                let (
1420                    align_32_js,
1421                    size_32_js,
1422                    flat_count_js,
1423                    lift_fn_js,
1424                    lower_fn_js,
1425                    is_none_js,
1426                    is_numeric_type_js,
1427                    is_borrow_js,
1428                    is_async_value_js,
1429                    typed_array_js,
1430                ) = match stream_ty.payload {
1431                    // If there is no payload for the stream, we know the values
1432                    None => (
1433                        "0".into(),
1434                        "0".into(),
1435                        "0".into(),
1436                        "null".into(),
1437                        "null".into(),
1438                        "true",
1439                        "false".into(),
1440                        "false".into(),
1441                        "false".into(),
1442                        "undefined",
1443                    ),
1444                    // If there is a payload, generate relevant lift/lower and other metadata
1445                    Some(ty) => (
1446                        self.types.canonical_abi(&ty).align32.to_string(),
1447                        self.types.canonical_abi(&ty).size32.to_string(),
1448                        self.types
1449                            .canonical_abi(&ty)
1450                            .flat_count
1451                            .map(|v| v.to_string())
1452                            .unwrap_or_else(|| "null".into()),
1453                        gen_flat_lift_fn_js_expr(self, &ty, &None),
1454                        gen_flat_lower_fn_js_expr(self, &ty, &None),
1455                        "false",
1456                        format!(
1457                            "{}",
1458                            matches!(
1459                                ty,
1460                                InterfaceType::U8
1461                                    | InterfaceType::U16
1462                                    | InterfaceType::U32
1463                                    | InterfaceType::U64
1464                                    | InterfaceType::S8
1465                                    | InterfaceType::S16
1466                                    | InterfaceType::S32
1467                                    | InterfaceType::S64
1468                                    | InterfaceType::Float32
1469                                    | InterfaceType::Float64
1470                            )
1471                        ),
1472                        format!("{}", matches!(ty, InterfaceType::Borrow(_))),
1473                        format!(
1474                            "{}",
1475                            matches!(ty, InterfaceType::Stream(_) | InterfaceType::Future(_))
1476                        ),
1477                        js_typed_array_ctor(&ty).unwrap_or("undefined"),
1478                    ),
1479                };
1480
1481                uwriteln!(
1482                    self.src.js,
1483                    "const trampoline{i} = {stream_new_fn}.bind(null, {{
1484                        streamTableIdx: {stream_table_idx},
1485                        callerComponentIdx: {instance_idx},
1486                        elemMeta: {{
1487                            liftFn: {lift_fn_js},
1488                            lowerFn: {lower_fn_js},
1489                            payloadTypeName: {payload_ty_name_js},
1490                            isNone: {is_none_js},
1491                            isNumeric: {is_numeric_type_js},
1492                            isBorrowed: {is_borrow_js},
1493                            isAsyncValue: {is_async_value_js},
1494                            typedArray: {typed_array_js},
1495                            flatCount: {flat_count_js},
1496                            align32: {align_32_js},
1497                            size32: {size_32_js},
1498                        }},
1499                    }});\n",
1500                );
1501            }
1502
1503            Trampoline::StreamRead {
1504                instance,
1505                ty,
1506                options,
1507            } => {
1508                let options = self
1509                    .component
1510                    .options
1511                    .get(*options)
1512                    .expect("failed to find options");
1513                assert_eq!(
1514                    instance.as_u32(),
1515                    options.instance.as_u32(),
1516                    "options index instance must match trampoline"
1517                );
1518
1519                let CanonicalOptions {
1520                    instance,
1521                    string_encoding,
1522                    async_,
1523                    data_model:
1524                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
1525                    ..
1526                } = options
1527                else {
1528                    unreachable!("missing/invalid data model for options during stream.read")
1529                };
1530                let memory_idx = memory.expect("missing memory idx for stream.read").as_u32();
1531                let (realloc_idx, get_realloc_fn_js) = match realloc {
1532                    Some(v) => {
1533                        let v = v.as_u32().to_string();
1534                        (v.to_string(), format!("() => realloc{v}"))
1535                    }
1536                    None => ("undefined".into(), "undefined".into()),
1537                };
1538
1539                let component_instance_id = instance.as_u32();
1540                let string_encoding = string_encoding_js_literal(string_encoding);
1541                let stream_table_idx = ty.as_u32();
1542                let stream_read_fn = self
1543                    .bindgen
1544                    .intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamRead));
1545
1546                // PrepareCall for an async call is sometimes missing memories,
1547                // so we augment and save here, knowing that any stream.write/read operation
1548                // that uses a memory is indicative of that component's memory
1549                //
1550                let register_global_memory_for_component_fn =
1551                    Intrinsic::RegisterGlobalMemoryForComponent.name();
1552                uwriteln!(
1553                    self.src.js_init,
1554                    r#"{register_global_memory_for_component_fn}({{
1555                         componentIdx: {component_instance_id},
1556                         memoryIdx: {memory_idx},
1557                         memory: memory{memory_idx},
1558                     }});"#
1559                );
1560
1561                uwriteln!(
1562                    self.src.js,
1563                    r#"const trampoline{i} = new WebAssembly.Suspending({stream_read_fn}.bind(
1564                         null,
1565                         {{
1566                             componentIdx: {component_instance_id},
1567                             memoryIdx: {memory_idx},
1568                             getMemoryFn: () => memory{memory_idx},
1569                             reallocIdx: {realloc_idx},
1570                             getReallocFn: {get_realloc_fn_js},
1571                             stringEncoding: {string_encoding},
1572                             isAsync: {async_},
1573                             streamTableIdx: {stream_table_idx},
1574                         }}
1575                     ));
1576                    "#,
1577                );
1578            }
1579
1580            Trampoline::StreamWrite {
1581                instance,
1582                ty,
1583                options,
1584            } => {
1585                let options = self
1586                    .component
1587                    .options
1588                    .get(*options)
1589                    .expect("failed to find options");
1590                assert_eq!(
1591                    instance.as_u32(),
1592                    options.instance.as_u32(),
1593                    "options index instance must match trampoline"
1594                );
1595
1596                let CanonicalOptions {
1597                    instance,
1598                    string_encoding,
1599                    async_,
1600                    data_model:
1601                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
1602                    ..
1603                } = options
1604                else {
1605                    unreachable!("unexpected memory data model during stream.write");
1606                };
1607                let component_instance_id = instance.as_u32();
1608                let memory_idx = memory
1609                    .expect("missing memory idx for stream.write")
1610                    .as_u32();
1611                let (realloc_idx, get_realloc_fn_js) = match realloc {
1612                    Some(v) => {
1613                        let v = v.as_u32().to_string();
1614                        (v.to_string(), format!("() => realloc{v}"))
1615                    }
1616                    None => ("undefined".into(), "undefined".into()),
1617                };
1618
1619                let string_encoding = string_encoding_js_literal(string_encoding);
1620                let stream_table_idx = ty.as_u32();
1621                let stream_write_fn = self
1622                    .bindgen
1623                    .intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamWrite));
1624
1625                // PrepareCall for an async call is sometimes missing memories,
1626                // so we augment and save here, knowing that any stream.write/read operation
1627                // that uses a memory is indicative of that component's memory
1628                let register_global_memory_for_component_fn =
1629                    Intrinsic::RegisterGlobalMemoryForComponent.name();
1630                uwriteln!(
1631                    self.src.js_init,
1632                    r#"{register_global_memory_for_component_fn}({{
1633                         componentIdx: {component_instance_id},
1634                         memoryIdx: {memory_idx},
1635                         memory: memory{memory_idx},
1636                     }});"#
1637                );
1638
1639                uwriteln!(
1640                    self.src.js,
1641                    r#"
1642                     const trampoline{i} = new WebAssembly.Suspending({stream_write_fn}.bind(
1643                         null,
1644                         {{
1645                             componentIdx: {component_instance_id},
1646                             memoryIdx: {memory_idx},
1647                             getMemoryFn: () => memory{memory_idx},
1648                             reallocIdx: {realloc_idx},
1649                             getReallocFn: {get_realloc_fn_js},
1650                             stringEncoding: {string_encoding},
1651                             isAsync: {async_},
1652                             streamTableIdx: {stream_table_idx},
1653                         }}
1654                     ));
1655                    "#,
1656                );
1657            }
1658
1659            Trampoline::StreamCancelRead {
1660                instance,
1661                ty,
1662                async_,
1663            }
1664            | Trampoline::StreamCancelWrite {
1665                instance,
1666                ty,
1667                async_,
1668            } => {
1669                let stream_cancel_fn = match trampoline {
1670                    Trampoline::StreamCancelRead { .. } => self.bindgen.intrinsic(
1671                        Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamCancelRead),
1672                    ),
1673                    Trampoline::StreamCancelWrite { .. } => self.bindgen.intrinsic(
1674                        Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamCancelWrite),
1675                    ),
1676                    _ => unreachable!("unexpected trampoline"),
1677                };
1678
1679                let stream_table_idx = ty.as_u32();
1680                let component_idx = instance.as_u32();
1681                uwriteln!(
1682                    self.src.js,
1683                    r#"
1684                      const trampoline{i} = new WebAssembly.Suspending({stream_cancel_fn}.bind(null, {{
1685                          streamTableIdx: {stream_table_idx},
1686                          isAsync: {async_},
1687                          componentIdx: {component_idx},
1688                      }}));
1689                    "#,
1690                );
1691            }
1692
1693            Trampoline::StreamDropReadable { ty, instance }
1694            | Trampoline::StreamDropWritable { ty, instance } => {
1695                let intrinsic_fn = match trampoline {
1696                    Trampoline::StreamDropReadable { .. } => self.bindgen.intrinsic(
1697                        Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamDropReadable),
1698                    ),
1699                    Trampoline::StreamDropWritable { .. } => self.bindgen.intrinsic(
1700                        Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamDropWritable),
1701                    ),
1702                    _ => unreachable!("unexpected trampoline"),
1703                };
1704                let stream_idx = ty.as_u32();
1705                let instance_idx = instance.as_u32();
1706                uwriteln!(
1707                    self.src.js,
1708                    "const trampoline{i} = {intrinsic_fn}.bind(null, {{
1709                        streamTableIdx: {stream_idx},
1710                        componentIdx: {instance_idx},
1711                    }});\n",
1712                );
1713            }
1714
1715            Trampoline::StreamTransfer => {
1716                let stream_transfer_fn = self
1717                    .bindgen
1718                    .intrinsic(Intrinsic::AsyncStream(AsyncStreamIntrinsic::StreamTransfer));
1719                uwriteln!(self.src.js, "const trampoline{i} = {stream_transfer_fn};\n",);
1720            }
1721
1722            Trampoline::FutureNew { instance, ty } => {
1723                let future_new_fn = self
1724                    .bindgen
1725                    .intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureNew));
1726                let future_table_idx = ty.as_u32();
1727                let component_idx = instance.as_u32();
1728
1729                // Build element metadata
1730                let future_table_ty = &self.types[*ty];
1731                let future_ty = &self.types[future_table_ty.ty];
1732                let (
1733                    payload_size32,
1734                    payload_align32,
1735                    payload_flat_count_js,
1736                    payload_lift_fn_js,
1737                    payload_lower_fn_js,
1738                    is_borrowed,
1739                    is_none_type,
1740                    is_numeric_type,
1741                    is_async_value,
1742                ) = match future_ty.payload {
1743                    None => (
1744                        0,
1745                        0,
1746                        "0".into(),
1747                        "() => {{ throw new Error('empty future payload'); }}".into(),
1748                        "() => {{ throw new Error('empty future payload'); }}".into(),
1749                        false,
1750                        true,
1751                        false,
1752                        false,
1753                    ),
1754                    Some(payload_ty) => {
1755                        let cabi = self.types.canonical_abi(&payload_ty);
1756                        (
1757                            cabi.size32,
1758                            cabi.align32,
1759                            cabi.flat_count
1760                                .map(|v| format!("{v}"))
1761                                .unwrap_or_else(|| "null".into()),
1762                            gen_flat_lift_fn_js_expr(self, &payload_ty, &None),
1763                            gen_flat_lower_fn_js_expr(self, &payload_ty, &None),
1764                            matches!(payload_ty, InterfaceType::Borrow(_)),
1765                            false,
1766                            matches!(
1767                                payload_ty,
1768                                InterfaceType::U8
1769                                    | InterfaceType::U16
1770                                    | InterfaceType::U32
1771                                    | InterfaceType::U64
1772                                    | InterfaceType::S8
1773                                    | InterfaceType::S16
1774                                    | InterfaceType::S32
1775                                    | InterfaceType::S64
1776                                    | InterfaceType::Float32
1777                                    | InterfaceType::Float64
1778                            ),
1779                            matches!(
1780                                payload_ty,
1781                                InterfaceType::Stream(_) | InterfaceType::Future(_)
1782                            ),
1783                        )
1784                    }
1785                };
1786                let payload_ty_name_js = future_ty
1787                    .payload
1788                    .map(|iface_ty| format!("'{iface_ty:?}'"))
1789                    .unwrap_or_else(|| "null".into());
1790
1791                uwriteln!(
1792                    self.src.js,
1793                    r#"
1794                      const trampoline{i} = {future_new_fn}.bind(null, {{
1795                          componentIdx: {component_idx},
1796                          futureTableIdx: {future_table_idx},
1797                          elemMeta: {{
1798                              liftFn: {payload_lift_fn_js},
1799                              lowerFn: {payload_lower_fn_js},
1800                              payloadTypeName: {payload_ty_name_js},
1801                              isNone: {is_none_type},
1802                              isNumeric: {is_numeric_type},
1803                              isBorrowed: {is_borrowed},
1804                              isAsyncValue: {is_async_value},
1805                              flatCount: {payload_flat_count_js},
1806                              align32: {payload_align32},
1807                              size32: {payload_size32},
1808                          }},
1809                      }});
1810                    "#,
1811                );
1812            }
1813
1814            Trampoline::FutureWrite {
1815                instance,
1816                ty,
1817                options,
1818            }
1819            | Trampoline::FutureRead {
1820                instance,
1821                ty,
1822                options,
1823            } => {
1824                let intrinsic_fn = match trampoline {
1825                    Trampoline::FutureRead { .. } => self
1826                        .bindgen
1827                        .intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureRead)),
1828                    Trampoline::FutureWrite { .. } => self
1829                        .bindgen
1830                        .intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureWrite)),
1831                    _ => unreachable!("invalid trampoline"),
1832                };
1833
1834                let options = self
1835                    .component
1836                    .options
1837                    .get(*options)
1838                    .expect("failed to find options");
1839                let CanonicalOptions {
1840                    async_,
1841                    string_encoding,
1842                    callback,
1843                    post_return,
1844                    data_model:
1845                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
1846                    ..
1847                } = options
1848                else {
1849                    unreachable!("unexpected memory data model during future intrinsic");
1850                };
1851
1852                assert_eq!(
1853                    *instance, options.instance,
1854                    "component instances should match"
1855                );
1856                assert!(
1857                    callback.is_none(),
1858                    "callback should not be present for future intrinsic"
1859                );
1860                assert!(
1861                    post_return.is_none(),
1862                    "post_return should not be present for future intrinsic"
1863                );
1864
1865                let future_table_idx = ty.as_u32();
1866                let component_idx = instance.as_u32();
1867                let memory_idx = memory
1868                    .expect("missing memory idx for future intrinsic")
1869                    .as_u32();
1870                let (realloc_idx, get_realloc_fn_js) = match realloc {
1871                    Some(idx) => (
1872                        idx.as_u32().to_string(),
1873                        format!("() => realloc{}", idx.as_u32()),
1874                    ),
1875                    None => ("undefined".into(), "undefined".to_string()),
1876                };
1877                let string_encoding = string_encoding_js_literal(string_encoding);
1878
1879                uwriteln!(
1880                    self.src.js,
1881                    r#"
1882                      const trampoline{i} = new WebAssembly.Suspending({intrinsic_fn}.bind(
1883                          null,
1884                          {{
1885                              componentIdx: {component_idx},
1886                              memoryIdx: {memory_idx},
1887                              getMemoryFn: () => memory{memory_idx},
1888                              reallocIdx: {realloc_idx},
1889                              getReallocFn: {get_realloc_fn_js},
1890                              stringEncoding: {string_encoding},
1891                              futureTableIdx: {future_table_idx},
1892                              isAsync: {async_},
1893                          }},
1894                      ));
1895                    "#,
1896                );
1897            }
1898
1899            Trampoline::FutureCancelRead {
1900                instance,
1901                ty,
1902                async_,
1903            }
1904            | Trampoline::FutureCancelWrite {
1905                instance,
1906                ty,
1907                async_,
1908            } => {
1909                let future_cancel_op_fn = match trampoline {
1910                    Trampoline::FutureCancelRead { .. } => self.bindgen.intrinsic(
1911                        Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureCancelRead),
1912                    ),
1913                    Trampoline::FutureCancelWrite { .. } => self.bindgen.intrinsic(
1914                        Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureCancelWrite),
1915                    ),
1916                    _ => unreachable!(),
1917                };
1918
1919                let component_idx = instance.as_u32();
1920                let future_table_idx = ty.as_u32();
1921
1922                uwriteln!(
1923                    self.src.js,
1924                    r#"
1925                      const trampoline{i} = new WebAssembly.Suspending({future_cancel_op_fn}.bind(
1926                          null,
1927                          {{
1928                              futureTableIdx: {future_table_idx},
1929                              componentIdx: {component_idx},
1930                              isAsync: {async_},
1931                          }},
1932                      ));
1933                    "#,
1934                );
1935            }
1936
1937            Trampoline::FutureDropReadable { instance, ty }
1938            | Trampoline::FutureDropWritable { instance, ty } => {
1939                let future_drop_op_fn = match trampoline {
1940                    Trampoline::FutureDropReadable { .. } => self.bindgen.intrinsic(
1941                        Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureDropReadable),
1942                    ),
1943                    Trampoline::FutureDropWritable { .. } => self.bindgen.intrinsic(
1944                        Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureDropWritable),
1945                    ),
1946                    _ => unreachable!(),
1947                };
1948
1949                let component_idx = instance.as_u32();
1950                let future_table_idx = ty.as_u32();
1951
1952                uwriteln!(
1953                    self.src.js,
1954                    r#"
1955                      const trampoline{i} = new WebAssembly.Suspending({future_drop_op_fn}.bind(
1956                          null,
1957                          {{
1958                              futureTableIdx: {future_table_idx},
1959                              componentIdx: {component_idx},
1960                          }},
1961                      ));
1962                "#
1963                );
1964            }
1965
1966            Trampoline::FutureTransfer => {
1967                let future_drop_writable_fn = self
1968                    .bindgen
1969                    .intrinsic(Intrinsic::AsyncFuture(AsyncFutureIntrinsic::FutureTransfer));
1970                uwriteln!(
1971                    self.src.js,
1972                    "const trampoline{i} = {future_drop_writable_fn};"
1973                );
1974            }
1975
1976            Trampoline::ErrorContextNew { ty, options, .. } => {
1977                let CanonicalOptions {
1978                    instance,
1979                    string_encoding,
1980                    data_model:
1981                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, .. }),
1982                    ..
1983                } = self
1984                    .component
1985                    .options
1986                    .get(*options)
1987                    .expect("failed to find options")
1988                else {
1989                    panic!("unexpected memory data model during error-context.new");
1990                };
1991
1992                self.ensure_error_context_local_table(*instance, *ty);
1993
1994                let local_err_tbl_idx = ty.as_u32();
1995                let component_idx = instance.as_u32();
1996
1997                let memory_idx = memory
1998                    .expect("missing realloc fn idx for error-context.debug-message")
1999                    .as_u32();
2000
2001                // Generate a string decoding function to match this trampoline that does appropriate encoding
2002                let decoder = match string_encoding {
2003                    wasmtime_environ::component::StringEncoding::Utf8 => self
2004                        .bindgen
2005                        .intrinsic(Intrinsic::String(StringIntrinsic::GlobalTextDecoderUtf8)),
2006                    wasmtime_environ::component::StringEncoding::Utf16 => self
2007                        .bindgen
2008                        .intrinsic(Intrinsic::String(StringIntrinsic::Utf16Decoder)),
2009                    enc => panic!(
2010                        "unsupported string encoding [{enc:?}] for error-context.debug-message"
2011                    ),
2012                };
2013                uwriteln!(
2014                    self.src.js,
2015                    "function trampoline{i}InputStr(ptr, len) {{
2016                         return {decoder}.decode(new DataView(memory{memory_idx}.buffer, ptr, len));
2017                    }}"
2018                );
2019
2020                let err_ctx_new_fn = self
2021                    .bindgen
2022                    .intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextNew));
2023                // Store the options associated with this new error context for later use in the global array
2024                uwriteln!(
2025                    self.src.js,
2026                    "const trampoline{i} = {err_ctx_new_fn}.bind(
2027                         null,
2028                         {{
2029                             componentIdx: {component_idx},
2030                             localTableIdx: {local_err_tbl_idx},
2031                             readStrFn: trampoline{i}InputStr,
2032                         }}
2033                     );
2034                    "
2035                );
2036            }
2037
2038            Trampoline::ErrorContextDebugMessage {
2039                instance, options, ..
2040            } => {
2041                let CanonicalOptions {
2042                    async_,
2043                    callback,
2044                    post_return,
2045                    string_encoding,
2046                    data_model:
2047                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
2048                    ..
2049                } = self
2050                    .component
2051                    .options
2052                    .get(*options)
2053                    .expect("failed to find options")
2054                else {
2055                    panic!("unexpected memory data model during error-context.debug-message");
2056                };
2057
2058                let debug_message_fn = self
2059                    .bindgen
2060                    .intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextDebugMessage));
2061
2062                let realloc_fn_idx = realloc
2063                    .expect("missing realloc fn idx for error-context.debug-message")
2064                    .as_u32();
2065                let memory_idx = memory
2066                    .expect("missing realloc fn idx for error-context.debug-message")
2067                    .as_u32();
2068
2069                // Generate a string encoding function to match this trampoline that does appropriate encoding
2070                match string_encoding {
2071                    wasmtime_environ::component::StringEncoding::Utf8 => {
2072                        let encode_fn = self
2073                            .bindgen
2074                            .intrinsic(Intrinsic::String(StringIntrinsic::Utf8Encode));
2075                        uwriteln!(
2076                            self.src.js,
2077                            "function trampoline{i}OutputStr(s, outputPtr) {{
2078                                 const memory = memory{memory_idx};
2079                                 const reallocFn = realloc{realloc_fn_idx};
2080                                 let {{ ptr, len }} = {encode_fn}(s, reallocFn, memory);
2081                                 new DataView(memory.buffer).setUint32(outputPtr, ptr, true)
2082                                 new DataView(memory.buffer).setUint32(outputPtr + 4, len, true)
2083                             }}"
2084                        );
2085                    }
2086                    wasmtime_environ::component::StringEncoding::Utf16 => {
2087                        let encode_fn = self
2088                            .bindgen
2089                            .intrinsic(Intrinsic::String(StringIntrinsic::Utf16Encode));
2090                        uwriteln!(
2091                            self.src.js,
2092                            "function trampoline{i}OutputStr(s, outputPtr) {{
2093                                 const memory = memory{memory_idx};
2094                                 const reallocFn = realloc{realloc_fn_idx};
2095                                 let ptr = {encode_fn}(s, reallocFn, memory);
2096                                 let len = s.length;
2097                                 new DataView(memory.buffer).setUint32(outputPtr, ptr, true)
2098                                 new DataView(memory.buffer).setUint32(outputPtr + 4, len, true)
2099                             }}"
2100                        );
2101                    }
2102                    enc => panic!(
2103                        "unsupported string encoding [{enc:?}] for error-context.debug-message"
2104                    ),
2105                };
2106
2107                let options_obj = format!(
2108                    "{{callback:{callback}, postReturn: {post_return}, async: {async_}}}",
2109                    callback = callback
2110                        .map(|v| v.as_u32().to_string())
2111                        .unwrap_or_else(|| "null".into()),
2112                    post_return = post_return
2113                        .map(|v| v.as_u32().to_string())
2114                        .unwrap_or_else(|| "null".into()),
2115                );
2116
2117                let component_idx = instance.as_u32();
2118                uwriteln!(
2119                    self.src.js,
2120                    "const trampoline{i} = {debug_message_fn}.bind(
2121                         null,
2122                         {{
2123                             componentIdx: {component_idx},
2124                             options: {options_obj},
2125                             writeStrFn: trampoline{i}OutputStr,
2126                         }}
2127                     );"
2128                );
2129            }
2130
2131            Trampoline::ErrorContextDrop { instance, ty } => {
2132                let drop_fn = self
2133                    .bindgen
2134                    .intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextDrop));
2135                let local_err_tbl_idx = ty.as_u32();
2136                let component_idx = instance.as_u32();
2137                uwriteln!(
2138                    self.src.js,
2139                    r#"
2140                      const trampoline{i} = {drop_fn}.bind(
2141                          null,
2142                          {{ componentIdx: {component_idx}, localTableIdx: {local_err_tbl_idx} }},
2143                      );
2144                    "#
2145                );
2146            }
2147
2148            Trampoline::ErrorContextTransfer => {
2149                let transfer_fn = self
2150                    .bindgen
2151                    .intrinsic(Intrinsic::ErrCtx(ErrCtxIntrinsic::ErrorContextTransfer));
2152                uwriteln!(self.src.js, "const trampoline{i} = {transfer_fn};");
2153            }
2154
2155            // This sets up a subtask (sets parent, etc) for guest -> guest calls
2156            Trampoline::PrepareCall { memory } => {
2157                let prepare_call_fn = self
2158                    .bindgen
2159                    .intrinsic(Intrinsic::Host(HostIntrinsic::PrepareCall));
2160                let (memory_idx_js, memory_fn_js) = memory
2161                    .map(|v| {
2162                        (
2163                            v.as_u32().to_string(),
2164                            format!("() => memory{}", v.as_u32()),
2165                        )
2166                    })
2167                    .unwrap_or_else(|| ("null".into(), "() => null".into()));
2168                uwriteln!(
2169                    self.src.js,
2170                    "const trampoline{i} = {prepare_call_fn}.bind(null, {memory_idx_js}, {memory_fn_js});",
2171                )
2172            }
2173
2174            Trampoline::SyncStartCall { callback } => {
2175                let sync_start_call_fn = self
2176                    .bindgen
2177                    .intrinsic(Intrinsic::Host(HostIntrinsic::SyncStartCall));
2178                uwriteln!(
2179                    self.src.js,
2180                    "const trampoline{i} = {sync_start_call_fn}.bind(null, {});",
2181                    callback
2182                        .map(|v| v.as_u32().to_string())
2183                        .unwrap_or_else(|| "null".into()),
2184                );
2185            }
2186
2187            // This actually starts a Task (whose parent is a subtask generated during PrepareCall)
2188            // for a from-component async import call
2189            Trampoline::AsyncStartCall {
2190                callback,
2191                post_return,
2192            } => {
2193                let async_start_call_fn = self
2194                    .bindgen
2195                    .intrinsic(Intrinsic::Host(HostIntrinsic::AsyncStartCall));
2196                let (callback_idx, callback_fn) = callback
2197                    .map(|v| (v.as_u32().to_string(), format!("callback_{}", v.as_u32())))
2198                    .unwrap_or_else(|| ("null".into(), "null".into()));
2199                let (post_return_idx, post_return_fn) = post_return
2200                    .map(|v| (v.as_u32().to_string(), format!("postReturn{}", v.as_u32())))
2201                    .unwrap_or_else(|| ("null".into(), "null".into()));
2202
2203                uwriteln!(
2204                    self.src.js,
2205                    "const trampoline{i} = {async_start_call_fn}.bind(
2206                         null,
2207                         {{
2208                             postReturnIdx: {post_return_idx},
2209                             getPostReturnFn: () => {post_return_fn},
2210                             callbackIdx: {callback_idx},
2211                             getCallbackFn: () => {callback_fn},
2212                         }},
2213                     );",
2214                );
2215            }
2216
2217            Trampoline::LowerImport {
2218                index: _,
2219                lower_ty,
2220                options,
2221            } => {
2222                let canon_opts = self
2223                    .component
2224                    .options
2225                    .get(*options)
2226                    .expect("failed to find options");
2227
2228                // TODO(fix): remove Global lowers, should enable using just exports[x] to export[y] call
2229                // TODO(fix): promising for the run (*as well as exports*)
2230                // TODO(fix): delete all asyncImports/exports
2231                // TODO(opt): opt-in sync import
2232
2233                let component_idx = canon_opts.instance.as_u32();
2234                let is_async = canon_opts.async_;
2235
2236                let cancellable = canon_opts.cancellable;
2237
2238                let func_ty = self.types.index(*lower_ty);
2239
2240                // Build list of lift functions for the params of the lowered import
2241                let param_types = &self.types.index(func_ty.params).types;
2242                let param_lift_fns_js =
2243                    gen_flat_lift_fn_list_js_expr(self, param_types.iter().as_slice(), &None);
2244
2245                // Build list of lower functions for the results of the lowered import
2246                let result_types = &self.types.index(func_ty.results).types;
2247                let result_lower_fns_js =
2248                    gen_flat_lower_fn_list_js_expr(self, result_types.iter().as_slice(), &None);
2249                let result_flat_count = result_types.iter().try_fold(0usize, |count, ty| {
2250                    self.types
2251                        .canonical_abi(ty)
2252                        .flat_count
2253                        .map(|flat_count| count + usize::from(flat_count))
2254                });
2255
2256                let get_callback_fn_js = canon_opts
2257                    .callback
2258                    .map(|idx| format!("() => callback_{}", idx.as_u32()))
2259                    .unwrap_or_else(|| "() => null".into());
2260                let get_post_return_fn_js = canon_opts
2261                    .post_return
2262                    .map(|idx| format!("() => postReturn{}", idx.as_u32()))
2263                    .unwrap_or_else(|| "() => null".into());
2264
2265                // Build the memory and realloc js expressions, retrieving the memory index and getter functions
2266                let (memory_exprs, realloc_expr_js) =
2267                    if let CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
2268                        memory,
2269                        realloc,
2270                    }) = canon_opts.data_model
2271                    {
2272                        (
2273                            memory.map(|idx| {
2274                                (
2275                                    idx.as_u32().to_string(),
2276                                    format!("() => memory{}", idx.as_u32()),
2277                                )
2278                            }),
2279                            realloc.map(|idx| format!("() => realloc{}", idx.as_u32())),
2280                        )
2281                    } else {
2282                        (None, None)
2283                    };
2284                let (memory_idx_js, memory_expr_js) =
2285                    memory_exprs.unwrap_or_else(|| ("null".into(), "() => null".into()));
2286                let realloc_expr_js = realloc_expr_js.unwrap_or_else(|| "undefined".into());
2287                let string_encoding_js = string_encoding_js_literal(&canon_opts.string_encoding);
2288
2289                // Build the lower import call that will wrap the actual trampoline
2290                let func_ty_async = func_ty.async_;
2291                let max_direct_results = if is_async || func_ty_async {
2292                    0
2293                } else {
2294                    MAX_FLAT_RESULTS
2295                };
2296                let has_result_pointer = result_flat_count
2297                    .map(|count| count > max_direct_results)
2298                    .unwrap_or(true);
2299                let call = format!(
2300                    r#"{lower_import_intrinsic}.bind(
2301                        null,
2302                        {{
2303                            trampolineIdx: {i},
2304                            componentIdx: {component_idx},
2305                            isAsync: {is_async},
2306                            isManualAsync: _trampoline{i}.manuallyAsync,
2307                            paramLiftFns: {param_lift_fns_js},
2308                            resultLowerFns: {result_lower_fns_js},
2309                            hasResultPointer: {has_result_pointer},
2310                            funcTypeIsAsync: {func_ty_async},
2311                            getCallbackFn: {get_callback_fn_js},
2312                            getPostReturnFn: {get_post_return_fn_js},
2313                            isCancellable: {cancellable},
2314                            memoryIdx: {memory_idx_js},
2315                            stringEncoding: {string_encoding_js},
2316                            getMemoryFn: {memory_expr_js},
2317                            getReallocFn: {realloc_expr_js},
2318                            importFn: _trampoline{i},
2319                        }},
2320                    )"#,
2321                    lower_import_intrinsic = if is_async || func_ty_async {
2322                        self.bindgen
2323                            .intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::LowerImport))
2324                    } else {
2325                        self.bindgen.intrinsic(Intrinsic::AsyncTask(
2326                            AsyncTaskIntrinsic::LowerImportBackwardsCompat,
2327                        ))
2328                    }
2329                );
2330
2331                // NOTE: For Trampoline::LowerImport, the trampoline index is actually already defined,
2332                // but we *redefine* it to call the lower import function first.
2333                if is_async || func_ty_async {
2334                    uwriteln!(
2335                        self.src.js,
2336                        "let trampoline{i} = new WebAssembly.Suspending({call});"
2337                    );
2338                } else {
2339                    // TODO(breaking): once manually specifying async imports is removed,
2340                    // we can avoid the second check below.
2341                    uwriteln!(
2342                        self.src.js,
2343                        "let trampoline{i} = _trampoline{i}.manuallyAsync ? new WebAssembly.Suspending({call}) : {call};"
2344                    );
2345                }
2346            }
2347
2348            Trampoline::Transcoder {
2349                op,
2350                from,
2351                from64,
2352                to,
2353                to64,
2354            } => {
2355                if *from64 || *to64 {
2356                    unimplemented!("memory 64 transcoder");
2357                }
2358                let from = from.as_u32();
2359                let to = to.as_u32();
2360                match op {
2361                    Transcode::Copy(FixedEncoding::Utf8) => {
2362                        uwriteln!(
2363                            self.src.js,
2364                            r#"
2365                              function trampoline{i} (from_ptr, len, to_ptr) {{
2366                                  new Uint8Array(memory{to}.buffer, to_ptr, len).set(new Uint8Array(memory{from}.buffer, from_ptr, len));
2367                              }}
2368                            "#
2369                        );
2370                    }
2371                    Transcode::Copy(FixedEncoding::Utf16) => unimplemented!("utf16 copier"),
2372                    Transcode::Copy(FixedEncoding::Latin1) => unimplemented!("latin1 copier"),
2373                    Transcode::Latin1ToUtf16 => unimplemented!("latin to utf16 transcoder"),
2374                    Transcode::Latin1ToUtf8 => unimplemented!("latin to utf8 transcoder"),
2375                    Transcode::Utf16ToCompactProbablyUtf16 => {
2376                        unimplemented!("utf16 to compact wtf16 transcoder")
2377                    }
2378                    Transcode::Utf16ToCompactUtf16 => {
2379                        unimplemented!("utf16 to compact utf16 transcoder")
2380                    }
2381                    Transcode::Utf16ToLatin1 => unimplemented!("utf16 to latin1 transcoder"),
2382                    Transcode::Utf16ToUtf8 => {
2383                        uwriteln!(
2384                            self.src.js,
2385                            r#"
2386                              function trampoline{i} (src, src_len, dst, dst_len) {{
2387                                  const encoder = new TextEncoder();
2388                                  const {{ read, written }} = encoder.encodeInto(String.fromCharCode.apply(null, new Uint16Array(memory{from}.buffer, src, src_len)), new Uint8Array(memory{to}.buffer, dst, dst_len));
2389                                  return [read, written];
2390                              }}
2391                            "#,
2392                        );
2393                    }
2394                    Transcode::Utf8ToCompactUtf16 => {
2395                        unimplemented!("utf8 to compact utf16 transcoder")
2396                    }
2397                    Transcode::Utf8ToLatin1 => unimplemented!("utf8 to latin1 transcoder"),
2398                    Transcode::Utf8ToUtf16 => {
2399                        uwriteln!(
2400                            self.src.js,
2401                            r#"
2402                              function trampoline{i} (from_ptr, len, to_ptr) {{
2403                                  const decoder = new TextDecoder();
2404                                  const content = decoder.decode(new Uint8Array(memory{from}.buffer, from_ptr, len));
2405                                  const strlen = content.length
2406                                  const view = new Uint16Array(memory{to}.buffer, to_ptr, strlen * 2)
2407                                  for (var i = 0; i < strlen; i++) {{
2408                                      view[i] = content.charCodeAt(i);
2409                                  }}
2410                                  return strlen;
2411                              }}
2412                            "#,
2413                        );
2414                    }
2415                };
2416            }
2417
2418            Trampoline::ResourceNew {
2419                ty: resource_ty_idx,
2420                ..
2421            } => {
2422                self.ensure_resource_table(*resource_ty_idx);
2423                let rid = resource_ty_idx.as_u32();
2424                let rsc_table_create_own = self.bindgen.intrinsic(Intrinsic::Resource(
2425                    ResourceIntrinsic::ResourceTableCreateOwn,
2426                ));
2427                uwriteln!(
2428                    self.src.js,
2429                    "const trampoline{i} = {rsc_table_create_own}.bind(null, handleTable{rid});"
2430                );
2431            }
2432
2433            Trampoline::ResourceRep {
2434                ty: resource_ty_idx,
2435                ..
2436            } => {
2437                self.ensure_resource_table(*resource_ty_idx);
2438                let rid = resource_ty_idx.as_u32();
2439                let rsc_flag = self
2440                    .bindgen
2441                    .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
2442                uwriteln!(
2443                    self.src.js,
2444                    "function trampoline{i} (handle) {{
2445                        return handleTable{rid}[(handle << 1) + 1] & ~{rsc_flag};
2446                    }}"
2447                );
2448            }
2449
2450            Trampoline::ResourceDrop {
2451                ty: resource_table_ty_idx,
2452                ..
2453            } => {
2454                self.ensure_resource_table(*resource_table_ty_idx);
2455                let tid = resource_table_ty_idx.as_u32();
2456                let resource_table_ty = &self.types[*resource_table_ty_idx];
2457                let resource_ty = resource_table_ty.unwrap_concrete_ty();
2458                let rid = resource_ty.as_u32();
2459
2460                // Build the code fragment that encapsulates calling the destructor
2461                let dtor = if let Some(resource_idx) =
2462                    self.component.defined_resource_index(resource_ty)
2463                {
2464                    let resource_def = self
2465                        .component
2466                        .initializers
2467                        .iter()
2468                        .find_map(|i| match i {
2469                            GlobalInitializer::Resource(r) if r.index == resource_idx => Some(r),
2470                            _ => None,
2471                        })
2472                        .unwrap();
2473
2474                    // If a destructor index is defined for the resource, call it
2475                    if let Some(dtor) = &resource_def.dtor {
2476                        format!(
2477                            "
2478                            {}(handleEntry.rep);",
2479                            self.core_def(dtor)
2480                        )
2481                    } else {
2482                        "".into()
2483                    }
2484                } else {
2485                    // Imported resource is one without a defined resource index.
2486                    // If it is a captured instance (class instance was created externally so had to
2487                    // be assigned a rep), and there is a Symbol.dispose handler, call it explicitly
2488                    // for imported resources when the resource is dropped.
2489                    // Otherwise if it is an instance without a captured class definition, then
2490                    // call the low-level bindgen destructor.
2491                    let symbol_dispose = self.bindgen.intrinsic(Intrinsic::SymbolDispose);
2492                    let symbol_cabi_dispose = self.bindgen.intrinsic(Intrinsic::SymbolCabiDispose);
2493
2494                    // previous imports walk should define all imported resources which are accessible
2495                    if let Some(imported_resource_local_name) =
2496                        self.bindgen.local_names.try_get(resource_ty)
2497                    {
2498                        format!(
2499                                            "
2500                            const rsc = captureTable{rid}.get(handleEntry.rep);
2501                            if (rsc) {{
2502                                if (rsc[{symbol_dispose}]) rsc[{symbol_dispose}]();
2503                                captureTable{rid}.delete(handleEntry.rep);
2504                            }} else if ({imported_resource_local_name}[{symbol_cabi_dispose}]) {{
2505                                {imported_resource_local_name}[{symbol_cabi_dispose}](handleEntry.rep);
2506                            }}"
2507                                        )
2508                    } else {
2509                        // If not, then capture / disposal paths are never called
2510                        format!(
2511                            "throw new TypeError('unreachable trampoline for resource [{:?}]')",
2512                            resource_ty
2513                        )
2514                    }
2515                };
2516
2517                let rsc_table_remove = self
2518                    .bindgen
2519                    .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
2520                uwrite!(
2521                    self.src.js,
2522                    "function trampoline{i}(handle) {{
2523                        const handleEntry = {rsc_table_remove}(handleTable{tid}, handle);
2524                        if (handleEntry.own) {{
2525                            {dtor}
2526                        }}
2527                    }}
2528                    ",
2529                );
2530            }
2531
2532            Trampoline::ResourceTransferOwn => {
2533                let resource_transfer = self
2534                    .bindgen
2535                    .intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTransferOwn));
2536                uwriteln!(self.src.js, "const trampoline{i} = {resource_transfer};");
2537            }
2538
2539            Trampoline::ResourceTransferBorrow => {
2540                let resource_transfer =
2541                    self.bindgen
2542                        .intrinsic(if self.bindgen.opts.valid_lifting_optimization {
2543                            Intrinsic::Resource(
2544                                ResourceIntrinsic::ResourceTransferBorrowValidLifting,
2545                            )
2546                        } else {
2547                            Intrinsic::Resource(ResourceIntrinsic::ResourceTransferBorrow)
2548                        });
2549                uwriteln!(self.src.js, "const trampoline{i} = {resource_transfer};");
2550            }
2551
2552            Trampoline::TaskReturn {
2553                results, options, ..
2554            } => {
2555                let canon_opts = self
2556                    .component
2557                    .options
2558                    .get(*options)
2559                    .expect("failed to find options");
2560                let CanonicalOptions {
2561                    instance,
2562                    async_,
2563                    data_model:
2564                        CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions { memory, realloc }),
2565                    callback,
2566                    post_return,
2567                    string_encoding,
2568                    ..
2569                } = canon_opts
2570                else {
2571                    unreachable!("unexpected memory data model during task.return");
2572                };
2573
2574                // Validate canonopts
2575                if realloc.is_some() && memory.is_none() {
2576                    panic!("memory must be present if realloc is");
2577                }
2578                if *async_ && post_return.is_some() {
2579                    panic!("async and post return must not be specified together");
2580                }
2581                if *async_ && callback.is_none() {
2582                    panic!("callback must be specified for async");
2583                }
2584                if let Some(cb_idx) = callback {
2585                    let cb_fn = &self.types[TypeFuncIndex::from_u32(cb_idx.as_u32())];
2586                    match self.types[cb_fn.params].types[..] {
2587                        [InterfaceType::S32, InterfaceType::S32, InterfaceType::S32] => {}
2588                        _ => panic!("unexpected params for async callback fn"),
2589                    }
2590                    match self.types[cb_fn.results].types[..] {
2591                        [InterfaceType::S32] => {}
2592                        _ => panic!("unexpected results for async callback fn"),
2593                    }
2594                }
2595
2596                let result_types = &self.types[*results].types;
2597
2598                // Calculate the number of parameters required to represent the results,
2599                // and whether they'll be stored in memory
2600                let result_flat_param_total: usize = result_types
2601                    .iter()
2602                    .map(|t| {
2603                        self.types
2604                            .canonical_abi(t)
2605                            .flat_count
2606                            .map(usize::from)
2607                            .unwrap_or(0)
2608                    })
2609                    .sum();
2610                let use_direct_params = result_flat_param_total < MAX_FLAT_PARAMS;
2611
2612                // Build up a list of all the lifting functions that will be needed for the types
2613                // that are actually being passed through task.return
2614                let mut lift_fns: Vec<String> = Vec::with_capacity(result_types.len());
2615                for result_ty in result_types {
2616                    lift_fns.push(gen_flat_lift_fn_js_expr(self, result_ty, &None));
2617                }
2618                let lift_fns_js = format!("[{}]", lift_fns.join(","));
2619
2620                // Build up a list of all the lowering functions that will be needed for the types
2621                // that are actually being passed through task.return
2622                //
2623                // This is usually only necessary if this task is part of a guest->guest async call
2624                // (i.e. via prepare & async start call)
2625                let mut lower_fns: Vec<String> = Vec::with_capacity(result_types.len());
2626                for result_ty in result_types {
2627                    lower_fns.push(gen_flat_lower_fn_js_expr(self, result_ty, &None));
2628                }
2629                let lower_fns_js = format!("[{}]", lower_fns.join(","));
2630
2631                let get_memory_fn_js = memory
2632                    .map(|idx| format!("() => memory{}", idx.as_u32()))
2633                    .unwrap_or_else(|| "() => null".into());
2634                let memory_idx_js = memory
2635                    .map(|idx| idx.as_u32().to_string())
2636                    .unwrap_or_else(|| "null".into());
2637                let component_idx = instance.as_u32();
2638                let task_return_fn = self
2639                    .bindgen
2640                    .intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::TaskReturn));
2641                let callback_fn_idx = callback
2642                    .map(|v| v.as_u32().to_string())
2643                    .unwrap_or_else(|| "null".into());
2644                let string_encoding_js = string_encoding_js_literal(string_encoding);
2645
2646                uwriteln!(
2647                    self.src.js,
2648                    "const trampoline{i} = {task_return_fn}.bind(
2649                         null,
2650                         {{
2651                             componentIdx: {component_idx},
2652                             useDirectParams: {use_direct_params},
2653                             getMemoryFn: {get_memory_fn_js},
2654                             memoryIdx: {memory_idx_js},
2655                             callbackFnIdx: {callback_fn_idx},
2656                             liftFns: {lift_fns_js},
2657                             lowerFns: {lower_fns_js},
2658                             stringEncoding: {string_encoding_js},
2659                         }},
2660                     );",
2661                );
2662            }
2663
2664            Trampoline::BackpressureInc { instance } => {
2665                let backpressure_inc_fn = self
2666                    .bindgen
2667                    .intrinsic(Intrinsic::Component(ComponentIntrinsic::BackpressureInc));
2668                uwriteln!(
2669                    self.src.js,
2670                    "const trampoline{i} = {backpressure_inc_fn}.bind(null, {instance});\n",
2671                    instance = instance.as_u32(),
2672                );
2673            }
2674
2675            Trampoline::BackpressureDec { instance } => {
2676                let backpressure_dec_fn = self
2677                    .bindgen
2678                    .intrinsic(Intrinsic::Component(ComponentIntrinsic::BackpressureDec));
2679                uwriteln!(
2680                    self.src.js,
2681                    "const trampoline{i} = {backpressure_dec_fn}.bind(null, {instance});\n",
2682                    instance = instance.as_u32(),
2683                );
2684            }
2685
2686            Trampoline::ThreadYield {
2687                cancellable,
2688                instance,
2689            } => {
2690                let yield_fn = self
2691                    .bindgen
2692                    .intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::Yield));
2693                let component_instance_idx = instance.as_u32();
2694                uwriteln!(
2695                    self.src.js,
2696                    r#"
2697                      const trampoline{i} = new WebAssembly.Suspending({yield_fn}.bind(null, {{
2698                          isCancellable: {cancellable},
2699                          componentIdx: {component_instance_idx},
2700                      }}));
2701                    "#,
2702                );
2703            }
2704            Trampoline::ThreadIndex => todo!("Trampoline::ThreadIndex"),
2705            Trampoline::ThreadNewIndirect { .. } => todo!("Trampoline::ThreadNewIndirect"),
2706            Trampoline::ThreadSuspend { .. } => todo!("Trampoline::ThreadSuspend"),
2707            Trampoline::ThreadSuspendTo { .. } => todo!("Trampoline::ThreadSuspendTo"),
2708            Trampoline::ThreadUnsuspend { .. } => todo!("Trampoline::ThreadUnsuspend"),
2709            Trampoline::ThreadYieldToSuspended { .. } => {
2710                todo!("Trampoline::ThreadYieldToSuspended")
2711            }
2712            Trampoline::ThreadSuspendToSuspended { .. } => {
2713                todo!("Trampoline::ThreadYieldToSuspended")
2714            }
2715
2716            Trampoline::Trap => {
2717                uwriteln!(
2718                    self.src.js,
2719                    "function trampoline{i}(rep) {{ throw new TypeError('Trap'); }}"
2720                );
2721            }
2722
2723            Trampoline::EnterSyncCall => {
2724                let enter_symmetric_sync_guest_call_fn = self.bindgen.intrinsic(
2725                    Intrinsic::AsyncTask(AsyncTaskIntrinsic::EnterSymmetricSyncGuestCall),
2726                );
2727                uwriteln!(
2728                    self.src.js,
2729                    r#"
2730                      const trampoline{i} = {enter_symmetric_sync_guest_call_fn};
2731                    "#,
2732                );
2733            }
2734
2735            Trampoline::ExitSyncCall => {
2736                let exit_symmetric_sync_guest_call_fn = self.bindgen.intrinsic(
2737                    Intrinsic::AsyncTask(AsyncTaskIntrinsic::ExitSymmetricSyncGuestCall),
2738                );
2739                uwriteln!(
2740                    self.src.js,
2741                    "const trampoline{i} = {exit_symmetric_sync_guest_call_fn};\n",
2742                );
2743            }
2744        }
2745    }
2746
2747    fn instantiation_global_initializer(&mut self, init: &GlobalInitializer) {
2748        match init {
2749            // Extracting callbacks is a part of the async support for hosts -- it ensures that
2750            // a given core export can be turned into a callback function that will be used
2751            // later.
2752            //
2753            // Generally what we have to do here is to create a callback that can be called upon re-entrance
2754            // into the component after a related suspension.
2755            GlobalInitializer::ExtractCallback(ExtractCallback { index, def }) => {
2756                let callback_idx = index.as_u32();
2757                let core_def = self.core_def(def);
2758
2759                uwriteln!(self.src.js, "let callback_{callback_idx};",);
2760
2761                // If the function returns an async value like a stream or future,
2762                // the callback that is executed in the the event loop (`AsyncTaskIntrinsic::DriverLoop`)
2763                // may attempt to wait due to calling necessarily async host imports like {stream, future}.{write, read}.
2764                //
2765                // Here, we mark the task with an indicator that denotes whether the callback should be run this way.
2766                //
2767                // TODO: can we be more selective here rather than wrapping every callback in WebAssembly.promising?
2768                // every callback *could* do stream.write, but many may not.
2769                uwriteln!(
2770                    self.src.js_init,
2771                    r#"
2772                      callback_{callback_idx} = WebAssembly.promising({core_def});
2773                      callback_{callback_idx}.fnName = "{core_def}";
2774                    "#
2775                );
2776            }
2777
2778            GlobalInitializer::InstantiateModule(m, instance) => {
2779                // NOTE: we keep track of the current component instance index for the module being instantiatied
2780                // because some  because context.set/context.get are no longer trampolines,
2781                // and we do not have access to the correct component index that each operation belongs to at build time
2782                self.init_current_module = *instance;
2783
2784                match m {
2785                    InstantiateModule::Static(idx, args) => {
2786                        self.instantiate_static_module(*idx, args, *instance);
2787                    }
2788                    // This is only needed when instantiating an imported core wasm
2789                    // module which while easy to implement here is not possible to
2790                    // test at this time so it's left unimplemented.
2791                    InstantiateModule::Import(..) => unimplemented!(),
2792                }
2793            }
2794
2795            GlobalInitializer::LowerImport { index, import } => {
2796                self.lower_import(*index, *import);
2797            }
2798
2799            GlobalInitializer::ExtractMemory(m) => {
2800                let def = self.core_export_var_name(&m.export);
2801                let idx = m.index.as_u32();
2802                uwriteln!(self.src.js, "let memory{idx};");
2803                uwriteln!(self.src.js_init, "memory{idx} = {def};");
2804            }
2805
2806            GlobalInitializer::ExtractRealloc(r) => {
2807                let def = self.core_def(&r.def);
2808                let idx = r.index.as_u32();
2809                uwriteln!(self.src.js, "let realloc{idx};");
2810                uwriteln!(self.src.js, "let realloc{idx}Async;");
2811                uwriteln!(self.src.js_init, "realloc{idx} = {def};",);
2812                // NOTE: sometimes we may be fed a realloc that isn't a webassembly function at all
2813                // but has instead been converted to JS (see 'flavorful' test in test/runtime.js')
2814                uwriteln!(
2815                    self.src.js_init,
2816                    r#"
2817                      try {{
2818                          realloc{idx}Async = WebAssembly.promising({def});
2819                      }} catch(err) {{
2820                          realloc{idx}Async = {def};
2821                      }}
2822                    "#
2823                );
2824            }
2825
2826            GlobalInitializer::ExtractPostReturn(p) => {
2827                let def = self.core_def(&p.def);
2828                let idx = p.index.as_u32();
2829                uwriteln!(self.src.js, "let postReturn{idx};");
2830                uwriteln!(self.src.js, "let postReturn{idx}Async;");
2831                uwriteln!(self.src.js_init, "postReturn{idx} = {def};");
2832                // NOTE: sometimes we may be fed a post return fn that isn't a webassembly function
2833                // at all but has instead been converted to JS (see 'flavorful' test in test/runtime.js)
2834                uwriteln!(
2835                    self.src.js_init,
2836                    r#"
2837                      try {{
2838                          postReturn{idx}Async = WebAssembly.promising({def});
2839                      }} catch(err) {{
2840                          postReturn{idx}Async = {def};
2841                      }}
2842                    "#
2843                );
2844            }
2845
2846            GlobalInitializer::Resource(_) => {}
2847
2848            GlobalInitializer::ExtractTable(_) => {}
2849        }
2850    }
2851
2852    fn instantiate_static_module(
2853        &mut self,
2854        module_idx: StaticModuleIndex,
2855        args: &[CoreDef],
2856        instance: Option<RuntimeComponentInstanceIndex>,
2857    ) {
2858        // Build a JS "import object" which represents `args`. The `args` is a
2859        // flat representation which needs to be zip'd with the list of names to
2860        // correspond to the JS wasm embedding API. This is one of the major
2861        // differences between Wasmtime's and JS's embedding API.
2862        let mut import_obj = BTreeMap::new();
2863        for (module, name, arg) in self.modules[module_idx].imports(args) {
2864            let def = self.augmented_import_def(&arg);
2865            let dst = import_obj.entry(module).or_insert(BTreeMap::new());
2866            let prev = dst.insert(name, def);
2867            assert!(
2868                prev.is_none(),
2869                "unsupported duplicate import of `{module}::{name}`"
2870            );
2871            assert!(prev.is_none());
2872        }
2873
2874        if self.bindgen.opts.asmjs {
2875            let component_instance_idx = instance
2876                .expect("missing runtime component index during static module instantiation")
2877                .as_u32();
2878
2879            self.add_intrinsic(Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask));
2880            self.add_intrinsic(Intrinsic::GetGlobalCurrentTaskMetaFn);
2881            let current_task_get_fn =
2882                Intrinsic::AsyncTask(AsyncTaskIntrinsic::GetCurrentTask).name();
2883            let get_global_current_task_meta_fn = Intrinsic::GetGlobalCurrentTaskMetaFn.name();
2884
2885            let dst = import_obj.entry("env").or_insert(BTreeMap::new());
2886            let prev = dst.insert(
2887                "setTempRet0",
2888                format!(
2889                    "(x) => {{
2890                const {{ taskID }} = {get_global_current_task_meta_fn}({component_instance_idx});
2891
2892                const taskMeta = {current_task_get_fn}({component_instance_idx}, taskID);
2893                if (!taskMeta) {{ throw new Error('invalid/missing async task meta'); }}
2894
2895                const task = taskMeta.task;
2896                if (!task) {{ throw new Error('invalid/missing async task'); }}
2897
2898                task.tmpRetI64HighBits = x|0;
2899            }}"
2900                ),
2901            );
2902            assert!(
2903                prev.is_none(),
2904                "unsupported duplicate import of `env::setTempRet0`"
2905            );
2906            assert!(prev.is_none());
2907        }
2908
2909        // Build list of imports
2910        let mut imports = String::new();
2911        if !import_obj.is_empty() {
2912            imports.push_str(", {\n");
2913            for (module, names) in import_obj {
2914                imports.push_str(&maybe_quote_id(module));
2915                imports.push_str(": {\n");
2916                for (name, val) in names {
2917                    imports.push_str(&maybe_quote_id(name));
2918                    uwriteln!(imports, ": {val},");
2919                }
2920                imports.push_str("},\n");
2921            }
2922            imports.push('}');
2923        }
2924
2925        let i = self.instances.push(module_idx);
2926        let iu32 = i.as_u32();
2927        let instantiate = self.bindgen.intrinsic(Intrinsic::InstantiateCore);
2928        uwriteln!(self.src.js, "let exports{iu32};");
2929
2930        match self.bindgen.opts.instantiation_mode {
2931            Some(InstantiationMode::Async) | None => {
2932                uwriteln!(
2933                    self.src.js_init,
2934                    "({{ exports: exports{iu32} }} = yield {instantiate}(yield module{}{imports}));",
2935                    module_idx.as_u32(),
2936                )
2937            }
2938
2939            Some(InstantiationMode::Sync) => {
2940                uwriteln!(
2941                    self.src.js_init,
2942                    "({{ exports: exports{iu32} }} = {instantiate}(module{}{imports}));",
2943                    module_idx.as_u32(),
2944                );
2945            }
2946        }
2947    }
2948
2949    /// Map all types in parameters and results to local resource types
2950    ///
2951    /// # Arguments
2952    ///
2953    /// * `func` - The function in question
2954    /// * `ty_func_idx` - Type index of the function
2955    /// * `resource_map` - resource map of locally resolved types
2956    fn create_resource_fn_map(
2957        &mut self,
2958        func: &Function,
2959        ty_func_idx: TypeFuncIndex,
2960        resource_map: &mut ResourceMap,
2961    ) {
2962        // Connect resources used in parameters
2963        let params_ty = &self.types[self.types[ty_func_idx].params];
2964        for (p, iface_ty) in func.params.iter().zip(params_ty.types.iter()) {
2965            if let Type::Id(id) = p.ty {
2966                self.connect_resource_types(id, iface_ty, resource_map);
2967            }
2968        }
2969        // Connect resources used in results
2970        let results_ty = &self.types[self.types[ty_func_idx].results];
2971        if let (Some(Type::Id(id)), Some(iface_ty)) = (func.result, results_ty.types.first()) {
2972            self.connect_resource_types(id, iface_ty, resource_map);
2973        }
2974    }
2975
2976    fn resource_name(
2977        resolve: &Resolve,
2978        local_names: &'a mut LocalNames,
2979        resource: TypeId,
2980        resource_map: &BTreeMap<TypeId, ResourceIndex>,
2981    ) -> &'a str {
2982        let resource = crate::dealias(resolve, resource);
2983        local_names
2984            .get_or_create(
2985                resource_map[&resource],
2986                &resolve.types[resource]
2987                    .name
2988                    .as_ref()
2989                    .unwrap()
2990                    .to_upper_camel_case(),
2991            )
2992            .0
2993    }
2994
2995    /// Returns the local JS class name for an imported resource attached to
2996    /// the lowered import `import_index` (as the receiver class of a
2997    /// method/static or the target of a constructor).
2998    ///
2999    /// The same interface -- and thus the same `wit-parser` resource type --
3000    /// may be imported more than once under different labels via the
3001    /// component model `implements` feature, each import with its own
3002    /// resource table. The class must therefore be resolved through the
3003    /// specific import instance rather than through the wit type, which is
3004    /// shared by all labels.
3005    fn imported_resource_name(&mut self, import_index: ImportIndex, resource: TypeId) -> String {
3006        let resolve = self.resolve;
3007        let types = self.types;
3008        let component = self.component;
3009        let resource = crate::dealias(resolve, resource);
3010        let resource_wit_name = resolve.types[resource].name.as_ref().unwrap();
3011        if let (
3012            _,
3013            ComponentExtern {
3014                ty: TypeDef::ComponentInstance(inst),
3015                ..
3016            },
3017        ) = &component.import_types[import_index]
3018            && let Some(ComponentExtern {
3019                ty: TypeDef::Resource(rt_idx),
3020                ..
3021            }) = types[*inst].exports.get(resource_wit_name)
3022        {
3023            let rid = types[*rt_idx].unwrap_concrete_ty();
3024            return self
3025                .bindgen
3026                .local_names
3027                .get_or_create(rid, &resource_wit_name.to_upper_camel_case())
3028                .0
3029                .to_string();
3030        }
3031        // World-level resource imports (and any other shape) are uniquely
3032        // identified by their wit type.
3033        Instantiator::resource_name(
3034            resolve,
3035            &mut self.bindgen.local_names,
3036            resource,
3037            &self.imports_resource_types,
3038        )
3039        .to_string()
3040    }
3041
3042    /// Finds the component import that provides the given resource table.
3043    ///
3044    /// Returns the import name along with whether the resource is provided
3045    /// by an imported instance (`true`) or directly by a world-level
3046    /// resource import (`false`).
3047    ///
3048    /// The same interface may be imported under multiple labels (component
3049    /// model `implements` feature), each label with its own resource table,
3050    /// so the import is identified through the wasmtime resource index
3051    /// rather than through the wit type's owning interface, which is shared
3052    /// by all labels.
3053    fn find_import_providing_resource(
3054        &self,
3055        resource_idx: ResourceIndex,
3056    ) -> Option<(&'a str, bool)> {
3057        let component = self.component;
3058        let types = self.types;
3059        for (_, (imp_name, extern_)) in component.import_types.iter() {
3060            match &extern_.ty {
3061                TypeDef::ComponentInstance(inst) => {
3062                    for (_, export) in types[*inst].exports.iter() {
3063                        if let TypeDef::Resource(rt) = &export.ty
3064                            && types[*rt].unwrap_concrete_ty() == resource_idx
3065                        {
3066                            return Some((imp_name.as_str(), true));
3067                        }
3068                    }
3069                }
3070                TypeDef::Resource(rt) if types[*rt].unwrap_concrete_ty() == resource_idx => {
3071                    return Some((imp_name.as_str(), false));
3072                }
3073                _ => {}
3074            }
3075        }
3076        None
3077    }
3078
3079    fn lower_import(&mut self, index: LoweredIndex, import: RuntimeImportIndex) {
3080        let (options, trampoline, func_ty) = self.lowering_options[index];
3081
3082        // Get the world key for the CM import
3083        let (import_index, path) = &self.component.imports[import];
3084        let (import_name, _) = &self.component.import_types[*import_index];
3085        let world_key = &self.imports[import_name];
3086
3087        // Determine the name of the function
3088        let (func, func_name, iface_name) =
3089            match &self.resolve.worlds[self.world].imports[world_key] {
3090                WorldItem::Function(func) => {
3091                    assert_eq!(path.len(), 0);
3092                    (func, import_name, None)
3093                }
3094                WorldItem::Interface { id, .. } => {
3095                    assert_eq!(path.len(), 1);
3096                    let iface = &self.resolve.interfaces[*id];
3097                    let func = &iface.functions[&path[0]];
3098                    (
3099                        func,
3100                        &path[0],
3101                        Some(iface.name.as_deref().unwrap_or_else(|| import_name)),
3102                    )
3103                }
3104                WorldItem::Type { .. } => unreachable!("unexpected imported world item type"),
3105            };
3106
3107        let is_async = is_async_fn(func, options);
3108
3109        if options.async_ {
3110            assert!(
3111                options.post_return.is_none(),
3112                "async function {func_name} (import {import_name}) can't have post return",
3113            );
3114        }
3115
3116        // Host lifted async import (i.e. JSPI)
3117        let requires_async_porcelain = requires_async_porcelain(
3118            FunctionIdentifier::Fn(func),
3119            import_name,
3120            &self.async_imports,
3121        );
3122
3123        // A labeled import of a named interface (the component model
3124        // `implements` feature) falls back to a mapping for the implemented
3125        // interface id when the label itself has no mapping, so that e.g.
3126        // WASI shim mappings apply to labeled imports as well.
3127        let implements = self.resolve.implements_value(
3128            world_key,
3129            &self.resolve.worlds[self.world].imports[world_key],
3130        );
3131
3132        // Nested interfaces only currently possible through mapping
3133        let (import_specifier, maybe_iface_member) = map_import_with_implements(
3134            &self.bindgen.opts.map,
3135            if iface_name.is_some() {
3136                import_name
3137            } else {
3138                match func.kind {
3139                    FunctionKind::Method(_) => {
3140                        let stripped = import_name.strip_prefix("[method]").unwrap();
3141                        &stripped[0..stripped.find(".").unwrap()]
3142                    }
3143                    FunctionKind::AsyncMethod(_) => {
3144                        let stripped = import_name.strip_prefix("[async method]").unwrap();
3145                        &stripped[0..stripped.find(".").unwrap()]
3146                    }
3147                    FunctionKind::Static(_) => {
3148                        let stripped = import_name.strip_prefix("[static]").unwrap();
3149                        &stripped[0..stripped.find(".").unwrap()]
3150                    }
3151                    FunctionKind::AsyncStatic(_) => {
3152                        let stripped = import_name.strip_prefix("[async static]").unwrap();
3153                        &stripped[0..stripped.find(".").unwrap()]
3154                    }
3155                    FunctionKind::Constructor(_) => {
3156                        import_name.strip_prefix("[constructor]").unwrap()
3157                    }
3158                    FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => import_name,
3159                }
3160            },
3161            implements.as_deref(),
3162        );
3163
3164        // Create mappings for resources
3165        let mut import_resource_map = ResourceMap::new();
3166
3167        self.create_resource_fn_map(func, func_ty, &mut import_resource_map);
3168
3169        let (callee_name, call_type) = match func.kind {
3170            FunctionKind::Freestanding => (
3171                self.bindgen
3172                    .local_names
3173                    .get_or_create(
3174                        format!(
3175                            "import:{import}-{maybe_iface_member}-{func_name}",
3176                            import = import_specifier,
3177                            maybe_iface_member = maybe_iface_member.as_deref().unwrap_or(""),
3178                            func_name = func.name
3179                        ),
3180                        &func.name,
3181                    )
3182                    .0
3183                    .to_string(),
3184                CallType::Standard,
3185            ),
3186
3187            FunctionKind::AsyncFreestanding => (
3188                self.bindgen
3189                    .local_names
3190                    .get_or_create(
3191                        format!(
3192                            "import:async-{import}-{maybe_iface_member}-{func_name}",
3193                            import = import_specifier,
3194                            maybe_iface_member = maybe_iface_member.as_deref().unwrap_or(""),
3195                            func_name = func.name
3196                        ),
3197                        &func.name,
3198                    )
3199                    .0
3200                    .to_string(),
3201                CallType::AsyncStandard,
3202            ),
3203
3204            FunctionKind::Method(_) => (
3205                func.item_name().to_lower_camel_case(),
3206                CallType::CalleeResourceDispatch,
3207            ),
3208
3209            FunctionKind::AsyncMethod(_) => (
3210                func.item_name().to_lower_camel_case(),
3211                CallType::AsyncCalleeResourceDispatch,
3212            ),
3213
3214            FunctionKind::Static(resource_id) => (
3215                format!(
3216                    "{}.{}",
3217                    self.imported_resource_name(*import_index, resource_id),
3218                    func.item_name().to_lower_camel_case()
3219                ),
3220                CallType::Standard,
3221            ),
3222
3223            FunctionKind::AsyncStatic(resource_id) => (
3224                format!(
3225                    "{}.{}",
3226                    self.imported_resource_name(*import_index, resource_id),
3227                    func.item_name().to_lower_camel_case()
3228                ),
3229                CallType::AsyncStandard,
3230            ),
3231
3232            FunctionKind::Constructor(resource_id) => (
3233                format!(
3234                    "new {}",
3235                    self.imported_resource_name(*import_index, resource_id)
3236                ),
3237                CallType::Standard,
3238            ),
3239        };
3240
3241        let abi = if is_async {
3242            AbiVariant::GuestImportAsync
3243        } else {
3244            AbiVariant::GuestImport
3245        };
3246
3247        let nparams = self.resolve.wasm_signature(abi, func).params.len();
3248
3249        // Generate the JS trampoline function for a bound import
3250        let trampoline_idx = trampoline.as_u32();
3251        match self.bindgen.opts.import_bindings {
3252            None | Some(BindingsMode::Js) | Some(BindingsMode::Hybrid) => {
3253                // TODO(breaking): remove as we do not not need to manually specify async imports anymore in P3 w/ native coloring
3254                if is_async | requires_async_porcelain {
3255                    // NOTE: for async imports that will go through Trampoline::LowerImport,
3256                    // we prefix the raw import with '_' as it will later be used in the
3257                    // definition of trampoline{i} which will actually be fed into
3258                    // unbundled modules
3259                    uwrite!(
3260                        self.src.js,
3261                        "\nconst _trampoline{trampoline_idx} = async function"
3262                    );
3263                } else {
3264                    uwrite!(
3265                        self.src.js,
3266                        "\nconst _trampoline{trampoline_idx} = function"
3267                    );
3268                }
3269
3270                let iface_name = if import_name.is_empty() {
3271                    None
3272                } else {
3273                    Some(import_name.to_string())
3274                };
3275
3276                // Write out the function (brace + body + brace)
3277                self.bindgen(JsFunctionBindgenArgs {
3278                    nparams,
3279                    call_type,
3280                    iface_name: iface_name.as_deref(),
3281                    callee: &callee_name,
3282                    opts: options,
3283                    func,
3284                    resource_map: &import_resource_map,
3285                    abi,
3286                    requires_async_porcelain,
3287                    is_async,
3288                    for_import: true,
3289                });
3290                uwriteln!(self.src.js, "");
3291
3292                uwriteln!(
3293                    self.src.js,
3294                    "_trampoline{trampoline_idx}.fnName = '{}#{callee_name}';",
3295                    iface_name.unwrap_or_default(),
3296                );
3297
3298                // TODO(breaking): remove once support for manually specified async imports is removed
3299                if requires_async_porcelain {
3300                    uwriteln!(
3301                        self.src.js,
3302                        "_trampoline{trampoline_idx}.manuallyAsync = true;"
3303                    );
3304                }
3305            }
3306
3307            Some(BindingsMode::Optimized) | Some(BindingsMode::DirectOptimized) => {
3308                uwriteln!(self.src.js, "let trampoline{trampoline_idx};");
3309            }
3310        };
3311
3312        // Build import bindings & trampolines for the import
3313        //
3314        // This is only necessary if an import binding mode is specified and not JS (the default),
3315        // (e.g. Optimized, Direct, Hybrid).
3316        if !matches!(
3317            self.bindgen.opts.import_bindings,
3318            None | Some(BindingsMode::Js)
3319        ) {
3320            let (memory, realloc) =
3321                if let CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
3322                    memory,
3323                    realloc,
3324                }) = options.data_model
3325                {
3326                    (
3327                        memory.map(|idx| format!(" memory: memory{},", idx.as_u32())),
3328                        realloc.map(|idx| format!(" realloc: realloc{},", idx.as_u32())),
3329                    )
3330                } else {
3331                    (None, None)
3332                };
3333            let memory = memory.unwrap_or_default();
3334            let realloc = realloc.unwrap_or_default();
3335
3336            let post_return = options
3337                .post_return
3338                .map(|idx| format!(" postReturn: postReturn{},", idx.as_u32()))
3339                .unwrap_or("".into());
3340            let string_encoding = match options.string_encoding {
3341                wasmtime_environ::component::StringEncoding::Utf8 => "",
3342                wasmtime_environ::component::StringEncoding::Utf16 => " stringEncoding: 'utf16',",
3343                wasmtime_environ::component::StringEncoding::CompactUtf16 => {
3344                    " stringEncoding: 'compact-utf16',"
3345                }
3346            };
3347
3348            let callee_name = match func.kind {
3349                FunctionKind::Constructor(_) => callee_name[4..].to_string(),
3350
3351                FunctionKind::Static(_)
3352                | FunctionKind::AsyncStatic(_)
3353                | FunctionKind::Freestanding
3354                | FunctionKind::AsyncFreestanding => callee_name.to_string(),
3355
3356                FunctionKind::Method(resource_id) | FunctionKind::AsyncMethod(resource_id) => {
3357                    format!(
3358                        "{}.prototype.{callee_name}",
3359                        self.imported_resource_name(*import_index, resource_id)
3360                    )
3361                }
3362            };
3363
3364            // Save information about imported resources for later
3365            self.resource_imports.extend(import_resource_map.clone());
3366
3367            let resource_tables = {
3368                let mut resource_table_ids: Vec<TypeResourceTableIndex> = Vec::new();
3369
3370                for (_, data) in import_resource_map {
3371                    let ResourceTable {
3372                        data: ResourceData::Host { tid, .. },
3373                        ..
3374                    } = &data
3375                    else {
3376                        unreachable!("unexpected non-host resource table");
3377                    };
3378                    resource_table_ids.push(*tid);
3379                }
3380
3381                if resource_table_ids.is_empty() {
3382                    "".to_string()
3383                } else {
3384                    format!(
3385                        " resourceTables: [{}],",
3386                        resource_table_ids
3387                            .iter()
3388                            .map(|x| format!("handleTable{}", x.as_u32()))
3389                            .collect::<Vec<String>>()
3390                            .join(", ")
3391                    )
3392                }
3393            };
3394
3395            // Build trampolines for the import
3396            match self.bindgen.opts.import_bindings {
3397                Some(BindingsMode::Hybrid) => {
3398                    let symbol_cabi_lower = self.bindgen.intrinsic(Intrinsic::SymbolCabiLower);
3399                    uwriteln!(self.src.js_init, "if ({callee_name}[{symbol_cabi_lower}]) {{
3400                        trampoline{} = {callee_name}[{symbol_cabi_lower}]({{{memory}{realloc}{post_return}{string_encoding}{resource_tables}}});
3401                    }}", trampoline.as_u32());
3402                }
3403                Some(BindingsMode::Optimized) => {
3404                    let symbol_cabi_lower = self.bindgen.intrinsic(Intrinsic::SymbolCabiLower);
3405                    if !self.bindgen.opts.valid_lifting_optimization {
3406                        uwriteln!(self.src.js_init, "if (!{callee_name}[{symbol_cabi_lower}]) {{
3407                            throw new TypeError('import for \"{import_name}\" does not define a Symbol.for(\"cabiLower\") optimized binding');
3408                        }}");
3409                    }
3410                    uwriteln!(
3411                        self.src.js_init,
3412                        "trampoline{} = {callee_name}[{symbol_cabi_lower}]({{{memory}{realloc}{post_return}{string_encoding}{resource_tables}}});",
3413                        trampoline.as_u32()
3414                    );
3415                }
3416                Some(BindingsMode::DirectOptimized) => {
3417                    uwriteln!(
3418                        self.src.js_init,
3419                        "trampoline{} = {callee_name}({{{memory}{realloc}{post_return}{string_encoding}}});",
3420                        trampoline.as_u32()
3421                    );
3422                }
3423                None | Some(BindingsMode::Js) => unreachable!("invalid bindings mode"),
3424            };
3425        }
3426
3427        // Figure out the function name and callee (e.g. class for a given resource) to use
3428        let (import_name, binding_name) = match func.kind {
3429            FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
3430                (func_name.to_lower_camel_case(), callee_name)
3431            }
3432
3433            FunctionKind::Method(tid)
3434            | FunctionKind::AsyncMethod(tid)
3435            | FunctionKind::Static(tid)
3436            | FunctionKind::AsyncStatic(tid)
3437            | FunctionKind::Constructor(tid) => {
3438                let ty = &self.resolve.types[tid];
3439                let class_name = ty.name.as_ref().unwrap().to_upper_camel_case();
3440                let resource_name = self.imported_resource_name(*import_index, tid);
3441                (class_name, resource_name)
3442            }
3443        };
3444
3445        self.ensure_import(
3446            import_specifier,
3447            iface_name,
3448            maybe_iface_member.as_deref(),
3449            if iface_name.is_some() {
3450                Some(import_name.to_string())
3451            } else {
3452                None
3453            },
3454            binding_name,
3455        );
3456    }
3457
3458    /// Process an import if it has not already been processed
3459    ///
3460    /// # Arguments
3461    ///
3462    /// * `import_specifier` - The specifier of the import as used in JS (ex. `"@bytecodealliance/preview2-shim/random"`)
3463    /// * `iface_name` - The name of the WIT interface related to this binding, if present (ex. `"random"`)
3464    /// * `iface_member` - The name of the interface member, if present (ex. `"random"`)
3465    /// * `import_binding` - The name of binding, if present (ex. `"getRandomBytes"`)
3466    /// * `local_name` - Local name of the import (ex. `"getRandomBytes"`)
3467    ///
3468    fn ensure_import(
3469        &mut self,
3470        import_specifier: String,
3471        iface_name: Option<&str>,
3472        iface_member: Option<&str>,
3473        import_binding: Option<String>,
3474        local_name: String,
3475    ) {
3476        if import_specifier.starts_with("webidl:") {
3477            self.bindgen
3478                .intrinsic(Intrinsic::WebIdl(WebIdlIntrinsic::GlobalThisIdlProxy));
3479        }
3480
3481        // Build the import path depending on the kind of interface
3482        let mut import_path = Vec::with_capacity(2);
3483        import_path.push(import_specifier);
3484        if let Some(_iface_name) = iface_name {
3485            // Mapping can be used to construct virtual nested namespaces
3486            // which is used eg to support WASI interface groupings
3487            if let Some(iface_member) = iface_member {
3488                import_path.push(iface_member.to_lower_camel_case());
3489            }
3490            import_path.push(import_binding.clone().unwrap());
3491        } else if let Some(iface_member) = iface_member {
3492            import_path.push(iface_member.into());
3493        } else if let Some(import_binding) = &import_binding {
3494            import_path.push(import_binding.into());
3495        }
3496
3497        // Add the import binding that represents this import
3498        self.bindgen
3499            .esm_bindgen
3500            .add_import_binding(&import_path, local_name);
3501    }
3502
3503    /// Connect resources that have no types
3504    ///
3505    /// Commonly this is used for resources that have a type on on the import side
3506    /// but no relevant type on the receiving side, for which local types must be generated locally:
3507    /// - `error-context`
3508    /// - `future<_>`
3509    /// - `stream<_>`
3510    ///
3511    fn connect_p3_resources(
3512        &mut self,
3513        id: &TypeId,
3514        maybe_elem_ty: &Option<Type>,
3515        iface_ty: &InterfaceType,
3516        resource_map: &mut ResourceMap,
3517    ) {
3518        let remote_resource = match iface_ty {
3519            InterfaceType::Future(table_idx) => {
3520                let future_table_ty = &self.types[*table_idx];
3521                let future_ty = &self.types[future_table_ty.ty];
3522
3523                // Determine the level of future nesting
3524                let mut future_nesting_level = 0;
3525                let mut payload_ty = future_ty.payload;
3526                while let Some(InterfaceType::Future(inner_ty)) = payload_ty {
3527                    future_nesting_level += 1;
3528                    payload_ty = self.types[self.types[inner_ty].ty].payload;
3529                }
3530
3531                ResourceTable {
3532                    imported: true,
3533                    data: ResourceData::Guest {
3534                        resource_name: "Future".into(),
3535                        prefix: Some(format!("${}", table_idx.as_u32())),
3536                        extra: Some(ResourceExtraData::Future {
3537                            table_idx: *table_idx,
3538                            nesting_level: future_nesting_level,
3539                            elem_ty: maybe_elem_ty.map(|ty| {
3540                                let table_ty = &self.types[*table_idx];
3541                                let future_ty_idx = table_ty.ty;
3542                                let future_ty = &self.types[future_ty_idx];
3543                                let iface_ty = future_ty.payload.expect(
3544                                    "missing future payload despite elem type being present",
3545                                );
3546                                let abi = self.types.canonical_abi(&iface_ty);
3547                                PayloadTypeMetadata {
3548                                    ty,
3549                                    iface_ty,
3550
3551                                    // TODO: we need to use the currently-being-built resource map here,
3552                                    // because it may contain *just inserted* information (could be either imports or exports)
3553                                    // that should be used
3554                                    //
3555                                    // We need to *augment* the normal built in
3556                                    // `instantiator.resource_{exports,imports}` with things that we're resolving now.
3557                                    lift_js_expr: gen_flat_lift_fn_js_expr(
3558                                        self,
3559                                        &iface_ty,
3560                                        &Some(resource_map),
3561                                    ),
3562                                    lower_js_expr: gen_flat_lower_fn_js_expr(
3563                                        self,
3564                                        &iface_ty,
3565                                        &Some(resource_map),
3566                                    ),
3567                                    size32: abi.size32,
3568                                    align32: abi.align32,
3569                                    flat_count: abi.flat_count,
3570                                }
3571                            }),
3572                        }),
3573                    },
3574                }
3575            }
3576            InterfaceType::Stream(table_idx) => ResourceTable {
3577                imported: true,
3578                data: ResourceData::Guest {
3579                    resource_name: "Stream".into(),
3580                    prefix: Some(format!("${}", table_idx.as_u32())),
3581                    extra: Some(ResourceExtraData::Stream {
3582                        table_idx: *table_idx,
3583                        elem_ty: maybe_elem_ty.map(|ty| {
3584                            let table_ty = &self.types[*table_idx];
3585                            let stream_ty_idx = table_ty.ty;
3586                            let stream_ty = &self.types[stream_ty_idx];
3587                            let iface_ty = stream_ty
3588                                .payload
3589                                .expect("missing payload despite elem type being present");
3590                            let abi = self.types.canonical_abi(&iface_ty);
3591                            PayloadTypeMetadata {
3592                                ty,
3593                                iface_ty,
3594                                lift_js_expr: gen_flat_lift_fn_js_expr(
3595                                    self,
3596                                    &iface_ty,
3597                                    &Some(resource_map),
3598                                ),
3599                                lower_js_expr: gen_flat_lower_fn_js_expr(
3600                                    self,
3601                                    &iface_ty,
3602                                    &Some(resource_map),
3603                                ),
3604                                size32: abi.size32,
3605                                align32: abi.align32,
3606                                flat_count: abi.flat_count,
3607                            }
3608                        }),
3609                    }),
3610                },
3611            },
3612            InterfaceType::ErrorContext(table_idx) => ResourceTable {
3613                imported: true,
3614                data: ResourceData::Guest {
3615                    resource_name: "ErrorContext".into(),
3616                    prefix: Some(format!("${}", table_idx.as_u32())),
3617                    extra: Some(ResourceExtraData::ErrorContext {
3618                        table_idx: *table_idx,
3619                    }),
3620                },
3621            },
3622            _ => unreachable!("unexpected interface type [{iface_ty:?}] with no type"),
3623        };
3624
3625        resource_map.insert(*id, remote_resource);
3626    }
3627
3628    /// Connect two types as host resources
3629    ///
3630    /// # Arguments
3631    ///
3632    /// * `t` - the TypeId
3633    /// * `tid` - Index into the type resource table of the interface (foreign side)
3634    /// * `resource_map` - Resource map that holds resource pairings
3635    ///
3636    fn connect_host_resource(
3637        &mut self,
3638        t: TypeId,
3639        resource_table_ty_idx: TypeResourceTableIndex,
3640        resource_map: &mut ResourceMap,
3641    ) {
3642        self.ensure_resource_table(resource_table_ty_idx);
3643
3644        // Figure out whether the resource index we're dealing with is for an imported type
3645        let resource_table_ty = &self.types[resource_table_ty_idx];
3646        let resource_idx = resource_table_ty.unwrap_concrete_ty();
3647        let imported = self
3648            .component
3649            .defined_resource_index(resource_idx)
3650            .is_none();
3651
3652        // Retrieve the resource id for the type definition
3653        let resource_id = crate::dealias(self.resolve, t);
3654        let ty = &self.resolve.types[resource_id];
3655
3656        // If the resource is defined by this component (i.e. exported/used internally, *not* imported),
3657        // then determine the destructor that should be run based on the relevant resource
3658        let mut dtor_str = None;
3659        if let Some(resource_idx) = self.component.defined_resource_index(resource_idx) {
3660            assert!(!imported);
3661            let resource_def = self
3662                .component
3663                .initializers
3664                .iter()
3665                .find_map(|i| match i {
3666                    GlobalInitializer::Resource(r) if r.index == resource_idx => Some(r),
3667                    _ => None,
3668                })
3669                .unwrap();
3670
3671            if let Some(dtor) = &resource_def.dtor {
3672                dtor_str = Some(self.core_def(dtor));
3673            }
3674        }
3675
3676        // Look up the local import name
3677        let resource_name = ty.name.as_ref().unwrap().to_upper_camel_case();
3678
3679        let local_name = if imported {
3680            // Resolve the specific import that provides this resource table:
3681            // see [`Instantiator::find_import_providing_resource`].
3682            let imported_resource_entry = self.find_import_providing_resource(resource_idx);
3683
3684            let (world_key, iface_name) = match imported_resource_entry {
3685                // Resource provided by an imported instance: derive the
3686                // interface member name from the world key shape.
3687                Some((imp_name, _is_from_instance @ true)) => {
3688                    let key = self.imports[imp_name].clone();
3689                    let iface_name = match &key {
3690                        WorldKey::Name(name) => Some(name.clone()),
3691                        WorldKey::Interface(_) => {
3692                            match &self.resolve.worlds[self.world].imports[&key] {
3693                                WorldItem::Interface { id, .. } => {
3694                                    self.resolve.interfaces[*id].name.clone()
3695                                }
3696                                _ => None,
3697                            }
3698                        }
3699                    };
3700                    (key, iface_name)
3701                }
3702                // A world-level `import x: resource` style import
3703                Some((imp_name, _is_from_instance @ false)) => {
3704                    (self.imports[imp_name].clone(), None)
3705                }
3706                // Fall back to locating the import through the wit type's
3707                // owner; reachable only if the resource table doesn't appear
3708                // in the component's import types.
3709                None => match ty.owner {
3710                    wit_parser::TypeOwner::World(world) => (
3711                        self.resolve.worlds[world]
3712                            .imports
3713                            .iter()
3714                            .find(
3715                                |&(_, item)| matches!(item, WorldItem::Type { id, .. } if *id == t),
3716                            )
3717                            .unwrap()
3718                            .0
3719                            .clone(),
3720                        None,
3721                    ),
3722                    wit_parser::TypeOwner::Interface(iface) => {
3723                        let key = self.resolve.worlds[self.world]
3724                            .imports
3725                            .iter()
3726                            .find(|&(_, item)| match item {
3727                                WorldItem::Interface { id, .. } => *id == iface,
3728                                _ => false,
3729                            })
3730                            .map(|(key, _)| key)
3731                            .unwrap_or_else(|| {
3732                                panic!(
3733                                    "unable to find world import for interface [{}]",
3734                                    self.resolve.interfaces[iface]
3735                                        .name
3736                                        .as_deref()
3737                                        .unwrap_or("<unnamed>")
3738                                )
3739                            });
3740                        (
3741                            key.clone(),
3742                            match key {
3743                                WorldKey::Name(name) => Some(name.clone()),
3744                                WorldKey::Interface(_) => {
3745                                    self.resolve.interfaces[iface].name.clone()
3746                                }
3747                            },
3748                        )
3749                    }
3750                    wit_parser::TypeOwner::None => unimplemented!(),
3751                },
3752            };
3753            let iface_name = iface_name.as_deref();
3754
3755            let import_name = self.resolve.name_world_key(&world_key);
3756            let implements = self.resolve.worlds[self.world]
3757                .imports
3758                .get(&world_key)
3759                .and_then(|item| self.resolve.implements_value(&world_key, item));
3760            let (local_name, _) = self
3761                .bindgen
3762                .local_names
3763                .get_or_create(resource_idx, &resource_name);
3764
3765            let local_name_str = local_name.to_string();
3766
3767            // Nested interfaces only currently possible through mapping; must
3768            // resolve to the same specifier as the owning interface's
3769            // functions, including the `implements` mapping fallback.
3770            let (import_specifier, maybe_iface_member) = map_import_with_implements(
3771                &self.bindgen.opts.map,
3772                &import_name,
3773                implements.as_deref(),
3774            );
3775
3776            // Ensure that the import exists
3777            self.ensure_import(
3778                import_specifier,
3779                iface_name,
3780                maybe_iface_member.as_deref(),
3781                iface_name.map(|_| resource_name),
3782                local_name_str.to_string(),
3783            );
3784            local_name_str
3785        } else {
3786            let (local_name, _) = self
3787                .bindgen
3788                .local_names
3789                .get_or_create(resource_idx, &resource_name);
3790            local_name.to_string()
3791        };
3792
3793        // Add a resource table to track the host resource
3794        let entry = ResourceTable {
3795            imported,
3796            data: ResourceData::Host {
3797                tid: resource_table_ty_idx,
3798                rid: resource_idx,
3799                local_name,
3800                dtor_name: dtor_str,
3801            },
3802        };
3803
3804        // If the the resource already exists, then  ensure that it is exactly the same as the
3805        // value we're attempting to insert
3806        if let Some(existing) = resource_map.get(&resource_id) {
3807            // The same wit resource type may be imported more than once under
3808            // different labels (component model `implements` feature), giving
3809            // it one resource table per label. Shared type-keyed maps keep
3810            // the first table encountered; maps built per lowered function
3811            // only ever see the table of that function's own instance.
3812            if *existing != entry {
3813                assert!(
3814                    imported && existing.imported,
3815                    "conflicting resource tables for non-imported resource"
3816                );
3817            }
3818            return;
3819        }
3820
3821        // Insert the resource into the map,
3822        resource_map.insert(resource_id, entry);
3823    }
3824
3825    /// Connect resources that are defined at the type levels in `wit-parser`
3826    /// to their types as defined in `wasmtime-environ`
3827    ///
3828    /// The types that are connected here are stored in the `resource_map` for
3829    /// use later.
3830    ///
3831    /// # Arguments
3832    ///
3833    /// * `id` - The ID of the type if present (can be missing when dealing with `error-context`s, `future<_>`, etc)
3834    /// * `iface_ty` - The relevant interface type
3835    /// * `resource_map` - Resource map that we will update with pairings
3836    ///
3837    fn connect_resource_types(
3838        &mut self,
3839        id: TypeId,
3840        iface_ty: &InterfaceType,
3841        resource_map: &mut ResourceMap,
3842    ) {
3843        let kind = &self.resolve.types[id].kind;
3844        match (kind, iface_ty) {
3845            // For flags and enums we can do nothing -- they're simple values (string/number)
3846            (TypeDefKind::Flags(_), InterfaceType::Flags(_))
3847            | (TypeDefKind::Enum(_), InterfaceType::Enum(_)) => {}
3848
3849            // Connect records to records
3850            (TypeDefKind::Record(t1), InterfaceType::Record(t2)) => {
3851                let t2 = &self.types[*t2];
3852                for (f1, f2) in t1.fields.iter().zip(t2.fields.iter()) {
3853                    if let Type::Id(id) = f1.ty {
3854                        self.connect_resource_types(id, &f2.ty, resource_map);
3855                    }
3856                }
3857            }
3858
3859            // Handle connecting owned/borrowed handles to owned/borrowed handles
3860            (
3861                TypeDefKind::Handle(Handle::Own(t1) | Handle::Borrow(t1)),
3862                InterfaceType::Own(t2) | InterfaceType::Borrow(t2),
3863            ) => {
3864                self.connect_host_resource(*t1, *t2, resource_map);
3865            }
3866
3867            // Connect tuples to interface tuples
3868            (TypeDefKind::Tuple(t1), InterfaceType::Tuple(t2)) => {
3869                let t2 = &self.types[*t2];
3870                for (f1, f2) in t1.types.iter().zip(t2.types.iter()) {
3871                    if let Type::Id(id) = f1 {
3872                        self.connect_resource_types(*id, f2, resource_map);
3873                    }
3874                }
3875            }
3876
3877            // Connect inner types of variants to their interface types
3878            (TypeDefKind::Variant(t1), InterfaceType::Variant(t2)) => {
3879                let t2 = &self.types[*t2];
3880                for (f1, f2) in t1.cases.iter().zip(t2.cases.iter()) {
3881                    if let Some(Type::Id(id)) = &f1.ty {
3882                        self.connect_resource_types(*id, f2.1.as_ref().unwrap(), resource_map);
3883                    }
3884                }
3885            }
3886
3887            // Connect option<t> to option<t>
3888            (TypeDefKind::Option(t1), InterfaceType::Option(t2)) => {
3889                let t2 = &self.types[*t2];
3890                if let Type::Id(id) = t1 {
3891                    self.connect_resource_types(*id, &t2.ty, resource_map);
3892                }
3893            }
3894
3895            // Connect result<t> to result<t>
3896            (TypeDefKind::Result(t1), InterfaceType::Result(t2)) => {
3897                let t2 = &self.types[*t2];
3898                if let Some(Type::Id(id)) = &t1.ok {
3899                    self.connect_resource_types(*id, &t2.ok.unwrap(), resource_map);
3900                }
3901                if let Some(Type::Id(id)) = &t1.err {
3902                    self.connect_resource_types(*id, &t2.err.unwrap(), resource_map);
3903                }
3904            }
3905
3906            // Connect list<t> to list types
3907            (TypeDefKind::List(t1), InterfaceType::List(t2)) => {
3908                let t2 = &self.types[*t2];
3909                if let Type::Id(id) = t1 {
3910                    self.connect_resource_types(*id, &t2.element, resource_map);
3911                }
3912            }
3913
3914            // Connect map key and value types
3915            (TypeDefKind::Map(key, value), InterfaceType::Map(map)) => {
3916                let map = &self.types[*map];
3917                if let Type::Id(id) = key {
3918                    self.connect_resource_types(*id, &map.key, resource_map);
3919                }
3920                if let Type::Id(id) = value {
3921                    self.connect_resource_types(*id, &map.value, resource_map);
3922                }
3923            }
3924
3925            // Connect list<t, size> to list types
3926            (TypeDefKind::FixedLengthList(t1, _len), InterfaceType::FixedLengthList(t2)) => {
3927                let t2 = &self.types[*t2];
3928                if let Type::Id(id) = t1 {
3929                    self.connect_resource_types(*id, &t2.element, resource_map);
3930                }
3931            }
3932
3933            // Connect named types
3934            (TypeDefKind::Type(ty), _) => {
3935                if let Type::Id(id) = ty {
3936                    self.connect_resource_types(*id, iface_ty, resource_map);
3937                }
3938            }
3939
3940            // Connect futures & stream types
3941            (TypeDefKind::Future(maybe_elem_ty), container_iface_ty)
3942            | (TypeDefKind::Stream(maybe_elem_ty), container_iface_ty) => {
3943                match maybe_elem_ty {
3944                    // The case of an empty future is the propagation of a `null`-like value, usually a simple signal
3945                    // which we'll connect with the *normally invalid* type value 0 as an indicator
3946                    None => {
3947                        self.connect_p3_resources(&id, maybe_elem_ty, iface_ty, resource_map);
3948                    }
3949                    // For custom types we must recur to properly connect the inner type
3950                    Some(elem_ty @ Type::Id(elem_ty_id)) => {
3951                        // As the internal type could be a resource, and connecting p3 resources
3952                        // may generate lifting/lowering fns, we must connect the payload of the
3953                        // future/stream first, if necessary
3954                        //
3955                        let maybe_elem_iface_ty = match container_iface_ty {
3956                            InterfaceType::Future(future_table_ty_idx) => {
3957                                let future_table_ty = &self.types[*future_table_ty_idx];
3958                                let future = &self.types[future_table_ty.ty];
3959                                future.payload
3960                            }
3961                            InterfaceType::Stream(stream_table_ty_idx) => {
3962                                let stream_table_ty = &self.types[*stream_table_ty_idx];
3963                                let stream = &self.types[stream_table_ty.ty];
3964                                stream.payload
3965                            }
3966                            _ => unreachable!("unexpected iface type"),
3967                        };
3968                        if let Some(elem_iface_ty) = maybe_elem_iface_ty {
3969                            // TODO(refactor): the last arg of `connect_resource_types()` (`extra_resource_map`) is
3970                            // necessary because we are not building the imports/exports array directly.
3971                            //
3972                            // It's a hack that *should* be removable if we do more explicit and intentional
3973                            // building of import/export resource mappings (i.e. not building a partial map that we
3974                            // later `.extend()` onto the instantiator's maps, depending on whether we were working on
3975                            // imports or exports).
3976                            self.connect_resource_types(*elem_ty_id, &elem_iface_ty, resource_map);
3977                        }
3978
3979                        self.connect_p3_resources(&id, &Some(*elem_ty), iface_ty, resource_map);
3980                    }
3981                    // For basic types that are connected (non inner types) we can do a generic connect
3982                    Some(_) => {
3983                        self.connect_p3_resources(&id, maybe_elem_ty, iface_ty, resource_map);
3984                    }
3985                }
3986            }
3987
3988            // Connect the types in an ok/error variant of a Result to the future that they're being sent in
3989            (
3990                TypeDefKind::Result(Result_ { ok, err }),
3991                tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
3992            ) => {
3993                if let Some(Type::Id(ok_t)) = ok {
3994                    self.connect_resource_types(*ok_t, tk2, resource_map)
3995                }
3996                if let Some(Type::Id(err_t)) = err {
3997                    self.connect_resource_types(*err_t, tk2, resource_map)
3998                }
3999            }
4000
4001            // Connect the types in an option to the future that they're being sent in
4002            (
4003                TypeDefKind::Option(ty),
4004                tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
4005            ) => {
4006                if let Type::Id(some_t) = ty {
4007                    self.connect_resource_types(*some_t, tk2, resource_map)
4008                }
4009            }
4010
4011            // Connect resources to the future/stream that they're being sent in
4012            (
4013                TypeDefKind::Handle(Handle::Own(t1) | Handle::Borrow(t1)),
4014                tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
4015            ) => self.connect_resource_types(*t1, tk2, resource_map),
4016
4017            (TypeDefKind::Resource, InterfaceType::Future(_) | InterfaceType::Stream(_)) => {}
4018
4019            // Connect the inner types of variants to the future they're being sent in
4020            (
4021                TypeDefKind::Variant(variant),
4022                tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
4023            ) => {
4024                for f1 in variant.cases.iter() {
4025                    if let Some(Type::Id(id)) = &f1.ty {
4026                        self.connect_resource_types(*id, tk2, resource_map);
4027                    }
4028                }
4029            }
4030
4031            // Connect the inner types of variants to the future they're being sent in
4032            (
4033                TypeDefKind::Record(record),
4034                tk2 @ (InterfaceType::Future(_) | InterfaceType::Stream(_)),
4035            ) => {
4036                for f1 in record.fields.iter() {
4037                    if let Type::Id(id) = f1.ty {
4038                        self.connect_resource_types(id, tk2, resource_map);
4039                    }
4040                }
4041            }
4042
4043            // Simliar to the non-stream/future case, we don't have to do anything for
4044            // flags and plain enums as they are read directly
4045            (
4046                TypeDefKind::Enum(_) | TypeDefKind::Flags(_),
4047                InterfaceType::Future(_) | InterfaceType::Stream(_),
4048            ) => {}
4049
4050            (TypeDefKind::Resource, tk2) => {
4051                unreachable!(
4052                    "resource types do not need to be connected (in this case, to [{tk2:?}])"
4053                )
4054            }
4055
4056            (TypeDefKind::Unknown, tk2) => {
4057                unreachable!("unknown types cannot be connected (in this case to [{tk2:?}])")
4058            }
4059
4060            (tk1, tk2) => unreachable!("invalid typedef kind combination [{tk1:?}] [{tk2:?}]",),
4061        }
4062    }
4063
4064    fn bindgen(&mut self, args: JsFunctionBindgenArgs) {
4065        let JsFunctionBindgenArgs {
4066            nparams,
4067            call_type,
4068            iface_name,
4069            callee,
4070            opts,
4071            func,
4072            resource_map,
4073            abi,
4074            requires_async_porcelain,
4075            is_async,
4076            for_import,
4077        } = args;
4078
4079        let (memory, realloc) =
4080            if let CanonicalOptionsDataModel::LinearMemory(LinearMemoryOptions {
4081                memory,
4082                realloc,
4083            }) = opts.data_model
4084            {
4085                (
4086                    memory.map(|idx| format!("memory{}", idx.as_u32())),
4087                    realloc.map(|idx| {
4088                        format!(
4089                            "realloc{}{}",
4090                            idx.as_u32(),
4091                            if is_async {
4092                                "Async"
4093                            } else {
4094                                Default::default()
4095                            }
4096                        )
4097                    }),
4098                )
4099            } else {
4100                (None, None)
4101            };
4102
4103        let post_return = opts.post_return.map(|idx| {
4104            format!(
4105                "postReturn{}{}",
4106                idx.as_u32(),
4107                if is_async {
4108                    "Async"
4109                } else {
4110                    Default::default()
4111                }
4112            )
4113        });
4114
4115        let tracing_prefix = format!(
4116            "[iface=\"{}\", function=\"{}\"]",
4117            iface_name.unwrap_or("<no iface>"),
4118            func.name
4119        );
4120
4121        // Write the function argument list
4122        //
4123        // At this point, only the function preamble (e.g. 'function nameOfFunc()') has been written
4124        self.src.js("(");
4125        let mut params = Vec::new();
4126        let mut first = true;
4127        for i in 0..nparams {
4128            if i == 0
4129                && matches!(
4130                    call_type,
4131                    CallType::FirstArgIsThis | CallType::AsyncFirstArgIsThis
4132                )
4133            {
4134                params.push("this".into());
4135                continue;
4136            }
4137            if !first {
4138                self.src.js(", ");
4139            } else {
4140                first = false;
4141            }
4142            let param = format!("arg{i}");
4143            self.src.js(&param);
4144            params.push(param);
4145        }
4146        uwriteln!(self.src.js, ") {{");
4147
4148        // If tracing is enabled, output a function entry tracing message
4149        if self.bindgen.opts.tracing {
4150            let event_fields = func
4151                .params
4152                .iter()
4153                .enumerate()
4154                .map(|(i, p)| format!("{}=${{arguments[{i}]}}", p.name))
4155                .collect::<Vec<String>>();
4156            uwriteln!(
4157                self.src.js,
4158                "console.error(`{tracing_prefix} call {}`);",
4159                event_fields.join(", ")
4160            );
4161        }
4162
4163        // If TLA compat was enabled, ensure that it was initialized
4164        if self.bindgen.opts.tla_compat
4165            && matches!(abi, AbiVariant::GuestExport)
4166            && self.bindgen.opts.instantiation_mode.is_none()
4167        {
4168            let throw_uninitialized = self.bindgen.intrinsic(Intrinsic::ThrowUninitialized);
4169            uwrite!(
4170                self.src.js,
4171                "\
4172                if (!_initialized) {throw_uninitialized}();
4173            "
4174            );
4175        }
4176
4177        // Generate function body
4178        let mut f = FunctionBindgen {
4179            resource_map,
4180            clear_resource_borrows: false,
4181            intrinsics: &mut self.bindgen.all_intrinsics,
4182            valid_lifting_optimization: self.bindgen.opts.valid_lifting_optimization,
4183            sizes: &self.sizes,
4184            err: if get_thrown_type(self.resolve, func.result).is_some() {
4185                match abi {
4186                    AbiVariant::GuestExport
4187                    | AbiVariant::GuestExportAsync
4188                    | AbiVariant::GuestExportAsyncStackful => ErrHandling::ThrowResultErr,
4189                    AbiVariant::GuestImport | AbiVariant::GuestImportAsync => {
4190                        ErrHandling::ResultCatchHandler
4191                    }
4192                }
4193            } else {
4194                ErrHandling::None
4195            },
4196            block_storage: Vec::new(),
4197            blocks: Vec::new(),
4198            callee,
4199            callee_resource_dynamic: matches!(
4200                call_type,
4201                CallType::CalleeResourceDispatch | CallType::AsyncCalleeResourceDispatch
4202            ),
4203            memory: memory.as_ref(),
4204            realloc: realloc.as_ref(),
4205            tmp: 0,
4206            params,
4207            post_return: post_return.as_ref(),
4208            tracing_prefix: &tracing_prefix,
4209            tracing_enabled: self.bindgen.opts.tracing,
4210            encoding: match opts.string_encoding {
4211                wasmtime_environ::component::StringEncoding::Utf8 => StringEncoding::UTF8,
4212                wasmtime_environ::component::StringEncoding::Utf16 => StringEncoding::UTF16,
4213                wasmtime_environ::component::StringEncoding::CompactUtf16 => {
4214                    StringEncoding::CompactUTF16
4215                }
4216            },
4217            src: source::Source::default(),
4218            resolve: self.resolve,
4219            requires_async_porcelain,
4220            is_async,
4221            iface_name,
4222            asmjs: self.bindgen.opts.asmjs,
4223            component_state: Some(FunctionBindgenComponentState {
4224                component_idx: opts.instance,
4225                realloc_fn_idx: if let CanonicalOptionsDataModel::LinearMemory(
4226                    LinearMemoryOptions { realloc, .. },
4227                ) = opts.data_model
4228                {
4229                    realloc
4230                } else {
4231                    None
4232                },
4233                memory_idx: opts.memory(),
4234                callback_fn_idx: opts.callback,
4235            }),
4236            for_import: Some(for_import),
4237        };
4238
4239        // Emit (and visit, via the `FunctionBindgen` object) an abstract sequence of
4240        // instructions which represents the function being generated.
4241        abi::call(
4242            self.resolve,
4243            abi,
4244            match abi {
4245                AbiVariant::GuestImport | AbiVariant::GuestImportAsync => {
4246                    LiftLower::LiftArgsLowerResults
4247                }
4248                AbiVariant::GuestExport
4249                | AbiVariant::GuestExportAsync
4250                | AbiVariant::GuestExportAsyncStackful => LiftLower::LowerArgsLiftResults,
4251            },
4252            func,
4253            &mut f,
4254            is_async,
4255        );
4256
4257        // Once visiting has completed, write the contents the `FunctionBindgen` generated to output
4258        self.src.js(&f.src);
4259
4260        // Close function body
4261        self.src.js("}");
4262    }
4263
4264    fn augmented_import_def(&self, def: &core::AugmentedImport<'_>) -> String {
4265        match def {
4266            core::AugmentedImport::CoreDef(def) => self.core_def(def),
4267            core::AugmentedImport::Memory { mem, op } => {
4268                let mem = self.core_def(mem);
4269                match op {
4270                    core::AugmentedOp::I32Load => {
4271                        format!(
4272                            "(ptr, off) => new DataView({mem}.buffer).getInt32(ptr + off, true)"
4273                        )
4274                    }
4275                    core::AugmentedOp::I32Load8U => {
4276                        format!(
4277                            "(ptr, off) => new DataView({mem}.buffer).getUint8(ptr + off, true)"
4278                        )
4279                    }
4280                    core::AugmentedOp::I32Load8S => {
4281                        format!("(ptr, off) => new DataView({mem}.buffer).getInt8(ptr + off, true)")
4282                    }
4283                    core::AugmentedOp::I32Load16U => {
4284                        format!(
4285                            "(ptr, off) => new DataView({mem}.buffer).getUint16(ptr + off, true)"
4286                        )
4287                    }
4288                    core::AugmentedOp::I32Load16S => {
4289                        format!(
4290                            "(ptr, off) => new DataView({mem}.buffer).getInt16(ptr + off, true)"
4291                        )
4292                    }
4293                    core::AugmentedOp::I64Load => {
4294                        format!(
4295                            "(ptr, off) => new DataView({mem}.buffer).getBigInt64(ptr + off, true)"
4296                        )
4297                    }
4298                    core::AugmentedOp::F32Load => {
4299                        format!(
4300                            "(ptr, off) => new DataView({mem}.buffer).getFloat32(ptr + off, true)"
4301                        )
4302                    }
4303                    core::AugmentedOp::F64Load => {
4304                        format!(
4305                            "(ptr, off) => new DataView({mem}.buffer).getFloat64(ptr + off, true)"
4306                        )
4307                    }
4308                    core::AugmentedOp::I32Store8 => {
4309                        format!(
4310                            "(ptr, val, offset) => {{
4311                                new DataView({mem}.buffer).setInt8(ptr + offset, val, true);
4312                            }}"
4313                        )
4314                    }
4315                    core::AugmentedOp::I32Store16 => {
4316                        format!(
4317                            "(ptr, val, offset) => {{
4318                                new DataView({mem}.buffer).setInt16(ptr + offset, val, true);
4319                            }}"
4320                        )
4321                    }
4322                    core::AugmentedOp::I32Store => {
4323                        format!(
4324                            "(ptr, val, offset) => {{
4325                                new DataView({mem}.buffer).setInt32(ptr + offset, val, true);
4326                            }}"
4327                        )
4328                    }
4329                    core::AugmentedOp::I64Store => {
4330                        format!(
4331                            "(ptr, val, offset) => {{
4332                                new DataView({mem}.buffer).setBigInt64(ptr + offset, val, true);
4333                            }}"
4334                        )
4335                    }
4336                    core::AugmentedOp::F32Store => {
4337                        format!(
4338                            "(ptr, val, offset) => {{
4339                                new DataView({mem}.buffer).setFloat32(ptr + offset, val, true);
4340                            }}"
4341                        )
4342                    }
4343                    core::AugmentedOp::F64Store => {
4344                        format!(
4345                            "(ptr, val, offset) => {{
4346                                new DataView({mem}.buffer).setFloat64(ptr + offset, val, true);
4347                            }}"
4348                        )
4349                    }
4350                    core::AugmentedOp::MemorySize => {
4351                        format!("ptr => {mem}.buffer.byteLength / 65536")
4352                    }
4353                }
4354            }
4355        }
4356    }
4357
4358    fn core_def(&self, def: &CoreDef) -> String {
4359        match def {
4360            CoreDef::Export(e) => self.core_export_var_name(e),
4361            CoreDef::TaskMayBlock => AsyncTaskIntrinsic::CurrentTaskMayBlock.name().into(),
4362            CoreDef::Trampoline(i) => format!("trampoline{}", i.as_u32()),
4363            CoreDef::InstanceFlags(i) => {
4364                // SAFETY: short-lived borrow-mut.
4365                self.used_instance_flags.borrow_mut().insert(*i);
4366                format!("instanceFlags{}", i.as_u32())
4367            }
4368            CoreDef::UnsafeIntrinsic(ui) => match ui {
4369                wasmtime_environ::component::UnsafeIntrinsic::ContextGetI32_0 => {
4370                    let context_get_fn =
4371                        Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextGet).name();
4372                    format!(
4373                        "{context_get_fn}.bind(null, {{ componentIdx: {}, slot: 0 }})",
4374                        self.init_current_module
4375                            .expect("missing current module")
4376                            .as_u32(),
4377                    )
4378                }
4379                wasmtime_environ::component::UnsafeIntrinsic::ContextSetI32_0 => {
4380                    let context_set_fn =
4381                        Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextSet).name();
4382                    format!(
4383                        "{context_set_fn}.bind(null, {{ componentIdx: {}, slot: 0 }})",
4384                        self.init_current_module
4385                            .expect("missing current module")
4386                            .as_u32(),
4387                    )
4388                }
4389                wasmtime_environ::component::UnsafeIntrinsic::ContextGetI32_1 => {
4390                    let context_get_fn =
4391                        Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextGet).name();
4392                    format!(
4393                        "{context_get_fn}.bind(null, {{ componentIdx: {}, slot: 1 }})",
4394                        self.init_current_module
4395                            .expect("missing current module")
4396                            .as_u32(),
4397                    )
4398                }
4399                wasmtime_environ::component::UnsafeIntrinsic::ContextSetI32_1 => {
4400                    let context_set_fn =
4401                        Intrinsic::AsyncTask(AsyncTaskIntrinsic::ContextSet).name();
4402                    format!(
4403                        "{context_set_fn}.bind(null, {{ componentIdx: {}, slot: 1 }})",
4404                        self.init_current_module
4405                            .expect("missing current module")
4406                            .as_u32(),
4407                    )
4408                }
4409
4410                // All other intriniscs can be set generically
4411                ui => {
4412                    let idx = ui.index();
4413                    format!("unsafeIntrinsic{idx}")
4414                }
4415            },
4416        }
4417    }
4418
4419    fn core_export_var_name<T>(&self, export: &CoreExport<T>) -> String
4420    where
4421        T: Into<EntityIndex> + Copy,
4422    {
4423        let name = match &export.item {
4424            ExportItem::Index(idx) => {
4425                let module_idx = self
4426                    .instances
4427                    .get(export.instance)
4428                    .expect("unexpectedly missing export instance");
4429                let module = &self
4430                    .modules
4431                    .get(*module_idx)
4432                    .expect("unexpectedly missing module by idx");
4433                let idx = (*idx).into();
4434                module
4435                    .exports()
4436                    .iter()
4437                    .find_map(|(name, i)| if *i == idx { Some(name) } else { None })
4438                    .unwrap()
4439                    .to_string()
4440            }
4441            ExportItem::Name(s) => s.to_string(),
4442        };
4443        let i = export.instance.as_u32() as usize;
4444        let quoted = maybe_quote_member(&name);
4445        format!("exports{i}{quoted}")
4446    }
4447
4448    /// Process the component imports and build mappings
4449    fn process_imports(&mut self) {
4450        let mut import_resource_map = ResourceMap::new();
4451        for (_import_name, (import_idx, _import_path)) in self.component.imports.iter() {
4452            let (import_name, import_type_def) = &self.component.import_types[*import_idx];
4453            let import_world_key = &self
4454                .imports
4455                .get(import_name)
4456                .expect("missing import mapping");
4457            let import_world_item = &self
4458                .resolve
4459                .worlds
4460                .get(self.world)
4461                .expect("missing world")
4462                .imports
4463                .get(*import_world_key)
4464                .expect("missing import in world for import");
4465
4466            // Generate type information for types used in functions
4467            match import_world_item {
4468                WorldItem::Interface { id: iface_id, .. } => {
4469                    let iface = &self.resolve.interfaces[*iface_id];
4470
4471                    // Process functions imported by the iface, which will use (as arg or param)
4472                    // relevant resources
4473                    for (fn_name, iface_fn) in iface.functions.iter() {
4474                        match import_type_def {
4475                            ComponentExtern {
4476                                ty: TypeDef::ComponentInstance(instance_ty),
4477                                ..
4478                            } => {
4479                                if let Some(ComponentExtern {
4480                                    ty: TypeDef::ComponentFunc(type_func_index),
4481                                    ..
4482                                }) = &self.types[*instance_ty].exports.get(fn_name)
4483                                {
4484                                    self.create_resource_fn_map(
4485                                        iface_fn,
4486                                        *type_func_index,
4487                                        &mut import_resource_map,
4488                                    );
4489                                }
4490                            }
4491                            ComponentExtern {
4492                                ty: TypeDef::ComponentFunc(type_func_idx),
4493                                ..
4494                            } => {
4495                                self.create_resource_fn_map(
4496                                    iface_fn,
4497                                    *type_func_idx,
4498                                    &mut import_resource_map,
4499                                );
4500                            }
4501                            _ => {}
4502                        }
4503                    }
4504                }
4505
4506                // Process imported functions directly to build resource maps
4507                WorldItem::Function(func) => {
4508                    let TypeDef::ComponentFunc(func_ty_idx) = &import_type_def.ty else {
4509                        unreachable!("invalid fn export");
4510                    };
4511                    self.create_resource_fn_map(func, *func_ty_idx, &mut import_resource_map);
4512                }
4513                // Simply informational at this point
4514                WorldItem::Type { .. } => {}
4515            }
4516        }
4517
4518        self.resource_imports.extend(import_resource_map);
4519    }
4520
4521    /// Process component exports and build mappings
4522    fn process_exports(&mut self) {
4523        // Since imports may be referred to by exports, we include all imports in the exports array
4524        self.resource_exports.extend(self.resource_imports.clone());
4525
4526        // Process individual component exports
4527        for (export_name, (export_idx, _extern_data)) in self.component.exports.raw_iter() {
4528            let export_name = export_name.as_ref().to_string();
4529            let export = &self.component.export_items[*export_idx];
4530            let world_key = &self.exports[&export_name];
4531            let item = &self.resolve.worlds[self.world].exports[world_key];
4532            let mut export_resource_map = ResourceMap::new();
4533
4534            match export {
4535                Export::LiftedFunction {
4536                    func: def,
4537                    options,
4538                    ty: func_ty,
4539                } => {
4540                    let func = match item {
4541                        WorldItem::Function(f) => f,
4542                        WorldItem::Interface { .. } | WorldItem::Type { .. } => {
4543                            unreachable!("unexpectedly non-function lifted function export")
4544                        }
4545                    };
4546
4547                    self.create_resource_fn_map(func, *func_ty, &mut export_resource_map);
4548
4549                    let local_name = String::from(match func.kind {
4550                        // For resources, we must take the type name (adding `.prototype.<fn name>` later)
4551                        FunctionKind::Constructor(resource_id)
4552                        | FunctionKind::Method(resource_id)
4553                        | FunctionKind::AsyncMethod(resource_id)
4554                        | FunctionKind::Static(resource_id)
4555                        | FunctionKind::AsyncStatic(resource_id) => Instantiator::resource_name(
4556                            self.resolve,
4557                            &mut self.bindgen.local_names,
4558                            resource_id,
4559                            &self.exports_resource_types,
4560                        ),
4561                        // Fore free standing functions we can use the exoprt name directly as a local name
4562                        FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
4563                            self.bindgen.local_names.create_once(&export_name)
4564                        }
4565                    });
4566
4567                    let options = self
4568                        .component
4569                        .options
4570                        .get(*options)
4571                        .expect("failed to find options");
4572
4573                    self.export_bindgen(
4574                        &local_name,
4575                        def,
4576                        options,
4577                        func,
4578                        func_ty,
4579                        &export_name,
4580                        &export_resource_map,
4581                    );
4582
4583                    let js_binding_name = match func.kind {
4584                        // For resources, we must take the type name (adding `.prototype.<fn name>` later)
4585                        FunctionKind::Constructor(ty)
4586                        | FunctionKind::Method(ty)
4587                        | FunctionKind::AsyncMethod(ty)
4588                        | FunctionKind::Static(ty)
4589                        | FunctionKind::AsyncStatic(ty) => self.resolve.types[ty]
4590                            .name
4591                            .as_ref()
4592                            .unwrap()
4593                            .to_upper_camel_case(),
4594                        // For free standing functions we can use the export name directly
4595                        FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
4596                            export_name.to_lower_camel_case()
4597                        }
4598                    };
4599
4600                    // Add the export binding
4601                    self.bindgen.esm_bindgen.add_export_binding(
4602                        None,
4603                        local_name,
4604                        js_binding_name,
4605                        func,
4606                    );
4607                }
4608
4609                Export::Instance { exports, .. } => {
4610                    let iface_id = match item {
4611                        WorldItem::Interface { id, .. } => *id,
4612                        WorldItem::Function(_) | WorldItem::Type { .. } => {
4613                            unreachable!("unexpectedly non-interface export instance")
4614                        }
4615                    };
4616
4617                    // Process exported instances
4618                    for (func_name, (export_idx, _extern_data)) in exports.raw_iter() {
4619                        let func_name = func_name.as_ref().to_string();
4620                        let export = &self.component.export_items[*export_idx];
4621
4622                        // Gather function information for all lifted functions in the isntance export
4623                        let (def, options, func_ty) = match export {
4624                            Export::LiftedFunction { func, options, ty } => (func, options, ty),
4625                            Export::Type(_) => continue, // ignored
4626                            _ => unreachable!("unexpected non-lifted function export"),
4627                        };
4628
4629                        let func = &self.resolve.interfaces[iface_id].functions[&func_name];
4630
4631                        self.create_resource_fn_map(func, *func_ty, &mut export_resource_map);
4632
4633                        let local_name = String::from(match func.kind {
4634                            // For resources, we must use the name of the type
4635                            FunctionKind::Constructor(resource_id)
4636                            | FunctionKind::Method(resource_id)
4637                            | FunctionKind::AsyncMethod(resource_id)
4638                            | FunctionKind::Static(resource_id)
4639                            | FunctionKind::AsyncStatic(resource_id) => {
4640                                Instantiator::resource_name(
4641                                    self.resolve,
4642                                    &mut self.bindgen.local_names,
4643                                    resource_id,
4644                                    &self.exports_resource_types,
4645                                )
4646                            }
4647                            // For free standing functions we can use the bare func name
4648                            FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
4649                                self.bindgen.local_names.create_once(&func_name)
4650                            }
4651                        });
4652
4653                        let options = self
4654                            .component
4655                            .options
4656                            .get(*options)
4657                            .expect("failed to find options");
4658
4659                        self.export_bindgen(
4660                            &local_name,
4661                            def,
4662                            options,
4663                            func,
4664                            func_ty,
4665                            &export_name,
4666                            &export_resource_map,
4667                        );
4668
4669                        // Determine the export func name (this can also be a class name)
4670                        let export_binding_name = match func.kind {
4671                            // For resources, we must use the type name (later adding `.prototype.<actual fn>`)
4672                            FunctionKind::Constructor(ty)
4673                            | FunctionKind::Method(ty)
4674                            | FunctionKind::AsyncMethod(ty)
4675                            | FunctionKind::Static(ty)
4676                            | FunctionKind::AsyncStatic(ty) => self.resolve.types[ty]
4677                                .name
4678                                .as_ref()
4679                                .unwrap()
4680                                .to_upper_camel_case(),
4681                            // Free standing functions we can use the function name directly
4682                            FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => {
4683                                func_name.to_lower_camel_case()
4684                            }
4685                        };
4686
4687                        // Add the export binding
4688                        self.bindgen.esm_bindgen.add_export_binding(
4689                            Some(&export_name),
4690                            local_name,
4691                            export_binding_name,
4692                            func,
4693                        );
4694                    }
4695                }
4696
4697                // ignore type exports for now
4698                Export::Type(_) => {}
4699
4700                // This can't be tested at this time so leave it unimplemented
4701                Export::ModuleStatic { .. } | Export::ModuleImport { .. } => unimplemented!(),
4702            }
4703
4704            // Save information about exported resources for later
4705            self.resource_exports.extend(export_resource_map);
4706        }
4707
4708        self.bindgen.esm_bindgen.populate_export_aliases();
4709    }
4710
4711    #[allow(clippy::too_many_arguments)]
4712    fn export_bindgen(
4713        &mut self,
4714        local_name: &str,
4715        def: &CoreDef,
4716        options: &CanonicalOptions,
4717        func: &Function,
4718        _func_ty_idx: &TypeFuncIndex,
4719        export_name: &String,
4720        export_resource_map: &ResourceMap,
4721    ) {
4722        // Determine whether the function should be generated as async
4723        let requires_async_porcelain = requires_async_porcelain(
4724            FunctionIdentifier::Fn(func),
4725            export_name,
4726            &self.async_exports,
4727        );
4728        // If the function is *also* async lifted, it
4729        if options.async_ {
4730            assert!(
4731                options.post_return.is_none(),
4732                "async function {local_name} (export {export_name}) can't have post return"
4733            );
4734        }
4735
4736        let is_async = is_async_fn(func, options);
4737
4738        let maybe_async = if requires_async_porcelain || is_async {
4739            "async "
4740        } else {
4741            ""
4742        };
4743
4744        // Start building early variable declarations
4745        let core_export_fn = self.core_def(def);
4746        let callee = match self
4747            .bindgen
4748            .local_names
4749            .get_or_create(&core_export_fn, &core_export_fn)
4750        {
4751            (local_name, true) => local_name.to_string(),
4752            (local_name, false) => {
4753                let local_name = local_name.to_string();
4754                uwriteln!(self.src.js, "let {local_name};");
4755                self.bindgen
4756                    .all_core_exported_funcs
4757                    // TODO(breaking): remove requires_async_porcelain  once support
4758                    // for manual async import specification is removed, as p3 has
4759                    // built-in function async coloring
4760                    .push((core_export_fn.clone(), is_async | requires_async_porcelain));
4761                local_name
4762            }
4763        };
4764
4765        let iface_name = if export_name.is_empty() {
4766            None
4767        } else {
4768            Some(export_name)
4769        };
4770
4771        // Write function preamble (everything up to the `(` in `function (...`)
4772        match func.kind {
4773            FunctionKind::Freestanding => {
4774                uwrite!(self.src.js, "\n{maybe_async}function {local_name}")
4775            }
4776            FunctionKind::Method(_) => {
4777                self.ensure_local_resource_class(local_name.to_string());
4778                let method_name = func.item_name().to_lower_camel_case();
4779
4780                uwrite!(
4781                    self.src.js,
4782                    "\n{local_name}.prototype.{method_name} = {maybe_async}function {}",
4783                    if !is_js_reserved_word(&method_name) {
4784                        method_name.to_string()
4785                    } else {
4786                        format!("${method_name}")
4787                    }
4788                );
4789            }
4790            FunctionKind::Static(_) => {
4791                self.ensure_local_resource_class(local_name.to_string());
4792                let method_name = func.item_name().to_lower_camel_case();
4793                uwrite!(
4794                    self.src.js,
4795                    "\n{local_name}.{method_name} = function {}",
4796                    if !is_js_reserved_word(&method_name) {
4797                        method_name.to_string()
4798                    } else {
4799                        format!("${method_name}")
4800                    }
4801                );
4802            }
4803            FunctionKind::Constructor(_) => {
4804                if self.defined_resource_classes.contains(local_name) {
4805                    panic!(
4806                        "Internal error: Resource constructor must be defined before other methods and statics"
4807                    );
4808                }
4809                uwrite!(
4810                    self.src.js,
4811                    "
4812                    class {local_name} {{
4813                        constructor"
4814                );
4815                self.defined_resource_classes.insert(local_name.to_string());
4816            }
4817            FunctionKind::AsyncFreestanding => {
4818                uwrite!(self.src.js, "\nasync function {local_name}")
4819            }
4820            FunctionKind::AsyncMethod(_) => {
4821                self.ensure_local_resource_class(local_name.to_string());
4822                let method_name = func.item_name().to_lower_camel_case();
4823                let fn_name = if !is_js_reserved_word(&method_name) {
4824                    method_name.to_string()
4825                } else {
4826                    format!("${method_name}")
4827                };
4828                uwrite!(
4829                    self.src.js,
4830                    "\n{local_name}.prototype.{method_name} = async function {fn_name}",
4831                );
4832            }
4833            FunctionKind::AsyncStatic(_) => {
4834                self.ensure_local_resource_class(local_name.to_string());
4835                let method_name = func.item_name().to_lower_camel_case();
4836                let fn_name = if !is_js_reserved_word(&method_name) {
4837                    method_name.to_string()
4838                } else {
4839                    format!("${method_name}")
4840                };
4841                uwrite!(
4842                    self.src.js,
4843                    "\n{local_name}.{method_name} = async function {fn_name}",
4844                );
4845            }
4846        };
4847
4848        // Perform bindgen
4849        self.bindgen(JsFunctionBindgenArgs {
4850            nparams: func.params.len(),
4851            call_type: match func.kind {
4852                FunctionKind::Method(_) => CallType::FirstArgIsThis,
4853                FunctionKind::AsyncMethod(_) => CallType::AsyncFirstArgIsThis,
4854                FunctionKind::Freestanding
4855                | FunctionKind::Static(_)
4856                | FunctionKind::Constructor(_) => CallType::Standard,
4857                FunctionKind::AsyncFreestanding | FunctionKind::AsyncStatic(_) => {
4858                    CallType::AsyncStandard
4859                }
4860            },
4861            iface_name: iface_name.map(|v| v.as_str()),
4862            callee: &callee,
4863            opts: options,
4864            func,
4865            resource_map: export_resource_map,
4866            abi: AbiVariant::GuestExport,
4867            requires_async_porcelain,
4868            is_async,
4869            for_import: false,
4870        });
4871
4872        // End the function
4873        match func.kind {
4874            FunctionKind::AsyncFreestanding | FunctionKind::Freestanding => self.src.js("\n"),
4875            FunctionKind::AsyncMethod(_)
4876            | FunctionKind::AsyncStatic(_)
4877            | FunctionKind::Method(_)
4878            | FunctionKind::Static(_) => self.src.js(";\n"),
4879            FunctionKind::Constructor(_) => self.src.js("\n}\n"),
4880        }
4881    }
4882}
4883
4884#[derive(Default)]
4885pub struct Source {
4886    pub js: source::Source,
4887    pub js_init: source::Source,
4888}
4889
4890impl Source {
4891    pub fn js(&mut self, s: &str) {
4892        self.js.push_str(s);
4893    }
4894    pub fn js_init(&mut self, s: &str) {
4895        self.js_init.push_str(s);
4896    }
4897}
4898
4899/// Compute the semver "compatibility track" for a version string.
4900/// Mirrors wasmtime's `alternate_lookup_key()` logic.
4901///
4902/// Returns the compat key and parsed `Version` on success.
4903///
4904/// Examples (showing just the key):
4905///   "1.2.3"  → Some(("1", ..))     — major > 0, compat within major
4906///   "0.2.10" → Some(("0.2", ..))   — minor > 0, compat within 0.minor
4907///   "0.0.1"  → None                — no semver compat
4908///   "1.0.0-rc.1" → None            — pre-release, no compat
4909fn semver_compat_key(version_str: &str) -> Option<(String, Version)> {
4910    let version = Version::parse(version_str).ok()?;
4911    if !version.pre.is_empty() {
4912        None
4913    } else if version.major != 0 {
4914        Some((format!("{}", version.major), version))
4915    } else if version.minor != 0 {
4916        Some((format!("0.{}", version.minor), version))
4917    } else {
4918        None
4919    }
4920}
4921
4922fn parse_mapping(mapping: &str) -> (String, Option<String>) {
4923    if mapping.len() > 1
4924        && let Some(hash_idx) = mapping[1..].find('#')
4925    {
4926        return (
4927            mapping[0..hash_idx + 1].to_string(),
4928            Some(mapping[hash_idx + 2..].into()),
4929        );
4930    }
4931    (mapping.into(), None)
4932}
4933
4934fn resolve_wildcard_mapping(key: &str, mapping: &str, impt: &str) -> Option<String> {
4935    let idx = key.find('*')?;
4936    let lhs = &key[..idx];
4937    let rhs = &key[idx + 1..];
4938
4939    if !impt.starts_with(lhs) || !impt.ends_with(rhs) {
4940        return None;
4941    }
4942
4943    let matched_len = impt.len() - lhs.len() - rhs.len();
4944    let matched = &impt[lhs.len()..lhs.len() + matched_len];
4945    Some(mapping.replace('*', matched))
4946}
4947
4948/// Same as [`map_import`], except that when `impt` itself has no mapping and
4949/// the import is a labeled import of a named interface (the component model
4950/// `implements` feature 🏷️), a mapping for the implemented interface id is
4951/// consulted as a fallback.
4952fn map_import_with_implements(
4953    map: &Option<HashMap<String, String>>,
4954    impt: &str,
4955    implements: Option<&str>,
4956) -> (String, Option<String>) {
4957    let (specifier, iface_member) = map_import(map, impt);
4958    if specifier == impt
4959        && iface_member.is_none()
4960        && let Some(target) = implements
4961    {
4962        let (mapped, member) = map_import(map, target);
4963        // An unmapped name is returned with just its version stripped
4964        let target_sans_version = match target.find('@') {
4965            Some(version_idx) => &target[0..version_idx],
4966            None => target,
4967        };
4968        if mapped != target_sans_version || member.is_some() {
4969            return (mapped, member);
4970        }
4971    }
4972    (specifier, iface_member)
4973}
4974
4975fn map_import(map: &Option<HashMap<String, String>>, impt: &str) -> (String, Option<String>) {
4976    let impt_sans_version = match impt.find('@') {
4977        Some(version_idx) => &impt[0..version_idx],
4978        None => impt,
4979    };
4980    if let Some(map) = map.as_ref() {
4981        // 1. Exact match (including version)
4982        if let Some(mapping) = map.get(impt) {
4983            return parse_mapping(mapping);
4984        }
4985
4986        // 2. Exact match without version
4987        if let Some(mapping) = map.get(impt_sans_version) {
4988            return parse_mapping(mapping);
4989        }
4990
4991        // Prefer versioned wildcards over unversioned fallbacks.
4992        for (key, mapping) in map {
4993            if !key.contains('@') {
4994                continue;
4995            }
4996            if let Some(mapping) = resolve_wildcard_mapping(key, mapping, impt) {
4997                return parse_mapping(&mapping);
4998            }
4999        }
5000
5001        // Then apply unversioned wildcards to the version-stripped import.
5002        for (key, mapping) in map {
5003            if key.contains('@') {
5004                continue;
5005            }
5006            if let Some(mapping) = resolve_wildcard_mapping(key, mapping, impt_sans_version) {
5007                return parse_mapping(&mapping);
5008            }
5009        }
5010
5011        // If the import has a parseable version and earlier steps didn't match,
5012        // try matching against map entries with compatible versions.
5013        if let Some(at) = impt.find('@') {
5014            let impt_ver_str = &impt[at + 1..];
5015            if let Some((impt_compat, _)) = semver_compat_key(impt_ver_str) {
5016                let mut best_match: Option<(String, Version)> = None;
5017
5018                for (key, mapping) in map {
5019                    let key_at = match key.find('@') {
5020                        Some(at) => at,
5021                        None => continue,
5022                    };
5023                    let key_base = &key[..key_at];
5024                    let key_ver_str = &key[key_at + 1..];
5025
5026                    let (key_compat, key_ver) = match semver_compat_key(key_ver_str) {
5027                        Some(k) => k,
5028                        None => continue,
5029                    };
5030                    if impt_compat != key_compat {
5031                        continue;
5032                    }
5033
5034                    let resolved = if let Some(mapping) =
5035                        resolve_wildcard_mapping(key_base, mapping, impt_sans_version)
5036                    {
5037                        Some(mapping)
5038                    } else if key_base == impt_sans_version {
5039                        Some(mapping.clone())
5040                    } else {
5041                        None
5042                    };
5043
5044                    if let Some(resolved_mapping) = resolved {
5045                        match &best_match {
5046                            Some((_, prev_ver)) if key_ver <= *prev_ver => {}
5047                            _ => {
5048                                best_match = Some((resolved_mapping, key_ver));
5049                            }
5050                        }
5051                    }
5052                }
5053
5054                if let Some((mapping, _)) = best_match {
5055                    return parse_mapping(&mapping);
5056                }
5057            }
5058        }
5059    }
5060    (impt_sans_version.to_string(), None)
5061}
5062
5063pub fn parse_world_key(name: &str) -> Option<(&str, &str, &str)> {
5064    let registry_idx = name.find(':')?;
5065    let ns = &name[0..registry_idx];
5066    match name.rfind('/') {
5067        Some(sep_idx) => {
5068            let end = if let Some(version_idx) = name.rfind('@') {
5069                version_idx
5070            } else {
5071                name.len()
5072            };
5073            Some((
5074                ns,
5075                &name[registry_idx + 1..sep_idx],
5076                &name[sep_idx + 1..end],
5077            ))
5078        }
5079        // interface is a namespace, function is a default export
5080        None => Some((ns, &name[registry_idx + 1..], "")),
5081    }
5082}
5083
5084fn core_file_name(name: &str, idx: u32) -> String {
5085    let i_str = if idx == 0 {
5086        String::from("")
5087    } else {
5088        (idx + 1).to_string()
5089    };
5090    format!("{name}.core{i_str}.wasm")
5091}
5092
5093/// Encode a [`StringEncoding`] as a string that can be used in Javascript
5094fn string_encoding_js_literal(val: &wasmtime_environ::component::StringEncoding) -> &'static str {
5095    match val {
5096        wasmtime_environ::component::StringEncoding::Utf8 => "'utf8'",
5097        wasmtime_environ::component::StringEncoding::Utf16 => "'utf16'",
5098        wasmtime_environ::component::StringEncoding::CompactUtf16 => "'compact-utf16'",
5099    }
5100}
5101
5102/// Generate the javascript that corresponds to a list of lifting functions for a given list of types
5103///
5104/// # Arguments
5105///
5106/// * `instantiator`
5107/// * `types` - Types for which to generate lift functions
5108/// * `extra_resource_map` - Extra resource mapping that do not exist on the `instantiatior` that should be used ad-hoc
5109///
5110pub fn gen_flat_lift_fn_list_js_expr(
5111    instantiator: &mut Instantiator,
5112    types: &[InterfaceType],
5113    extra_resource_map: &Option<&mut ResourceMap>,
5114) -> String {
5115    let mut lift_fns: Vec<String> = Vec::with_capacity(types.len());
5116    for ty in types.iter() {
5117        lift_fns.push(gen_flat_lift_fn_js_expr(
5118            instantiator,
5119            ty,
5120            extra_resource_map,
5121        ));
5122    }
5123    format!("[{}]", lift_fns.join(","))
5124}
5125
5126fn flat_count_js_expr(flat_count: &Option<u8>) -> String {
5127    flat_count
5128        .map(|count| count.to_string())
5129        .unwrap_or_else(|| "null".into())
5130}
5131
5132/// Generate the javascript lifting function for a given type
5133///
5134/// This function will a function object that can be executed with the right
5135/// context in order to perform the lift. For example, running this for bool
5136/// will produce the following:
5137///
5138/// ```
5139/// _liftFlatBool
5140/// ```
5141///
5142/// This is becasue all it takes to lift a flat boolean is to run the _liftFlatBool function intrinsic.
5143///
5144/// The intrinsic it guaranteed to be in scope once execution time because it wlil be used in the relevant branch.
5145///
5146/// # Arguments
5147///
5148/// * `instantiator`
5149/// * `ty` - The type for which to generate a lift function
5150/// * `extra_resource_map` - Extra resource mapping that do not exist on the `instantiatior` that should be used ad-hoc
5151///
5152pub fn gen_flat_lift_fn_js_expr(
5153    instantiator: &mut Instantiator,
5154    ty: &InterfaceType,
5155    extra_resource_map: &Option<&mut ResourceMap>,
5156) -> String {
5157    let component_types = instantiator.types;
5158
5159    match ty {
5160        InterfaceType::Bool => {
5161            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatBool));
5162            Intrinsic::Lift(LiftIntrinsic::LiftFlatBool).name().into()
5163        }
5164
5165        InterfaceType::S8 => {
5166            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS8));
5167            Intrinsic::Lift(LiftIntrinsic::LiftFlatS8).name().into()
5168        }
5169
5170        InterfaceType::U8 => {
5171            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU8));
5172            Intrinsic::Lift(LiftIntrinsic::LiftFlatU8).name().into()
5173        }
5174
5175        InterfaceType::S16 => {
5176            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS16));
5177            Intrinsic::Lift(LiftIntrinsic::LiftFlatS16).name().into()
5178        }
5179
5180        InterfaceType::U16 => {
5181            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU16));
5182            Intrinsic::Lift(LiftIntrinsic::LiftFlatU16).name().into()
5183        }
5184
5185        InterfaceType::S32 => {
5186            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS32));
5187            Intrinsic::Lift(LiftIntrinsic::LiftFlatS32).name().into()
5188        }
5189
5190        InterfaceType::U32 => {
5191            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU32));
5192            Intrinsic::Lift(LiftIntrinsic::LiftFlatU32).name().into()
5193        }
5194
5195        InterfaceType::S64 => {
5196            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatS64));
5197            Intrinsic::Lift(LiftIntrinsic::LiftFlatS64).name().into()
5198        }
5199
5200        InterfaceType::U64 => {
5201            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatU64));
5202            Intrinsic::Lift(LiftIntrinsic::LiftFlatU64).name().into()
5203        }
5204
5205        InterfaceType::Float32 => {
5206            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat32));
5207            Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat32)
5208                .name()
5209                .into()
5210        }
5211
5212        InterfaceType::Float64 => {
5213            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat64));
5214            Intrinsic::Lift(LiftIntrinsic::LiftFlatFloat64)
5215                .name()
5216                .into()
5217        }
5218
5219        InterfaceType::Char => {
5220            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatChar));
5221            Intrinsic::Lift(LiftIntrinsic::LiftFlatChar).name().into()
5222        }
5223
5224        InterfaceType::String => {
5225            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatStringAny));
5226            Intrinsic::Lift(LiftIntrinsic::LiftFlatStringAny)
5227                .name()
5228                .into()
5229        }
5230
5231        InterfaceType::Record(ty_idx) => {
5232            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatRecord));
5233            let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatRecord).name();
5234            let record_ty = &component_types[*ty_idx];
5235            let size32 = record_ty.abi.size32;
5236            let align32 = record_ty.abi.align32;
5237            let mut keys_and_lifts_expr = String::from("[");
5238            // For each field we build a list of [name, liftFn, 32bit alignment]
5239            // so that the record lifting function (which is a higher level function)
5240            // can properly generate a function that lifts the fields.
5241            for f in &record_ty.fields {
5242                let field_abi = component_types.canonical_abi(&f.ty);
5243                let field_size32 = field_abi.size32;
5244                let field_align32 = field_abi.align32;
5245                keys_and_lifts_expr.push_str(&format!(
5246                    "['{}', {}, {}, {}],",
5247                    f.name.to_lower_camel_case(),
5248                    gen_flat_lift_fn_js_expr(instantiator, &f.ty, extra_resource_map),
5249                    field_size32,
5250                    field_align32,
5251                ));
5252            }
5253            keys_and_lifts_expr.push(']');
5254            format!(
5255                "{lift_fn}({{ fieldMetas: {keys_and_lifts_expr}, size32: {size32}, align32: {align32} }})"
5256            )
5257        }
5258
5259        InterfaceType::Variant(ty_idx) => {
5260            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatVariant));
5261            let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatVariant).name();
5262            let variant_ty = &component_types[*ty_idx];
5263            let variant_flat_count = flat_count_js_expr(&variant_ty.abi.flat_count);
5264            let variant_size32 = variant_ty.abi.size32;
5265            let variant_align32 = variant_ty.abi.align32;
5266            let variant_payload_offset32 = variant_ty.info.payload_offset32;
5267
5268            let mut lift_metas_expr = String::from("[");
5269            for (name, maybe_ty) in &variant_ty.cases {
5270                let (lift_fn_js, case_size32, case_align32, case_flat_count) = match maybe_ty {
5271                    Some(ty) => {
5272                        let cabi_info = component_types.canonical_abi(ty);
5273                        (
5274                            gen_flat_lift_fn_js_expr(instantiator, ty, extra_resource_map),
5275                            cabi_info.size32.to_string(),
5276                            cabi_info.align32.to_string(),
5277                            cabi_info
5278                                .flat_count(MAX_FLAT_PARAMS)
5279                                .map(|v| v.to_string())
5280                                .unwrap_or_else(|| "null".into()),
5281                        )
5282                    }
5283                    None => ("null".into(), "0".into(), "0".into(), "0".into()),
5284                };
5285
5286                lift_metas_expr.push_str(&format!(
5287                    "['{name}', {lift_fn_js}, {case_size32}, {case_align32}, {case_flat_count}],",
5288                ));
5289            }
5290            lift_metas_expr.push(']');
5291
5292            format!(
5293                "{lift_fn}({{
5294                     caseMetas: {lift_metas_expr},
5295                     variantSize32: {variant_size32},
5296                     variantAlign32: {variant_align32},
5297                     variantPayloadOffset32: {variant_payload_offset32},
5298                     variantFlatCount: {variant_flat_count},
5299                 }} )"
5300            )
5301        }
5302
5303        InterfaceType::List(ty_idx) => {
5304            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatList));
5305            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatList).name();
5306            let list_ty = &component_types[*ty_idx];
5307            let lift_fn_expr =
5308                gen_flat_lift_fn_js_expr(instantiator, &list_ty.element, extra_resource_map);
5309            let elem_cabi = component_types.canonical_abi(&list_ty.element);
5310            let elem_align32 = elem_cabi.align32;
5311            let elem_size32 = elem_cabi.size32;
5312            let typed_array = js_typed_array_ctor(&list_ty.element).unwrap_or("undefined");
5313            format!(
5314                "{f}({{
5315                     elemLiftFn: {lift_fn_expr},
5316                     elemAlign32: {elem_align32},
5317                     elemSize32: {elem_size32},
5318                     typedArray: {typed_array},
5319                  }})"
5320            )
5321        }
5322
5323        InterfaceType::FixedLengthList(ty_idx) => {
5324            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatList));
5325            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatList).name();
5326            let list_ty = &component_types[*ty_idx];
5327            let list_size32 = list_ty.abi.size32;
5328            let list_align32 = list_ty.abi.align32;
5329            let lift_fn_expr =
5330                gen_flat_lift_fn_js_expr(instantiator, &list_ty.element, extra_resource_map);
5331            let list_len = list_ty.size;
5332            let elem_cabi = component_types.canonical_abi(&list_ty.element);
5333            let elem_align32 = elem_cabi.align32;
5334            let elem_size32 = elem_cabi.size32;
5335            format!(
5336                "{f}({{
5337                     elemLiftFn: {lift_fn_expr},
5338                     elemAlign32: {elem_align32},
5339                     elemSize32: {elem_size32},
5340                     listSize32: {list_size32},
5341                     listAlign32: {list_align32},
5342                     knownLen: {list_len},
5343                 }})"
5344            )
5345        }
5346
5347        InterfaceType::Tuple(ty_idx) => {
5348            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatTuple));
5349            let tuple_ty = &component_types[*ty_idx];
5350            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatTuple).name();
5351            let size_u32 = tuple_ty.abi.size32;
5352            let align_u32 = tuple_ty.abi.align32;
5353
5354            let mut elem_lifts_expr = String::from("[");
5355            for ty in &tuple_ty.types {
5356                let lift_fn_js = gen_flat_lift_fn_js_expr(instantiator, ty, extra_resource_map);
5357                let elem_abi = component_types.canonical_abi(ty);
5358                let elem_size32 = elem_abi.size32;
5359                let elem_align32 = elem_abi.align32;
5360                elem_lifts_expr
5361                    .push_str(&format!("[{lift_fn_js}, {elem_size32}, {elem_align32}],"));
5362            }
5363            elem_lifts_expr.push(']');
5364
5365            format!(
5366                "{f}({{ elemLiftFns: {elem_lifts_expr}, size32: {size_u32}, align32: {align_u32} }})"
5367            )
5368        }
5369
5370        InterfaceType::Flags(ty_idx) => {
5371            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFlags));
5372            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatFlags).name();
5373            let flags_ty = &component_types[*ty_idx];
5374            let size_u32 = flags_ty.abi.size32;
5375            let align_u32 = flags_ty.abi.align32;
5376            let names_expr = format!(
5377                "[{}]",
5378                flags_ty
5379                    .names
5380                    .iter()
5381                    .map(|s| format!("'{}'", s.to_lower_camel_case()))
5382                    .collect::<Vec<_>>()
5383                    .join(",")
5384            );
5385            let num_flags = flags_ty.names.len();
5386            let elem_size = if num_flags <= 8 {
5387                1
5388            } else if num_flags <= 16 {
5389                2
5390            } else {
5391                4
5392            };
5393
5394            format!(
5395                "{f}({{ names: {names_expr}, size32: {size_u32}, align32: {align_u32}, intSizeBytes: {elem_size} }})"
5396            )
5397        }
5398
5399        InterfaceType::Enum(ty_idx) => {
5400            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatEnum));
5401            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatEnum).name();
5402            let enum_ty = &component_types[*ty_idx];
5403            let enum_size32 = enum_ty.abi.size32;
5404            let enum_align32 = enum_ty.abi.align32;
5405            let enum_payload_offset32 = enum_ty.info.payload_offset32;
5406            let enum_flat_count = flat_count_js_expr(&enum_ty.abi.flat_count);
5407
5408            let mut elem_lifts_expr = String::from("[");
5409            for name in &enum_ty.names {
5410                elem_lifts_expr.push_str(&format!(
5411                    "['{name}', null, {enum_size32}, {enum_align32}, {enum_payload_offset32}],"
5412                ));
5413            }
5414            elem_lifts_expr.push(']');
5415
5416            format!(
5417                r#"
5418                  {f}({{
5419                      caseMetas: {elem_lifts_expr},
5420                      variantSize32: {enum_size32},
5421                      variantAlign32: {enum_align32},
5422                      variantPayloadOffset32: {enum_payload_offset32},
5423                      variantFlatCount: {enum_flat_count},
5424                  }})
5425               "#
5426            )
5427        }
5428
5429        InterfaceType::Option(ty_idx) => {
5430            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatOption));
5431            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatOption).name();
5432            let option_ty = &component_types[*ty_idx];
5433            let option_payload_offset32 = option_ty.info.payload_offset32;
5434            let option_align32 = option_ty.abi.align32;
5435            let option_size32 = option_ty.abi.size32;
5436            let option_flat_count = flat_count_js_expr(&option_ty.abi.flat_count);
5437
5438            let some_ty_abi = component_types.canonical_abi(&option_ty.ty);
5439            let some_ty_flat_count = flat_count_js_expr(&some_ty_abi.flat_count);
5440            let some_ty_size32 = some_ty_abi.size32;
5441            let some_ty_align32 = some_ty_abi.align32;
5442            let some_ty_lift_fn_js =
5443                gen_flat_lift_fn_js_expr(instantiator, &option_ty.ty, extra_resource_map);
5444
5445            format!(
5446                r#"
5447                {f}({{
5448                    caseMetas: [
5449                        ['none', null, 0, 0, 0 ],
5450                        ['some', {some_ty_lift_fn_js}, {some_ty_size32}, {some_ty_align32}, {some_ty_flat_count} ],
5451                    ],
5452                    variantSize32: {option_size32},
5453                    variantAlign32: {option_align32},
5454                    variantPayloadOffset32: {option_payload_offset32},
5455                    variantFlatCount: {option_flat_count},
5456                }})
5457                "#
5458            )
5459        }
5460
5461        InterfaceType::Result(ty_idx) => {
5462            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatResult));
5463            let lift_fn = Intrinsic::Lift(LiftIntrinsic::LiftFlatResult).name();
5464            let result_ty = &component_types[*ty_idx];
5465            let result_size32 = result_ty.abi.size32;
5466            let result_align32 = result_ty.abi.align32;
5467            let result_payload_offset32 = result_ty.info.payload_offset32;
5468            let result_flat_count = flat_count_js_expr(&result_ty.abi.flat_count);
5469
5470            let mut cases_and_lifts_expr = String::from("[");
5471            if let Some(ok_ty) = result_ty.ok {
5472                let ok_ty_abi = component_types.canonical_abi(&ok_ty);
5473                let ok_ty_size32 = ok_ty_abi.size32;
5474                let ok_ty_align32 = ok_ty_abi.align32;
5475                let ok_flat_count = flat_count_js_expr(&ok_ty_abi.flat_count);
5476                let ok_ty_lift_fn =
5477                    gen_flat_lift_fn_js_expr(instantiator, &ok_ty, extra_resource_map);
5478                cases_and_lifts_expr.push_str(&format!(
5479                    "['ok', {ok_ty_lift_fn}, {ok_ty_size32}, {ok_ty_align32}, {ok_flat_count}],",
5480                ))
5481            } else {
5482                cases_and_lifts_expr.push_str("['ok', null, 0, 0, 0],");
5483            }
5484
5485            if let Some(err_ty) = &result_ty.err {
5486                let err_ty_abi = component_types.canonical_abi(err_ty);
5487                let err_ty_size32 = err_ty_abi.size32;
5488                let err_ty_align32 = err_ty_abi.align32;
5489                let err_ty_flat_count = flat_count_js_expr(&err_ty_abi.flat_count);
5490                let err_ty_lift_fn =
5491                    gen_flat_lift_fn_js_expr(instantiator, err_ty, extra_resource_map);
5492                cases_and_lifts_expr.push_str(&format!(
5493                    "['err', {err_ty_lift_fn}, {err_ty_size32}, {err_ty_align32}, {err_ty_flat_count}],",
5494                ))
5495            } else {
5496                cases_and_lifts_expr.push_str("['err', null, 0, 0, 0],");
5497            }
5498            cases_and_lifts_expr.push(']');
5499
5500            format!(
5501                r#"
5502                  {lift_fn}({{
5503                      caseMetas: {cases_and_lifts_expr},
5504                      variantSize32: {result_size32},
5505                      variantAlign32: {result_align32},
5506                      variantPayloadOffset32: {result_payload_offset32},
5507                      variantFlatCount: {result_flat_count},
5508                  }})
5509                "#
5510            )
5511        }
5512
5513        InterfaceType::Own(ty_idx) => {
5514            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatOwn));
5515            instantiator.add_intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
5516            instantiator.add_intrinsic(Intrinsic::SymbolResourceHandle);
5517            instantiator.add_intrinsic(Intrinsic::SymbolResourceRep);
5518            instantiator.add_intrinsic(Intrinsic::SymbolDispose);
5519            instantiator.add_intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableRemove));
5520            instantiator.add_intrinsic(Intrinsic::Resource(ResourceIntrinsic::ResourceTableFlag));
5521            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatOwn).name();
5522            let table_ty = &component_types[*ty_idx];
5523            let component_idx = table_ty.unwrap_concrete_instance().as_u32();
5524            let resource_idx = table_ty.unwrap_concrete_ty();
5525
5526            // Attempt to find information about the owned resource
5527            match instantiator.exports_resource_index_types.get(&resource_idx) {
5528                // Type information not found for this resource index
5529                None => format!(
5530                    r#"{f}({{
5531                       componentIdx: {component_idx},
5532                       className: null,
5533                       createResourceFn: () => {{ throw new Error('invalid/missing resource type data'); }},
5534                    }})
5535                "#,
5536                ),
5537
5538                // If we have a resource type def, find more information about it to generate
5539                // the resource creation function
5540                Some(resource_typedef) => {
5541                    // Look in both the resource exports and the provided extra resource map for the resource
5542                    let (resource_class_name, create_resource_fn_js) = match (
5543                        instantiator.resource_exports.get(resource_typedef),
5544                        extra_resource_map
5545                            .as_ref()
5546                            .and_then(|v| v.get(resource_typedef)),
5547                    ) {
5548                        // Resource type information wasn't found
5549                        (None, None) => (
5550                            "null".into(),
5551                            "() => {{ throw new Error('missing resource information'); }}".into(),
5552                        ),
5553
5554                        // Resource type was found in either resource_exports or extra provided resource map
5555                        (Some(ResourceTable { imported, data }), _)
5556                        | (_, Some(ResourceTable { imported, data })) => match data {
5557                            ResourceData::Guest { .. } => {
5558                                unimplemented!(
5559                                    "owned resources created by guests should must have host-side data"
5560                                )
5561                            }
5562                            ResourceData::Host {
5563                                tid,
5564                                rid,
5565                                local_name,
5566                                dtor_name,
5567                            } => {
5568                                let empty_func = JsHelperIntrinsic::EmptyFunc.name();
5569                                let symbol_resource_handle = Intrinsic::SymbolResourceHandle.name();
5570                                let symbol_dispose = Intrinsic::SymbolDispose.name();
5571                                let rsc_table_remove =
5572                                    ResourceIntrinsic::ResourceTableRemove.name();
5573                                let tid = tid.as_u32();
5574                                let rsc_flag = ResourceIntrinsic::ResourceTableFlag.name();
5575
5576                                // Mirrors `Instruction::HandleLift` in `function_bindgen.rs`:
5577                                let create_resource_fn_js = if *imported {
5578                                    let symbol_resource_rep = Intrinsic::SymbolResourceRep.name();
5579                                    let rid = rid.as_u32();
5580                                    format!(
5581                                        r#"
5582                                      (handle) => {{
5583                                          const rep = handleTable{tid}[(handle << 1) + 1] & ~{rsc_flag};
5584                                          let resourceObj = captureTable{rid}.get(rep);
5585                                          if (!resourceObj) {{
5586                                              resourceObj = Object.create({local_name}.prototype);
5587                                              Object.defineProperty(resourceObj, {symbol_resource_handle}, {{ writable: true, value: handle }});
5588                                              Object.defineProperty(resourceObj, {symbol_resource_rep}, {{ writable: true, value: rep }});
5589                                          }} else {{
5590                                              captureTable{rid}.delete(rep);
5591                                          }}
5592                                          {rsc_table_remove}(handleTable{tid}, handle);
5593                                          return resourceObj;
5594                                      }}
5595                                     "#
5596                                    )
5597                                } else {
5598                                    let dtor_setup_js = dtor_name
5599                                    .as_ref()
5600                                    .map(|dtor|
5601                                         format!(
5602                                             r#"
5603                                               Object.defineProperty(
5604                                                   resourceObj,
5605                                                   {symbol_dispose},
5606                                                   {{
5607                                                       writable: true,
5608                                                       value: function() {{
5609                                                           finalizationRegistry{tid}.unregister(resourceObj);
5610                                                           {rsc_table_remove}(handleTable{tid}, handle);
5611                                                           resourceObj[{symbol_dispose}] = {empty_func};
5612                                                           resourceObj[{symbol_resource_handle}] = undefined;
5613                                                           {dtor}(handleTable{tid}[(handle << 1) + 1] & ~{rsc_flag});
5614                                                       }}
5615                                                  }}
5616                                              );
5617                                        "#
5618                                         )
5619                                    ).unwrap_or_default();
5620
5621                                    format!(
5622                                        r#"
5623                                      (handle) => {{
5624                                          const resourceObj = Object.create({local_name}.prototype);
5625                                          Object.defineProperty(resourceObj, {symbol_resource_handle}, {{
5626                                              writable: true,
5627                                              value: handle,
5628                                          }});
5629                                          finalizationRegistry{tid}.register(resourceObj, handle, resourceObj);
5630                                          {dtor_setup_js}
5631                                          return resourceObj;
5632                                      }}
5633                                     "#
5634                                    )
5635                                };
5636
5637                                (local_name.to_string(), create_resource_fn_js)
5638                            }
5639                        },
5640                    };
5641
5642                    format!(
5643                        r#"{f}({{
5644                       componentIdx: {component_idx},
5645                       className: {resource_class_name},
5646                       createResourceFn: {create_resource_fn_js},
5647                    }})
5648                "#,
5649                    )
5650                }
5651            }
5652        }
5653
5654        InterfaceType::Borrow(ty_idx) => {
5655            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatBorrow));
5656            let table_idx = ty_idx.as_u32();
5657            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatBorrow).name();
5658            format!("{f}.bind(null, {table_idx})")
5659        }
5660
5661        InterfaceType::Future(ty_idx) => {
5662            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatFuture));
5663            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatFuture).name();
5664            let table_idx = ty_idx.as_u32();
5665            let table_ty = &component_types[*ty_idx];
5666            let component_idx = table_ty.instance.as_u32();
5667            format!("{f}({{ futureTableIdx: {table_idx}, componentIdx: {component_idx} }})")
5668        }
5669
5670        InterfaceType::Stream(ty_idx) => {
5671            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatStream));
5672            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatStream).name();
5673            let table_idx = ty_idx.as_u32();
5674            let table_ty = &component_types[*ty_idx];
5675            let component_idx = table_ty.instance.as_u32();
5676            format!("{f}({{ streamTableIdx: {table_idx}, componentIdx: {component_idx} }})")
5677        }
5678
5679        InterfaceType::ErrorContext(ty_idx) => {
5680            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatErrorContext));
5681            let table_idx = ty_idx.as_u32();
5682            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatErrorContext).name();
5683            format!("{f}.bind(null, {table_idx})")
5684        }
5685
5686        InterfaceType::Map(ty_idx) => {
5687            instantiator.add_intrinsic(Intrinsic::Lift(LiftIntrinsic::LiftFlatMap));
5688            let f = Intrinsic::Lift(LiftIntrinsic::LiftFlatMap).name();
5689            let map_ty = &component_types[*ty_idx];
5690            let key_lift = gen_flat_lift_fn_js_expr(instantiator, &map_ty.key, extra_resource_map);
5691            let value_lift =
5692                gen_flat_lift_fn_js_expr(instantiator, &map_ty.value, extra_resource_map);
5693            let entry_size32 = map_ty.entry_abi.size32;
5694            let entry_align32 = map_ty.entry_abi.align32;
5695            let value_offset32 = map_ty.value_offset32;
5696            format!(
5697                "{f}({{
5698                     keyLiftFn: {key_lift},
5699                     valueLiftFn: {value_lift},
5700                     entrySize32: {entry_size32},
5701                     entryAlign32: {entry_align32},
5702                     valueOffset32: {value_offset32},
5703                 }})"
5704            )
5705        }
5706    }
5707}
5708
5709fn js_typed_array_ctor(ty: &InterfaceType) -> Option<&'static str> {
5710    match ty {
5711        InterfaceType::U8 => Some("Uint8Array"),
5712        InterfaceType::S8 => Some("Int8Array"),
5713        InterfaceType::U16 => Some("Uint16Array"),
5714        InterfaceType::S16 => Some("Int16Array"),
5715        InterfaceType::U32 => Some("Uint32Array"),
5716        InterfaceType::S32 => Some("Int32Array"),
5717        InterfaceType::U64 => Some("BigUint64Array"),
5718        InterfaceType::S64 => Some("BigInt64Array"),
5719        InterfaceType::Float32 => Some("Float32Array"),
5720        InterfaceType::Float64 => Some("Float64Array"),
5721        _ => None,
5722    }
5723}
5724
5725/// Generate the javascript that corresponds to a list of lowering functions for a given list of types
5726///
5727/// # Arguments
5728///
5729/// * `instantiator`
5730/// * `types` - Types for which to generate lift functions
5731/// * `extra_resource_map` - Extra resource mapping that do not exist on the `instantiatior` that should be used ad-hoc
5732///
5733pub fn gen_flat_lower_fn_list_js_expr(
5734    instantiator: &mut Instantiator,
5735    types: &[InterfaceType],
5736    extra_import_map: &Option<&mut ResourceMap>,
5737) -> String {
5738    let mut lower_fns: Vec<String> = Vec::with_capacity(types.len());
5739    for ty in types.iter() {
5740        lower_fns.push(gen_flat_lower_fn_js_expr(
5741            instantiator,
5742            ty,
5743            extra_import_map,
5744        ));
5745    }
5746    format!("[{}]", lower_fns.join(","))
5747}
5748
5749/// Generate the javascript lowering function for a given type
5750///
5751/// This function will a function object that can be executed with the right
5752/// context in order to perform the lower. For example, running this for bool
5753/// will produce the following:
5754///
5755/// ```
5756/// _lowerFlatBool
5757/// ```
5758///
5759/// This is becasue all it takes to lower a flat boolean is to run the _lowerFlatBool function intrinsic.
5760///
5761/// The intrinsic it guaranteed to be in scope once execution time because it wlil be used in the relevant branch.
5762///
5763/// # Arguments
5764///
5765/// * `instantiator`
5766/// * `ty` - type for which to generate a lower function
5767/// * `extra_resource_map` - Extra resource mapping that do not exist on the `instantiatior` that should be used ad-hoc
5768///
5769pub fn gen_flat_lower_fn_js_expr(
5770    instantiator: &mut Instantiator,
5771    ty: &InterfaceType,
5772    extra_resource_map: &Option<&mut ResourceMap>,
5773) -> String {
5774    let component_types = instantiator.types;
5775    match ty {
5776        InterfaceType::Bool => {
5777            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatBool));
5778            Intrinsic::Lower(LowerIntrinsic::LowerFlatBool)
5779                .name()
5780                .into()
5781        }
5782
5783        InterfaceType::S8 => {
5784            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS8));
5785            Intrinsic::Lower(LowerIntrinsic::LowerFlatS8).name().into()
5786        }
5787
5788        InterfaceType::U8 => {
5789            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU8));
5790            Intrinsic::Lower(LowerIntrinsic::LowerFlatU8).name().into()
5791        }
5792
5793        InterfaceType::S16 => {
5794            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS16));
5795            Intrinsic::Lower(LowerIntrinsic::LowerFlatS16).name().into()
5796        }
5797
5798        InterfaceType::U16 => {
5799            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU16));
5800            Intrinsic::Lower(LowerIntrinsic::LowerFlatU16).name().into()
5801        }
5802
5803        InterfaceType::S32 => {
5804            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS32));
5805            Intrinsic::Lower(LowerIntrinsic::LowerFlatS32).name().into()
5806        }
5807
5808        InterfaceType::U32 => {
5809            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU32));
5810            Intrinsic::Lower(LowerIntrinsic::LowerFlatU32).name().into()
5811        }
5812
5813        InterfaceType::S64 => {
5814            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatS64));
5815            Intrinsic::Lower(LowerIntrinsic::LowerFlatS64).name().into()
5816        }
5817
5818        InterfaceType::U64 => {
5819            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatU64));
5820            Intrinsic::Lower(LowerIntrinsic::LowerFlatU64).name().into()
5821        }
5822
5823        InterfaceType::Float32 => {
5824            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat32));
5825            Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat32)
5826                .name()
5827                .into()
5828        }
5829
5830        InterfaceType::Float64 => {
5831            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat64));
5832            Intrinsic::Lower(LowerIntrinsic::LowerFlatFloat64)
5833                .name()
5834                .into()
5835        }
5836
5837        InterfaceType::Char => {
5838            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatChar));
5839            Intrinsic::Lower(LowerIntrinsic::LowerFlatChar)
5840                .name()
5841                .into()
5842        }
5843
5844        InterfaceType::String => {
5845            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatStringAny));
5846            Intrinsic::Lower(LowerIntrinsic::LowerFlatStringAny)
5847                .name()
5848                .into()
5849        }
5850
5851        InterfaceType::Record(ty_idx) => {
5852            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatRecord));
5853            let lower_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatRecord).name();
5854            let record_ty = &component_types[*ty_idx];
5855            let size32 = record_ty.abi.size32;
5856            let align32 = record_ty.abi.align32;
5857            let mut keys_and_lowers_expr = String::from("[");
5858            for f in &record_ty.fields {
5859                // For each field we build a list of [name, lowerFn, 32bit alignment]
5860                // so that the record lowering function (which is a higher level function)
5861                // can properly generate a function that lowers the fields.
5862                let field_abi = component_types.canonical_abi(&f.ty);
5863                let field_size32 = field_abi.size32;
5864                let field_align32 = field_abi.align32;
5865                keys_and_lowers_expr.push_str(&format!(
5866                    "['{}', {}, {}, {} ],",
5867                    f.name.to_lower_camel_case(),
5868                    gen_flat_lower_fn_js_expr(instantiator, &f.ty, &None),
5869                    field_size32,
5870                    field_align32,
5871                ));
5872            }
5873            keys_and_lowers_expr.push(']');
5874            format!(
5875                "{lower_fn}({{ fieldMetas: {keys_and_lowers_expr}, size32: {size32}, align32: {align32} }})"
5876            )
5877        }
5878
5879        InterfaceType::Variant(ty_idx) => {
5880            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatVariant));
5881            let lower_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatVariant).name();
5882            let variant_ty = &component_types[*ty_idx];
5883            let variant_flat_count = flat_count_js_expr(&variant_ty.abi.flat_count);
5884            let size32 = variant_ty.abi.size32;
5885            let align32 = variant_ty.abi.align32;
5886            let payload_offset32 = variant_ty.info.payload_offset32;
5887
5888            let mut lower_metas_expr = String::from("[");
5889            for (name, maybe_ty) in variant_ty.cases.iter() {
5890                let (case_size32, case_align32, case_flat_count) = if let Some(iface_ty) = maybe_ty
5891                {
5892                    let cabi_info = component_types.canonical_abi(iface_ty);
5893                    (
5894                        cabi_info.size32.to_string(),
5895                        cabi_info.align32.to_string(),
5896                        cabi_info
5897                            .flat_count(MAX_FLAT_PARAMS)
5898                            .map(|v| v.to_string())
5899                            .unwrap_or_else(|| "null".into()),
5900                    )
5901                } else {
5902                    ("0".into(), "0".into(), "0".into())
5903                };
5904
5905                lower_metas_expr.push_str(&format!(
5906                    "[ '{name}', {}, {case_size32}, {case_align32}, {case_flat_count} ],",
5907                    maybe_ty
5908                        .map(|ty| gen_flat_lower_fn_js_expr(instantiator, &ty, &None))
5909                        .unwrap_or_else(|| "null".into()),
5910                ));
5911            }
5912            lower_metas_expr.push(']');
5913
5914            format!(
5915                "{lower_fn}({{
5916                     caseMetas: {lower_metas_expr},
5917                     variantSize32: {size32},
5918                     variantAlign32: {align32},
5919                     variantPayloadOffset32: {payload_offset32},
5920                     variantFlatCount: {variant_flat_count},
5921                 }} )"
5922            )
5923        }
5924
5925        InterfaceType::List(ty_idx) => {
5926            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatList));
5927            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatList).name();
5928            let list_ty = &component_types[*ty_idx];
5929            let elem_ty_lower_expr =
5930                gen_flat_lower_fn_js_expr(instantiator, &list_ty.element, extra_resource_map);
5931            let elem_cabi = component_types.canonical_abi(&list_ty.element);
5932            let elem_align32 = elem_cabi.align32;
5933            let elem_size32 = elem_cabi.size32;
5934
5935            format!(
5936                "{f}({{
5937                elemLowerFn: {elem_ty_lower_expr},
5938                elemSize32: {elem_size32},
5939                elemAlign32: {elem_align32},
5940            }})"
5941            )
5942        }
5943
5944        InterfaceType::FixedLengthList(ty_idx) => {
5945            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatList));
5946            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatList).name();
5947            let list_ty = &component_types[*ty_idx];
5948            let elem_ty_lower_expr =
5949                gen_flat_lower_fn_js_expr(instantiator, &list_ty.element, extra_resource_map);
5950            let list_len = list_ty.size;
5951            let list_align32 = list_ty.abi.size32;
5952            let list_size32 = list_ty.abi.size32;
5953            let elem_cabi = component_types.canonical_abi(&list_ty.element);
5954            let elem_align32 = elem_cabi.align32;
5955            let elem_size32 = elem_cabi.size32;
5956
5957            format!(
5958                r#"{f}({{
5959                       elemLowerFn: {elem_ty_lower_expr},
5960                       elemAlign32: {elem_align32},
5961                       elemSize32: {elem_size32},
5962                       align32: {list_align32},
5963                       size32: {list_size32},
5964                       knownLen: {list_len},
5965                   }})"#
5966            )
5967        }
5968
5969        InterfaceType::Tuple(ty_idx) => {
5970            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatTuple));
5971            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatTuple).name();
5972            let tuple_ty = &component_types[*ty_idx];
5973            let size_u32 = tuple_ty.abi.size32;
5974            let align_u32 = tuple_ty.abi.align32;
5975
5976            let mut elem_lowers_expr = String::from("[");
5977            for ty in &tuple_ty.types {
5978                let lower_fn_js = gen_flat_lower_fn_js_expr(instantiator, ty, extra_resource_map);
5979                let elem_abi = component_types.canonical_abi(ty);
5980                let elem_size32 = elem_abi.size32;
5981                let elem_align32 = elem_abi.align32;
5982                elem_lowers_expr
5983                    .push_str(&format!("[{lower_fn_js}, {elem_size32}, {elem_align32}],"));
5984            }
5985            elem_lowers_expr.push(']');
5986
5987            format!(
5988                "{f}({{ elemLowerMetas: {elem_lowers_expr}, size32: {size_u32}, align32: {align_u32} }})"
5989            )
5990        }
5991
5992        InterfaceType::Flags(ty_idx) => {
5993            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFlags));
5994            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatFlags).name();
5995            let flags_ty = &component_types[*ty_idx];
5996            let size32 = flags_ty.abi.size32;
5997            let align32 = flags_ty.abi.align32;
5998            let names_list_js = format!(
5999                "[{}]",
6000                flags_ty
6001                    .names
6002                    .iter()
6003                    .map(|s| format!("'{}'", s.to_lower_camel_case()))
6004                    .collect::<Vec<_>>()
6005                    .join(",")
6006            );
6007            let num_flags = flags_ty.names.len();
6008            let elem_size = if num_flags <= 8 {
6009                1
6010            } else if num_flags <= 16 {
6011                2
6012            } else {
6013                4
6014            };
6015
6016            format!(
6017                "{f}({{ names: {names_list_js}, size32: {size32}, align32: {align32}, intSizeBytes: {elem_size} }})"
6018            )
6019        }
6020
6021        InterfaceType::Enum(ty_idx) => {
6022            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatEnum));
6023            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatEnum).name();
6024            let enum_ty = &component_types[*ty_idx];
6025            let enum_size32 = enum_ty.abi.size32;
6026            let enum_align32 = enum_ty.abi.align32;
6027            let enum_flat_count = flat_count_js_expr(&enum_ty.abi.flat_count);
6028            let enum_payload_offset32 = enum_ty.info.payload_offset32;
6029
6030            let mut elem_lowers_expr = String::from("[");
6031            for name in &enum_ty.names {
6032                elem_lowers_expr.push_str(&format!(
6033                    "['{name}', null, {enum_size32}, {enum_align32}, {enum_payload_offset32}],"
6034                ));
6035            }
6036            elem_lowers_expr.push(']');
6037
6038            format!(
6039                r#"
6040                  {f}({{
6041                       caseMetas: {elem_lowers_expr},
6042                       variantSize32: {enum_size32},
6043                       variantAlign32: {enum_align32},
6044                       variantPayloadOffset32: {enum_payload_offset32},
6045                       variantFlatCount: {enum_flat_count},
6046                  }})
6047                "#
6048            )
6049        }
6050
6051        InterfaceType::Option(ty_idx) => {
6052            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatOption));
6053            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatOption).name();
6054            let option_ty = &component_types[*ty_idx];
6055            let option_size32 = option_ty.abi.size32;
6056            let option_align32 = option_ty.abi.align32;
6057            let option_payload_offset32 = option_ty.info.payload_offset32;
6058            let option_flat_count = flat_count_js_expr(&option_ty.abi.flat_count);
6059
6060            let some_ty_abi = component_types.canonical_abi(&option_ty.ty);
6061            let some_ty_flat_count = flat_count_js_expr(&some_ty_abi.flat_count);
6062            let some_ty_size32 = some_ty_abi.size32;
6063            let some_ty_align32 = some_ty_abi.align32;
6064            let some_ty_lower_fn_js =
6065                gen_flat_lower_fn_js_expr(instantiator, &option_ty.ty, extra_resource_map);
6066
6067            format!(
6068                r#"
6069                  {f}({{
6070                       caseMetas: [
6071                           [ 'none', null, 0, 0, 0 ],
6072                           [ 'some', {some_ty_lower_fn_js}, {some_ty_size32}, {some_ty_align32}, {some_ty_flat_count}],
6073                       ],
6074                       variantSize32: {option_size32},
6075                       variantAlign32: {option_align32},
6076                       variantPayloadOffset32: {option_payload_offset32},
6077                       variantFlatCount: {option_flat_count},
6078                   }})
6079                "#
6080            )
6081        }
6082
6083        InterfaceType::Result(ty_idx) => {
6084            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatResult));
6085            let lower_fn = Intrinsic::Lower(LowerIntrinsic::LowerFlatResult).name();
6086            let result_ty = &component_types[*ty_idx];
6087            let result_size32 = result_ty.abi.size32;
6088            let result_align32 = result_ty.abi.align32;
6089            let result_payload_offset32 = result_ty.info.payload_offset32;
6090            let result_flat_count = flat_count_js_expr(&result_ty.abi.flat_count);
6091
6092            let ok_lower_fn_js = result_ty
6093                .ok
6094                .map(|ty| gen_flat_lower_fn_js_expr(instantiator, &ty, extra_resource_map))
6095                .unwrap_or_else(|| "null".into());
6096            let err_lower_fn_js = result_ty
6097                .err
6098                .map(|ty| gen_flat_lower_fn_js_expr(instantiator, &ty, extra_resource_map))
6099                .unwrap_or_else(|| "null".into());
6100
6101            format!(
6102                r#"
6103                   {lower_fn}({{
6104                       caseMetas: [
6105                           [ 'ok', {ok_lower_fn_js}, {result_size32}, {result_align32}, {result_payload_offset32} ],
6106                           [ 'err', {err_lower_fn_js}, {result_size32}, {result_align32}, {result_payload_offset32} ],
6107                       ],
6108                       variantSize32: {result_size32},
6109                       variantAlign32: {result_align32},
6110                       variantPayloadOffset32: {result_payload_offset32},
6111                       variantFlatCount: {result_flat_count},
6112                   }})
6113                "#
6114            )
6115        }
6116
6117        InterfaceType::Own(ty_idx) => {
6118            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatOwn));
6119            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatOwn).name();
6120            let resource_table_ty = &component_types[*ty_idx];
6121            let component_idx = resource_table_ty.unwrap_concrete_instance().as_u32();
6122            let resource_idx = resource_table_ty.unwrap_concrete_ty();
6123
6124            // Retrieve resource information for the given resource, looking
6125            // in both the extra resource map and the instantiator's dedicated resource-to-imports/
6126            // exports maps.
6127            let (_, ResourceTable { imported, data }) = match (
6128                instantiator.imports_resource_index_types.get(&resource_idx),
6129                instantiator.exports_resource_index_types.get(&resource_idx),
6130            ) {
6131                (Some(import_ty_id), _) => {
6132                    let ty = crate::dealias(instantiator.resolve, *import_ty_id);
6133                    let maybe_resource_table =
6134                        instantiator.resource_imports.get(&ty).or(extra_resource_map
6135                            .as_ref()
6136                            .and_then(|m| m.get(import_ty_id)));
6137                    (
6138                        ty,
6139                        maybe_resource_table.expect("missing imported resource table information"),
6140                    )
6141                }
6142                (_, Some(export_ty_id)) => {
6143                    let ty = crate::dealias(instantiator.resolve, *export_ty_id);
6144                    let maybe_resource_table =
6145                        instantiator.resource_exports.get(&ty).or(extra_resource_map
6146                            .as_ref()
6147                            .and_then(|m| m.get(export_ty_id)));
6148                    (
6149                        ty,
6150                        maybe_resource_table.expect("missing exported resource table information"),
6151                    )
6152                }
6153
6154                // If resource was not found in the index type map at all, we're missing resource metadata.
6155                (None, None) => {
6156                    return format!(
6157                        "{f}({{
6158                             componentIdx: {component_idx},
6159                             lowerFn: () => {{ throw new Error('missing/invalid resource metadata'); }}
6160                         }})"
6161                    );
6162                }
6163            };
6164
6165            // Build the function to create the resource, depending on how it was provided
6166            let lower_fn_js = match data {
6167                // If the resource was provided by the host, build the function to create it.
6168                ResourceData::Host {
6169                    tid,
6170                    rid,
6171                    local_name,
6172                    ..
6173                } => {
6174                    let tid = tid.as_u32();
6175                    let rid = rid.as_u32();
6176                    let symbol_resource_rep =
6177                        instantiator.bindgen.intrinsic(Intrinsic::SymbolResourceRep);
6178                    let symbol_resource_handle = instantiator
6179                        .bindgen
6180                        .intrinsic(Intrinsic::SymbolResourceHandle);
6181                    let symbol_dispose = instantiator.bindgen.intrinsic(Intrinsic::SymbolDispose);
6182
6183                    if *imported {
6184                        // If imported (and from the host), we must ensure that the incoming object is of the right
6185                        // instance, then add it to the capture table w/ the right resource ID,
6186                        let create_own_fn = instantiator.bindgen.intrinsic(Intrinsic::Resource(
6187                            ResourceIntrinsic::ResourceTableCreateOwn,
6188                        ));
6189                        format!(
6190                            r#"
6191                              function lowerImportedOwnedHost_{local_name}(obj) {{
6192                                  if (!(obj instanceof {local_name})) {{
6193                                      throw new TypeError('Resource error: Not a valid \"{local_name}\" resource.');
6194                                  }}
6195                                  let handle = obj[{symbol_resource_handle}];
6196                                  if (!handle) {{
6197                                    const rep = obj[{symbol_resource_rep}] || ++captureCnt{rid};
6198                                    captureTable{rid}.set(rep, obj);
6199                                    handle = {create_own_fn}(handleTable{tid}, rep);
6200                                  }}
6201                                  return handle;
6202                              }}
6203                            "#
6204                        )
6205                    } else {
6206                        // If the resource was not imported (and came from the host), it comes from the component receiving it,
6207                        // and the object should already have a handle associated inside of it (the component must have created it).
6208                        //
6209                        // We disconnect the external connections for dispose and remove the external
6210                        // facing resource handle that was added when lifted out.
6211                        let empty_func = instantiator
6212                            .bindgen
6213                            .intrinsic(Intrinsic::JsHelper(JsHelperIntrinsic::EmptyFunc));
6214                        format!(
6215                            r#"
6216                               function lowerExportedOwnedHost_{local_name}(obj) {{
6217                                   let handle = obj[{symbol_resource_handle}];
6218                                   if (!handle) {{
6219                                       throw new TypeError('Resource error: Not a valid \"{local_name}\" resource.');
6220                                   }}
6221                                   finalizationRegistry{tid}.unregister(obj);
6222                                   obj[{symbol_dispose}] = {empty_func};
6223                                   obj[{symbol_resource_handle}] = undefined;
6224                                   return handle;
6225                               }}
6226                        "#
6227                        )
6228                    }
6229                }
6230
6231                // If the resource was provided by the guest, build the function to create it.
6232                ResourceData::Guest {
6233                    resource_name,
6234                    prefix,
6235                    extra,
6236                } => {
6237                    assert!(
6238                        extra.is_none(),
6239                        "plain resource handles do not carry extra data"
6240                    );
6241
6242                    let upper_camel = resource_name.to_upper_camel_case();
6243                    let lower_camel = resource_name.to_lower_camel_case();
6244                    let prefix = prefix.as_deref().unwrap_or("");
6245
6246                    if *imported {
6247                        // If we get a resource that is provided by the host, then
6248                        // it should already have an external-facing resource handle on it.
6249                        let symbol_resource_handle = instantiator
6250                            .bindgen
6251                            .intrinsic(Intrinsic::SymbolResourceHandle);
6252                        format!(
6253                            r#"
6254                              function lowerImportedOwnedGuest_{upper_camel}(obj) {{
6255                                  const handle = obj[{symbol_resource_handle}];
6256                                  finalizationRegistry_import${prefix}{lower_camel}.unregister(obj);
6257                                  return handle;
6258                              }}
6259                            "#
6260                        )
6261                    } else {
6262                        // If we get a resource that was exported by the guest and is being lowered in,
6263                        // we can check that the object is of the right kidn of instance, and
6264                        // create rep for it if one does not already exist.
6265                        let symbol_resource_handle = instantiator
6266                            .bindgen
6267                            .intrinsic(Intrinsic::SymbolResourceHandle);
6268                        format!(
6269                            r#"
6270                              function lowerExportedOwnedGuest_{upper_camel}(obj) {{
6271                                  if (!(obj instanceof {upper_camel})) {{
6272                                    throw new TypeError('Resource error: Not a valid \"{upper_camel}\" resource.');
6273                                  }}
6274                                  let handle = obj[{symbol_resource_handle}];
6275                                  if (handle === undefined) {{
6276                                      const localRep = repCnt++;
6277                                      repTable.set(localRep, {{ rep: obj, own: true }});
6278                                      handle = $resource_{prefix}new${lower_camel}(localRep);
6279                                      obj[{symbol_resource_handle}] = handle;
6280                                      finalizationRegistry_export${prefix}{lower_camel}.register(obj, handle, obj);
6281                                  }}
6282                                  return handle;
6283                              }}
6284                            "#
6285                        )
6286                    }
6287                }
6288            };
6289
6290            format!(
6291                "{f}({{
6292                     componentIdx: {component_idx},
6293                     lowerFn: {lower_fn_js},
6294                 }})"
6295            )
6296        }
6297
6298        InterfaceType::Borrow(ty_idx) => {
6299            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatBorrow));
6300            let table_idx = ty_idx.as_u32();
6301            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatBorrow).name();
6302            format!("{f}.bind(null, {table_idx})")
6303        }
6304
6305        InterfaceType::Future(ty_idx) => {
6306            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatFuture));
6307            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatFuture).name();
6308            let table_idx = ty_idx.as_u32();
6309            let table_ty = &component_types[*ty_idx];
6310            let component_idx = table_ty.instance.as_u32();
6311            let future_ty_idx = table_ty.ty;
6312            let future_ty = &component_types[future_ty_idx];
6313            let payload = future_ty.payload;
6314            let payload_ty_name_js = future_ty
6315                .payload
6316                .map(|iface_ty| format!("'{iface_ty:?}'"))
6317                .unwrap_or_else(|| "null".into());
6318
6319            // Gather element metadata
6320            let (
6321                payload_size32,
6322                payload_align32,
6323                payload_flat_count_js,
6324                payload_lift_fn_js,
6325                payload_lower_fn_js,
6326                is_borrowed,
6327                is_none_type,
6328                is_numeric_type,
6329                is_async_value,
6330            ) = match payload {
6331                None => (
6332                    0,
6333                    0,
6334                    "0".into(),
6335                    "() => {{ throw new Error('empty future payload'); }}".into(),
6336                    "() => {{ throw new Error('empty future payload'); }}".into(),
6337                    false,
6338                    true,
6339                    false,
6340                    false,
6341                ),
6342                Some(payload_ty) => {
6343                    let cabi = instantiator.types.canonical_abi(&payload_ty);
6344                    (
6345                        cabi.size32,
6346                        cabi.align32,
6347                        cabi.flat_count
6348                            .map(|v| format!("{v}"))
6349                            .unwrap_or_else(|| "null".into()),
6350                        gen_flat_lift_fn_js_expr(instantiator, &payload_ty, extra_resource_map),
6351                        gen_flat_lower_fn_js_expr(instantiator, &payload_ty, extra_resource_map),
6352                        matches!(payload_ty, InterfaceType::Borrow(_)),
6353                        false,
6354                        matches!(
6355                            payload_ty,
6356                            InterfaceType::U8
6357                                | InterfaceType::U16
6358                                | InterfaceType::U32
6359                                | InterfaceType::U64
6360                                | InterfaceType::S8
6361                                | InterfaceType::S16
6362                                | InterfaceType::S32
6363                                | InterfaceType::S64
6364                                | InterfaceType::Float32
6365                                | InterfaceType::Float64
6366                        ),
6367                        matches!(
6368                            payload_ty,
6369                            InterfaceType::Stream(_) | InterfaceType::Future(_)
6370                        ),
6371                    )
6372                }
6373            };
6374
6375            // Determine the level of future nesting
6376            let mut future_nesting_level = 0;
6377            let mut payload_ty = future_ty.payload;
6378            while let Some(InterfaceType::Future(inner_ty)) = payload_ty {
6379                future_nesting_level += 1;
6380                payload_ty = component_types[component_types[inner_ty].ty].payload;
6381            }
6382
6383            format!(
6384                r#"{f}({{
6385                       futureTableIdx: {table_idx},
6386                       futureNestingLevel: {future_nesting_level},
6387                       componentIdx: {component_idx},
6388                       elemMeta: {{
6389                           liftFn: {payload_lift_fn_js},
6390                           lowerFn: {payload_lower_fn_js},
6391                           payloadTypeName: {payload_ty_name_js},
6392                           isNone: {is_none_type},
6393                           isNumeric: {is_numeric_type},
6394                           isBorrowed: {is_borrowed},
6395                           isAsyncValue: {is_async_value},
6396                           flatCount: {payload_flat_count_js},
6397                           align32: {payload_align32},
6398                           size32: {payload_size32},
6399                       }},
6400                   }})
6401                "#
6402            )
6403        }
6404
6405        InterfaceType::Stream(ty_idx) => {
6406            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatStream));
6407            let table_idx = ty_idx.as_u32();
6408            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatStream).name();
6409            let table_ty = &component_types[*ty_idx];
6410            let component_idx = table_ty.instance.as_u32();
6411            let stream_ty_idx = table_ty.ty;
6412            let stream_ty = &component_types[stream_ty_idx];
6413            let payload = stream_ty.payload;
6414            let payload_ty_name_js = stream_ty
6415                .payload
6416                .map(|iface_ty| format!("'{iface_ty:?}'"))
6417                .unwrap_or_else(|| "null".into());
6418
6419            // TODO(fix): payload u8 should be special cased here
6420
6421            let (
6422                payload_size32,
6423                payload_align32,
6424                payload_flat_count_js,
6425                payload_lift_fn_js,
6426                payload_lower_fn_js,
6427                is_borrowed,
6428                is_none_type,
6429                is_numeric_type,
6430                is_async_value,
6431                typed_array_js,
6432            ) = match payload {
6433                None => (
6434                    0,
6435                    0,
6436                    "0".into(),
6437                    "() => {{ throw new Error('empty stream payload'); }}".into(),
6438                    "() => {{ throw new Error('empty stream payload'); }}".into(),
6439                    false,
6440                    true,
6441                    false,
6442                    false,
6443                    "undefined",
6444                ),
6445                Some(payload_ty) => {
6446                    let cabi = instantiator.types.canonical_abi(&payload_ty);
6447                    (
6448                        cabi.size32,
6449                        cabi.align32,
6450                        cabi.flat_count
6451                            .map(|v| format!("{v}"))
6452                            .unwrap_or_else(|| "null".into()),
6453                        gen_flat_lift_fn_js_expr(instantiator, &payload_ty, extra_resource_map),
6454                        gen_flat_lower_fn_js_expr(instantiator, &payload_ty, extra_resource_map),
6455                        matches!(payload_ty, InterfaceType::Borrow(_)),
6456                        false,
6457                        matches!(
6458                            payload_ty,
6459                            InterfaceType::U8
6460                                | InterfaceType::U16
6461                                | InterfaceType::U32
6462                                | InterfaceType::U64
6463                                | InterfaceType::S8
6464                                | InterfaceType::S16
6465                                | InterfaceType::S32
6466                                | InterfaceType::S64
6467                                | InterfaceType::Float32
6468                                | InterfaceType::Float64
6469                        ),
6470                        matches!(
6471                            payload_ty,
6472                            InterfaceType::Stream(_) | InterfaceType::Future(_)
6473                        ),
6474                        js_typed_array_ctor(&payload_ty).unwrap_or("undefined"),
6475                    )
6476                }
6477            };
6478
6479            format!(
6480                r#"{f}({{
6481                       streamTableIdx: {table_idx},
6482                       componentIdx: {component_idx},
6483                       elemMeta: {{
6484                           liftFn: {payload_lift_fn_js},
6485                           lowerFn: {payload_lower_fn_js},
6486                           payloadTypeName: {payload_ty_name_js},
6487                           isNone: {is_none_type},
6488                           isNumeric: {is_numeric_type},
6489                           isBorrowed: {is_borrowed},
6490                           isAsyncValue: {is_async_value},
6491                           typedArray: {typed_array_js},
6492                           flatCount: {payload_flat_count_js},
6493                           align32: {payload_align32},
6494                           size32: {payload_size32},
6495                       }},
6496                   }})
6497                "#
6498            )
6499        }
6500
6501        InterfaceType::ErrorContext(ty_idx) => {
6502            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatErrorContext));
6503            let table_idx = ty_idx.as_u32();
6504            let lower_flat_err_ctx_fn =
6505                Intrinsic::Lower(LowerIntrinsic::LowerFlatErrorContext).name();
6506            format!("{lower_flat_err_ctx_fn}.bind(null, {table_idx})")
6507        }
6508
6509        InterfaceType::Map(ty_idx) => {
6510            instantiator.add_intrinsic(Intrinsic::Lower(LowerIntrinsic::LowerFlatMap));
6511            let f = Intrinsic::Lower(LowerIntrinsic::LowerFlatMap).name();
6512            let map_ty = &component_types[*ty_idx];
6513            let key_lower =
6514                gen_flat_lower_fn_js_expr(instantiator, &map_ty.key, extra_resource_map);
6515            let value_lower =
6516                gen_flat_lower_fn_js_expr(instantiator, &map_ty.value, extra_resource_map);
6517            let entry_size32 = map_ty.entry_abi.size32;
6518            let entry_align32 = map_ty.entry_abi.align32;
6519            let value_offset32 = map_ty.value_offset32;
6520            format!(
6521                "{f}({{
6522                     keyLowerFn: {key_lower},
6523                     valueLowerFn: {value_lower},
6524                     entrySize32: {entry_size32},
6525                     entryAlign32: {entry_align32},
6526                     valueOffset32: {value_offset32},
6527                 }})"
6528            )
6529        }
6530    }
6531}
6532
6533#[cfg(test)]
6534mod tests {
6535    use super::*;
6536
6537    /// Helper to extract just the compat key string for cleaner test assertions.
6538    fn compat_key(version_str: &str) -> Option<String> {
6539        semver_compat_key(version_str).map(|(key, _)| key)
6540    }
6541
6542    #[test]
6543    fn test_semver_compat_key() {
6544        assert_eq!(compat_key("1.0.0"), Some("1".into()));
6545        assert_eq!(compat_key("1.2.3"), Some("1".into()));
6546        assert_eq!(compat_key("2.0.0"), Some("2".into()));
6547        assert_eq!(compat_key("0.2.0"), Some("0.2".into()));
6548        assert_eq!(compat_key("0.2.10"), Some("0.2".into()));
6549        assert_eq!(compat_key("0.1.0"), Some("0.1".into()));
6550        assert_eq!(compat_key("0.0.1"), None);
6551        assert_eq!(compat_key("1.0.0-rc.1"), None);
6552        assert_eq!(compat_key("0.2.0-pre"), None);
6553        assert_eq!(compat_key("not-a-version"), None);
6554    }
6555
6556    #[test]
6557    fn test_semver_compat_key_returns_parsed_version() {
6558        let (key, ver) = semver_compat_key("1.2.3").unwrap();
6559        assert_eq!(key, "1");
6560        assert_eq!(ver, Version::new(1, 2, 3));
6561    }
6562
6563    #[test]
6564    fn test_map_import_exact_match() {
6565        let mut map = HashMap::new();
6566        map.insert("wasi:http/types@0.2.0".into(), "./http.js#types".into());
6567        let map = Some(map);
6568        assert_eq!(
6569            map_import(&map, "wasi:http/types@0.2.0"),
6570            ("./http.js".into(), Some("types".into()))
6571        );
6572    }
6573
6574    #[test]
6575    fn test_map_import_sans_version_match() {
6576        let mut map = HashMap::new();
6577        map.insert("wasi:http/types".into(), "./http.js".into());
6578        let map = Some(map);
6579        assert_eq!(
6580            map_import(&map, "wasi:http/types@0.2.10"),
6581            ("./http.js".into(), None)
6582        );
6583    }
6584
6585    #[test]
6586    fn test_map_import_wildcard_sans_version() {
6587        // Unversioned wildcard key matches via version-stripped path (pre-existing logic)
6588        let mut map = HashMap::new();
6589        map.insert("wasi:http/*".into(), "./http.js#*".into());
6590        let map = Some(map);
6591        assert_eq!(
6592            map_import(&map, "wasi:http/types@0.2.10"),
6593            ("./http.js".into(), Some("types".into()))
6594        );
6595    }
6596
6597    #[test]
6598    fn test_map_import_semver_exact_key() {
6599        // Map has @0.2.0, import is @0.2.10 — should match via semver
6600        let mut map = HashMap::new();
6601        map.insert("wasi:http/types@0.2.0".into(), "./http.js".into());
6602        let map = Some(map);
6603        assert_eq!(
6604            map_import(&map, "wasi:http/types@0.2.10"),
6605            ("./http.js".into(), None)
6606        );
6607    }
6608
6609    #[test]
6610    fn test_map_import_semver_wildcard_key() {
6611        // Map has wasi:http/*@0.2.0, import is @0.2.10 — should match via semver
6612        let mut map = HashMap::new();
6613        map.insert("wasi:http/*@0.2.1".into(), "./http.js#*".into());
6614        let map = Some(map);
6615        assert_eq!(
6616            map_import(&map, "wasi:http/types@0.2.10"),
6617            ("./http.js".into(), Some("types".into()))
6618        );
6619    }
6620
6621    #[test]
6622    fn test_map_import_semver_lower_import_version() {
6623        // Import version (0.2.1) is lower than map entry (0.2.10) — same compat track
6624        let mut map = HashMap::new();
6625        map.insert("wasi:http/types@0.2.10".into(), "./http.js".into());
6626        let map = Some(map);
6627        assert_eq!(
6628            map_import(&map, "wasi:http/types@0.2.1"),
6629            ("./http.js".into(), None)
6630        );
6631    }
6632
6633    #[test]
6634    fn test_map_import_semver_no_cross_minor() {
6635        // 0.2.x should NOT match 0.3.x
6636        let mut map = HashMap::new();
6637        map.insert("wasi:http/types@0.3.0".into(), "./http.js".into());
6638        let map = Some(map);
6639        assert_eq!(
6640            map_import(&map, "wasi:http/types@0.2.10"),
6641            ("wasi:http/types".into(), None)
6642        );
6643    }
6644
6645    #[test]
6646    fn test_map_import_semver_prefers_highest() {
6647        // Multiple compatible versions — should prefer highest
6648        let mut map = HashMap::new();
6649        map.insert("wasi:http/types@0.2.1".into(), "./http-old.js".into());
6650        map.insert("wasi:http/types@0.2.5".into(), "./http-new.js".into());
6651        let map = Some(map);
6652        assert_eq!(
6653            map_import(&map, "wasi:http/types@0.2.10"),
6654            ("./http-new.js".into(), None)
6655        );
6656    }
6657
6658    #[test]
6659    fn test_map_import_no_match_prerelease() {
6660        let mut map = HashMap::new();
6661        map.insert("wasi:http/types@0.2.0-rc.1".into(), "./http.js".into());
6662        let map = Some(map);
6663        assert_eq!(
6664            map_import(&map, "wasi:http/types@0.2.0"),
6665            ("wasi:http/types".into(), None)
6666        );
6667    }
6668
6669    #[test]
6670    fn test_map_import_prerelease_versioned_wildcard_wins_over_unversioned_wildcard() {
6671        // p3 imports (pre-release version) must route to the
6672        // version-pinned wildcard, not the unversioned p2 fallback.
6673        let mut map = HashMap::new();
6674        map.insert(
6675            "wasi:cli/*".into(),
6676            "@bytecodealliance/preview2-shim/cli#*".into(),
6677        );
6678        map.insert(
6679            "wasi:cli/*@0.3.0".into(),
6680            "@bytecodealliance/preview3-shim/cli#*".into(),
6681        );
6682        let map = Some(map);
6683        assert_eq!(
6684            map_import(&map, "wasi:cli/stdout@0.3.0"),
6685            (
6686                "@bytecodealliance/preview3-shim/cli".into(),
6687                Some("stdout".into())
6688            )
6689        );
6690        // Same map, p2 import should still resolve to preview2-shim.
6691        assert_eq!(
6692            map_import(&map, "wasi:cli/stdout@0.2.6"),
6693            (
6694                "@bytecodealliance/preview2-shim/cli".into(),
6695                Some("stdout".into())
6696            )
6697        );
6698        // Unversioned import should also flow to p2.
6699        assert_eq!(
6700            map_import(&map, "wasi:cli/stdout"),
6701            (
6702                "@bytecodealliance/preview2-shim/cli".into(),
6703                Some("stdout".into())
6704            )
6705        );
6706    }
6707
6708    #[test]
6709    fn test_map_import_no_match_zero_zero() {
6710        let mut map = HashMap::new();
6711        map.insert("wasi:http/types@0.0.1".into(), "./http.js".into());
6712        let map = Some(map);
6713        assert_eq!(
6714            map_import(&map, "wasi:http/types@0.0.2"),
6715            ("wasi:http/types".into(), None)
6716        );
6717    }
6718
6719    #[test]
6720    fn test_map_import_semver_major_version() {
6721        // Major version compat: 1.0.0 and 1.2.3 share compat key "1"
6722        let mut map = HashMap::new();
6723        map.insert("wasi:http/types@1.0.0".into(), "./http.js".into());
6724        let map = Some(map);
6725        assert_eq!(
6726            map_import(&map, "wasi:http/types@1.2.3"),
6727            ("./http.js".into(), None)
6728        );
6729    }
6730
6731    #[test]
6732    fn test_map_import_semver_no_cross_major() {
6733        // 1.x.y should NOT match 2.x.y
6734        let mut map = HashMap::new();
6735        map.insert("wasi:http/types@1.0.0".into(), "./http.js".into());
6736        let map = Some(map);
6737        assert_eq!(
6738            map_import(&map, "wasi:http/types@2.0.0"),
6739            ("wasi:http/types".into(), None)
6740        );
6741    }
6742
6743    #[test]
6744    fn test_map_import_no_map() {
6745        // No map provided — returns import sans version
6746        assert_eq!(
6747            map_import(&None, "wasi:http/types@0.2.0"),
6748            ("wasi:http/types".into(), None)
6749        );
6750    }
6751
6752    #[test]
6753    fn test_map_import_no_map_unversioned() {
6754        // No map, no version — returns import as-is
6755        assert_eq!(
6756            map_import(&None, "wasi:http/types"),
6757            ("wasi:http/types".into(), None)
6758        );
6759    }
6760
6761    #[test]
6762    fn test_parse_mapping_with_hash() {
6763        assert_eq!(
6764            parse_mapping("./http.js#types"),
6765            ("./http.js".into(), Some("types".into()))
6766        );
6767    }
6768
6769    #[test]
6770    fn test_parse_mapping_without_hash() {
6771        assert_eq!(parse_mapping("./http.js"), ("./http.js".into(), None));
6772    }
6773
6774    #[test]
6775    fn test_parse_mapping_leading_hash() {
6776        // Leading '#' should not be treated as a separator
6777        assert_eq!(parse_mapping("#foo"), ("#foo".into(), None));
6778    }
6779
6780    #[test]
6781    fn test_parse_mapping_empty() {
6782        assert_eq!(parse_mapping(""), ("".into(), None));
6783    }
6784}