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