Skip to main content

js_component_bindgen/
transpile_bindgen.rs

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