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