Skip to main content

js_component_bindgen/
transpile_bindgen.rs

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