Skip to main content

js_component_bindgen/
transpile_bindgen.rs

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