use crate::decode::{ImportModule, LocalModule};
use crate::descriptor::{Descriptor, Function, GenericImportKey};
use crate::descriptors::WasmBindgenDescriptorsSection;
use crate::intrinsic::Intrinsic;
use crate::transforms::threads::ThreadCount;
use crate::{decode, wasm_conventions, Bindgen, PLACEHOLDER_MODULE};
use anyhow::{anyhow, bail, ensure, Context as _, Error};
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::str;
use walrus::ir::VisitorMut;
use walrus::{ConstExpr, ElementItems, ExportId, FunctionId, ImportId, MemoryId, Module};
use wasm_bindgen_shared::struct_function_export_name;

mod incoming;
mod nonstandard;
mod outgoing;
mod standard;
pub use self::nonstandard::*;
pub use self::standard::*;

struct Context<'a> {
    start_found: bool,
    module: &'a mut Module,
    adapters: NonstandardWitSection,
    aux: WasmBindgenAux,
    /// All of the Wasm module's exported functions.
    function_exports: HashMap<String, (ExportId, FunctionId)>,
    /// All of the Wasm module's imported functions.
    function_imports: HashMap<String, (ImportId, FunctionId)>,
    memory: Option<MemoryId>,
    vendor_prefixes: HashMap<String, Vec<String>>,
    unique_crate_identifier: &'a str,
    descriptors: HashMap<String, Descriptor>,
    externref_enabled: bool,
    thread_count: Option<ThreadCount>,
    support_start: bool,
    linked_modules: bool,
    /// Tracks the descriptor signature (arguments, ret, inner_ret) used when
    /// creating each export adapter. Used to avoid incorrect deduplication
    /// when wasm-ld ICF merges invoke functions for different closure types
    /// into the same export.
    export_adapter_sigs: HashMap<AdapterId, (Vec<Descriptor>, Descriptor, Option<Descriptor>)>,
    /// Owned JS-binding metadata for generic (per-monomorphisation) imports,
    /// recorded during the AST import pass and keyed by `shim`. The generic
    /// import discovery pass (`bind_generic_imports`) joins the interpreted
    /// per-monomorphisation signatures against this table by `shim` to
    /// manufacture one JS binding per `(shim, signature)`.
    generic_import_bindings: HashMap<String, GenericImportMeta>,
    /// Per-monomorphisation generic imports discovered by the interpreter in
    /// `init`, stashed until after all programs are processed (the AST import
    /// pass populates `generic_import_bindings`, which the manufacture step
    /// then joins against by `shim`).
    pending_generic_imports: HashMap<(GenericImportKey, Descriptor), Vec<FunctionId>>,
    /// Human-meaningful name to blame in binding-failure diagnostics, for the
    /// imports manufactured by `bind_generic_imports`. Those are named
    /// `__wbindgen_generic_N` after a sort index that means nothing to a user, so
    /// the name to actually report is recorded here as they are created rather
    /// than recovered by parsing it back out of the walrus function name.
    generic_import_display_names: HashMap<ImportId, String>,
    /// Final JS identities for exported structs/enums whose qualified names
    /// collide across crates, keyed by `(unique_crate_identifier,
    /// qualified_name)`. Only the colliding `private` definitions appear
    /// here; anything absent keeps its canonical qualified name.
    item_identities: HashMap<(String, String), String>,
    /// Qualified names exported as `private` by multiple crates with no
    /// public owner. A structural reference to one of these from outside its
    /// defining crate is ambiguous and reported as an error.
    ambiguous_items: BTreeSet<String>,
    /// Exports restored from their mangled symbol to a canonical name, in
    /// module order; used to re-sort them by final name afterwards (see
    /// `restore_export_order`).
    demangled_exports: Vec<ExportId>,
}

/// Owned JS-binding metadata for a generic import, captured from its decoded
/// AST entry so it can be applied to each discovered monomorphisation.
///
/// The `AuxImport` and `AdapterJsImportKind` fully describe how the JS value is
/// bound (free function, method, constructor, static, getter, setter,
/// structural, ...) and are independent of the concrete ABI descriptor, so they
/// can be determined once (during the AST import pass) and reused for every
/// interpreted monomorphisation.
///
/// Note there is deliberately no `assert_no_shim`: the macro rejects
/// `assert_no_shim` combined with `experimental_generic_mono` at parse time, so it is
/// always `false` on this path and `bind_generic_imports` passes the constant
/// through to `finish_import_binding` (which still needs the parameter for the
/// normal import path).
struct GenericImportMeta {
    aux_import: AuxImport,
    adapter_kind: AdapterJsImportKind,
    catch: bool,
    variadic: bool,
    /// How to refer to this import in a diagnostic, e.g. `console.log` or
    /// `mylib.log`. The shim key is a hash, so it is useless for telling the user
    /// *which* of their imports is involved in a collision.
    display: String,
}

impl GenericImportMeta {
    /// A comparable identity for the JS binding this metadata describes, used
    /// to tell a genuine shim-key collision apart from the same import simply
    /// being declared twice.
    ///
    /// `display` is deliberately excluded. It is derived from the same data
    /// this identity already covers and exists purely for diagnostics.
    fn identity(&self) -> (&AuxImport, AdapterJsImportKind, bool, bool) {
        (
            &self.aux_import,
            self.adapter_kind,
            self.catch,
            self.variadic,
        )
    }
}

struct InstructionBuilder<'a, 'b> {
    input: Vec<AdapterType>,
    output: Vec<AdapterType>,
    instructions: Vec<InstructionData>,
    cx: &'a mut Context<'b>,
    return_position: bool,
}

impl InstructionBuilder<'_, '_> {
    /// Returns `AdapterType::I64` for memory64 modules, `AdapterType::I32` for wasm32.
    fn ptr_ty(&self) -> AdapterType {
        if self.cx.memory64() {
            AdapterType::I64
        } else {
            AdapterType::I32
        }
    }
}

pub fn process(
    bindgen: &mut Bindgen,
    module: &mut Module,
    programs: Vec<decode::Program>,
    thread_count: Option<ThreadCount>,
) -> Result<(NonstandardWitSectionId, WasmBindgenAuxId), Error> {
    let mut cx = Context {
        adapters: Default::default(),
        aux: Default::default(),
        function_exports: Default::default(),
        function_imports: Default::default(),
        vendor_prefixes: Default::default(),
        descriptors: Default::default(),
        unique_crate_identifier: "",
        memory: wasm_conventions::get_memory(module).ok(),
        module,
        start_found: false,
        externref_enabled: bindgen.externref,
        thread_count,
        support_start: bindgen.emit_start,
        linked_modules: bindgen.split_linked_modules,
        export_adapter_sigs: Default::default(),
        generic_import_bindings: Default::default(),
        pending_generic_imports: Default::default(),
        generic_import_display_names: Default::default(),
        item_identities: Default::default(),
        ambiguous_items: Default::default(),
        demangled_exports: Default::default(),
    };
    cx.init()?;

    resolve_item_identities(&mut cx, &programs)?;

    for program in programs {
        cx.program(program)?;
    }

    // Program context is over; anything processed from here on (e.g. generic
    // import monomorphisations) has no defining crate to resolve identities
    // against.
    cx.unique_crate_identifier = "";

    cx.restore_export_order();

    // All AST import metadata is now recorded; manufacture the per-
    // monomorphisation generic-import bindings discovered by the interpreter.
    cx.bind_generic_imports()?;

    if !cx.start_found {
        cx.discover_main()?;
    }
    cx.find_exn_store();
    cx.find_destroy_closure();

    // The JSPI transform (`transforms::jspi`) needs `__wbindgen_malloc`/
    // `__wbindgen_free` to evacuate fiber shadow stacks around suspensions,
    // and the address of the `__wbindgen_jspi_rejected` flag static to report
    // promise rejections. Record them before `unexport_intrinsics` removes
    // their exports.
    if !cx.aux.imports_with_suspending.is_empty() || cx.aux.export_map.values().any(|e| e.jspi) {
        cx.aux.jspi_malloc = Some(cx.malloc()?);
        cx.aux.jspi_free = Some(cx.free()?);
        cx.aux.jspi_rejected = cx
            .module
            .exports
            .iter()
            .find(|e| e.name == "__wbindgen_jspi_rejected")
            .and_then(|e| match e.item {
                walrus::ExportItem::Global(g) => Some(g),
                _ => None,
            })
            .and_then(|g| match &cx.module.globals.get(g).kind {
                walrus::GlobalKind::Local(walrus::ConstExpr::Value(v)) => match *v {
                    walrus::ir::Value::I32(v) => Some(v as u32 as u64),
                    walrus::ir::Value::I64(v) => Some(v as u64),
                    _ => None,
                },
                _ => None,
            });
    }

    cx.verify()?;

    cx.unexport_intrinsics();

    // Sort adapter exports by function signature for deterministic output.
    // Only sort adapters that are ContextAdapterKind::Adapter (not in export_map, not in implements).
    let implements_set: std::collections::HashSet<_> = cx
        .adapters
        .implements
        .iter()
        .map(|(_, _, id)| *id)
        .collect();

    // Filter and collect adapters that need sorting
    let mut adapter_exports: Vec<_> = cx
        .adapters
        .exports
        .iter()
        .filter(|(_, adapter_id)| {
            !cx.aux.export_map.contains_key(adapter_id) && !implements_set.contains(adapter_id)
        })
        .map(|(export_id, _)| {
            let export = cx.module.exports.get(*export_id);
            let sort_key = match export.item {
                walrus::ExportItem::Function(func_id) => {
                    let ty_id = cx.module.funcs.get(func_id).ty();
                    let ty = cx.module.types.get(ty_id);
                    format!("{:?}-{:?}", ty.params(), ty.results())
                }
                _ => String::new(),
            };
            (*export_id, sort_key, export.name.clone(), export.item)
        })
        .collect();

    // Sort bare adapters primarily by signature, to avoid machine-specific
    // mangling non-determinism, then by export name to make the order total.
    //
    // The name tie-break matters: `sort_by` is stable, so without it adapters
    // with an identical signature keep their *creation* order, which makes this
    // output depend on the order of unrelated earlier pipeline stages. That is
    // exactly how moving cast manufacture out of `Context::init` into
    // `bind_generic_imports` silently reordered several reference snapshots.
    // Resorting with Walrus requires deleting and re-injecting, so we then update the stored ID again.
    adapter_exports.sort_by(|a, b| a.1.cmp(&b.1).then_with(|| a.2.cmp(&b.2)));

    // Build mapping of old to new ExportIds
    let mut old_to_new: std::collections::HashMap<walrus::ExportId, walrus::ExportId> =
        std::collections::HashMap::new();

    for (old_export_id, _, name, item) in adapter_exports.iter() {
        cx.module.exports.delete(*old_export_id);
        let new_export_id = cx.module.exports.add(name, *item);
        old_to_new.insert(*old_export_id, new_export_id);

        // Update cx.adapters.exports
        if let Some(pos) = cx
            .adapters
            .exports
            .iter()
            .position(|(eid, _)| eid == old_export_id)
        {
            cx.adapters.exports[pos].0 = new_export_id;
        }
    }

    // Update CallExport instructions in all adapters to use new ExportIds
    for adapter in cx.adapters.adapters.values_mut() {
        let instructions = match &mut adapter.kind {
            AdapterKind::Local { instructions } => instructions,
            AdapterKind::Import { .. } => continue,
        };
        for instr in instructions {
            if let Instruction::CallExport(export_id) = &mut instr.instr {
                if let Some(&new_id) = old_to_new.get(export_id) {
                    *export_id = new_id;
                }
            }
        }
    }

    let adapters = cx.module.customs.add(cx.adapters);
    let aux = cx.module.customs.add(cx.aux);
    Ok((adapters, aux))
}

/// Decide the final JS identity of every exported struct/enum up front.
///
/// Identically named exports from different crates no longer collide at link
/// time (their wasm shims carry a per-crate hash), so name collisions are
/// resolved here instead: a name exported publicly by more than one crate is
/// an error, while `private` copies are remapped to numbered identities
/// (`Name`, `Name2`, `Name3`, ...) so any number of them can coexist. Names
/// defined only once keep their canonical identity and are unaffected.
fn resolve_item_identities(cx: &mut Context, programs: &[decode::Program]) -> Result<(), Error> {
    let mut definitions: BTreeMap<String, Vec<(&str, bool)>> = BTreeMap::new();
    for program in programs {
        let uci = program.unique_crate_identifier;
        for struct_ in &program.structs {
            let qualified =
                wasm_bindgen_shared::qualified_name(struct_.js_namespace.as_deref(), struct_.name);
            definitions
                .entry(qualified)
                .or_default()
                .push((uci, struct_.private));
        }
        for enum_ in &program.enums {
            let qualified =
                wasm_bindgen_shared::qualified_name(enum_.js_namespace.as_deref(), enum_.name);
            definitions
                .entry(qualified)
                .or_default()
                .push((uci, enum_.private));
        }
    }
    // All names taken so far; grown as identities are assigned so that a
    // numbered identity can never land on another definition's name.
    let mut taken: BTreeSet<String> = definitions.keys().cloned().collect();
    for (name, mut defs) in definitions {
        // A crate's own programs are one definition: the same item shows up
        // once per codegen unit its macro expansion landed in.
        defs.sort();
        defs.dedup();
        if defs.len() < 2 {
            continue;
        }
        let public_count = defs.iter().filter(|(_, private)| !private).count();
        if public_count > 1 {
            bail!(
                "the name `{name}` is exported multiple times in this build; \
                 rename one side with `js_name`/`js_namespace`, or mark copies that \
                 are not part of your public interface as `#[wasm_bindgen(private)]`"
            );
        }
        // The canonical name goes to the public definition when there is
        // one, otherwise to the first private definition; remaining private
        // copies are numbered `Name2`, `Name3`, ... skipping taken names.
        // Every private definition gets a map entry (even the one keeping
        // the canonical name) so its own crate's references resolve rather
        // than hitting the ambiguity check below.
        let mut canonical_free = public_count == 0;
        let mut counter = 2u32;
        for (uci, private) in &defs {
            if !*private {
                continue;
            }
            let identity = if canonical_free {
                canonical_free = false;
                name.clone()
            } else {
                loop {
                    let candidate = format!("{name}{counter}");
                    counter += 1;
                    if taken.insert(candidate.clone()) {
                        break candidate;
                    }
                }
            };
            cx.item_identities
                .insert(((*uci).to_string(), name.clone()), identity);
        }
        if public_count == 0 {
            cx.ambiguous_items.insert(name);
        }
    }
    Ok(())
}

impl<'a> Context<'a> {
    fn init(&mut self) -> Result<(), Error> {
        self.aux.stack_pointer = wasm_conventions::get_stack_pointer(self.module);

        // Make a map from string name to ids of all exports
        for export in self.module.exports.iter() {
            if let walrus::ExportItem::Function(f) = export.item {
                self.function_exports
                    .insert(export.name.clone(), (export.id(), f));
            }
        }

        // Make a map from string name to ids of all imports from our
        // placeholder module name which we'll want to be sure that we've got a
        // location listed of what to import there for each item.
        let mut duplicate_import_map = HashMap::new();
        // The order in which imports are deleted later might matter, so we
        // use an ordered set here to make everything deterministic.
        let mut imports_to_delete = BTreeSet::new();
        for import in self.module.imports.iter() {
            if import.module != PLACEHOLDER_MODULE {
                continue;
            }
            let f = match import.kind {
                walrus::ImportKind::Function(f) => f,
                _ => continue,
            };

            match self.function_imports.get(&import.name) {
                // If this `import.name` already exists in our import map, then
                // we need to delete `import`. We also need to replace any
                // references to it with `prev_func`, so register that here to
                // happen later.
                Some((_, prev_func)) => {
                    imports_to_delete.insert(import.id());
                    duplicate_import_map.insert(f, *prev_func);
                }

                // Otherwise this is brand new, so insert it into the map.
                None => {
                    self.function_imports
                        .insert(import.name.clone(), (import.id(), f));
                }
            }
        }
        self.add_aux_import_to_import_map(
            "__wbindgen_object_clone_ref",
            vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
            Descriptor::Externref,
            AuxImport::Intrinsic(Intrinsic::ObjectCloneRef),
        )?;
        self.add_aux_import_to_import_map(
            "__wbindgen_object_drop_ref",
            vec![Descriptor::Externref],
            Descriptor::Unit,
            AuxImport::Intrinsic(Intrinsic::ObjectDropRef),
        )?;

        for import in imports_to_delete {
            self.module.imports.delete(import);
        }

        self.inject_externref_initialization()?;

        if let Some(custom) = self
            .module
            .customs
            .delete_typed::<WasmBindgenDescriptorsSection>()
        {
            let WasmBindgenDescriptorsSection {
                descriptors,
                generic_imports,
            } = *custom;
            // Store all the executed descriptors in our own field so we have
            // access to them while processing programs.
            self.descriptors.extend(descriptors);

            // Per-monomorphisation imports discovered via the
            // `__wbindgen_describe_generic_import` marker — both generic imports
            // and `wbg_cast` identity adapters (empty shim key) — are
            // manufactured together in `bind_generic_imports`, after the AST
            // import pass has recorded the JS-binding metadata that generic
            // imports need. Stash them until then.
            self.pending_generic_imports = generic_imports;
        }

        self.handle_duplicate_imports(&duplicate_import_map);

        self.aux.thread_destroy = self.thread_destroy();

        Ok(())
    }

    /// Manufacture one JS binding per discovered `(shim, signature)`
    /// monomorphisation, and rewrite every originating call site to the
    /// manufactured import.
    ///
    /// Both kinds of entry discovered by the shared
    /// `__wbindgen_describe_generic_import` marker are handled here, sharing one
    /// sorted list, one index counter and one `__wbindgen_generic_*` naming
    /// scheme:
    ///
    /// * A non-empty `shim` is a generic (per-monomorphisation) import. Its JS
    ///   binding carries the real import semantics (kind/name/catch/variadic)
    ///   recovered from the AST entry via `shim`.
    /// * An empty `shim` is a [`wbg_cast`](wasm_bindgen::__rt::wbg_cast)
    ///   identity adapter, bound as `AuxImport::Cast` (JS that returns its
    ///   single argument unchanged); it needs no AST metadata.
    fn bind_generic_imports(&mut self) -> Result<(), Error> {
        let pending = std::mem::take(&mut self.pending_generic_imports);
        if pending.is_empty() {
            return Ok(());
        }

        // Deterministic ordering for stable import names/output. Casts and
        // generic imports share one sorted list and one index counter; each
        // entry's signature comment doubles as the primary sort key.
        let mut sorted: Vec<_> = pending
            .into_iter()
            .map(|((key, descriptor), orig_func_ids)| {
                let mut signature = match descriptor {
                    Descriptor::Function(f) => *f,
                    other => bail!(
                        "a per-monomorphisation descriptor decoded to {other:?} rather than a \
                         function signature; this is a wasm-bindgen bug, please report it"
                    ),
                };
                // Struct/enum references carry their defining crate in the
                // descriptor, so they resolve here even though
                // monomorphisations are discovered globally; only
                // `NamedExternref` references can still hit the ambiguity
                // check.
                self.resolve_descriptor_function(&mut signature)?;
                let sig_comment = match &key {
                    GenericImportKey::Cast => {
                        // `wbg_cast` is a one-argument identity function, but this
                        // is a property of the *data*, not of the type: the `Cast`
                        // variant is chosen purely because the wire carried a
                        // zero-length shim key, so a corrupt or version-skewed
                        // module can reach here with any arity. Report it rather
                        // than panicking — everything else in this closure is
                        // already fallible.
                        let [arg] = &signature.arguments[..] else {
                            bail!(
                                "a cast descriptor declared {} argument(s), but a cast is an \
                                 identity function and must take exactly one; this is a \
                                 wasm-bindgen bug, please report it",
                                signature.arguments.len()
                            );
                        };
                        format!("{arg:?} -> {:?}", signature.ret)
                    }
                    GenericImportKey::Shim(shim) => {
                        format!("{shim}: {:?} -> {:?}", signature.arguments, signature.ret)
                    }
                };
                Ok((sig_comment, key, signature, orig_func_ids))
            })
            .collect::<Result<Vec<_>, Error>>()?;
        // Sort primarily by `sig_comment` so the user-visible naming and
        // ordering is driven by the signature, then tie-break on the *whole*
        // HashMap key (`GenericImportKey` plus the full `Descriptor`).
        //
        // The tie-break has to be the full key, not just the shim: `sig_comment`
        // for a generic import already starts with the shim key, so tie-breaking
        // on the shim alone can never break a tie the primary key did not. More
        // importantly `sig_comment` omits `Function::shim_idx` and
        // `Function::inner_ret`, both of which participate in `Descriptor`'s
        // `Hash`/`Eq` — so two genuinely distinct map entries can produce an
        // equal `sig_comment` and, without this, fall through to nondeterministic
        // `HashMap` iteration order. Today the macro always emits `shim_idx = 0`
        // and `inner_ret == ret`, but nothing asserts that, so the ordering is
        // made total regardless.
        sorted.sort_by(|a, b| {
            a.0.cmp(&b.0)
                .then_with(|| a.1.cmp(&b.1))
                .then_with(|| a.2.cmp(&b.2))
        });

        let mut duplicate_import_map = HashMap::new();
        for (idx, (sig_comment, key, signature, orig_func_ids)) in sorted.into_iter().enumerate() {
            let import_name = format!("__wbindgen_generic_{:016x}", idx + 1);
            // Every monomorphisation in a group was keyed by an identical
            // decoded signature, so they must all share one Wasm type; the
            // manufactured import is given that type and every call site is
            // rewritten to it, which would produce an invalid module if they
            // did not. Verify rather than assume.
            let ty = self.module.funcs.get(orig_func_ids[0]).ty();
            for &other in &orig_func_ids[1..] {
                let other_ty = self.module.funcs.get(other).ty();
                if other_ty != ty {
                    bail!(
                        "monomorphisations grouped under `{sig_comment}` do not all have the \
                         same Wasm type ({ty:?} vs {other_ty:?}), so they cannot share one \
                         manufactured import; this is a wasm-bindgen bug, please report it"
                    );
                }
            }
            let (import_func_id, import_id) =
                self.module
                    .add_import_func(PLACEHOLDER_MODULE, &import_name, ty);
            self.module.funcs.get_mut(import_func_id).name = Some(sig_comment.clone());

            // Blame something meaningful if this import fails to bind: the shim
            // key for a generic import, or the cast's signature for a cast (which
            // has no user-facing name of its own).
            self.generic_import_display_names.insert(
                import_id,
                match &key {
                    GenericImportKey::Cast => format!("cast {sig_comment}"),
                    GenericImportKey::Shim(shim) => shim.clone(),
                },
            );

            match key {
                GenericImportKey::Cast => {
                    // Identity adapter, no AST metadata required.
                    let id =
                        self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?;
                    self.aux
                        .import_map
                        .insert(id, AuxImport::Cast { sig_comment });
                }
                GenericImportKey::Shim(shim) => {
                    let meta = self.generic_import_bindings.get(&shim).ok_or_else(|| {
                        // The monomorphised shim was instantiated (so its
                        // descriptor is in the module), but the `#[wasm_bindgen]`
                        // AST entry that supplies its JS binding metadata is not.
                        // In practice that means the declaring crate's
                        // `__wasm_bindgen_unstable` metadata section did not make it
                        // into the link — see the anchoring descriptor export in
                        // `DescribeImport`, which exists precisely to stop wasm-ld
                        // dropping that archive member.
                        anyhow::anyhow!(
                            "a generic import was monomorphised as `{shim}`, but the \
                             #[wasm_bindgen] metadata describing how to bind it is missing \
                             from this module.\n\
                             \n\
                             This usually means the crate declaring the import did not \
                             contribute its wasm-bindgen metadata to the link. Check that \
                             the declaring crate is built with the same wasm-bindgen \
                             version as this CLI, and that its `extern \"C\"` block is not \
                             entirely #[cfg]-ed out for this target.\n\
                             \n\
                             Otherwise this is a wasm-bindgen bug; please report it at \
                             https://github.com/wasm-bindgen/wasm-bindgen/issues"
                        )
                    })?;
                    let aux_import = meta.aux_import.clone();
                    let adapter_kind = meta.adapter_kind;
                    let catch = meta.catch;
                    let variadic = meta.variadic;
                    let display = meta.display.clone();

                    // The generated name is `__wbindgen_generic_<hash>`, which says
                    // nothing about what this binds. Record the JS target and this
                    // monomorphisation's concrete signature, which together are what
                    // distinguish sibling shims — two monomorphisations can have
                    // byte-identical JS bodies and differ only in their wasm
                    // signature. The shim key is deliberately left out: it is a hash,
                    // and `display` already names the import.
                    let mono = format!("{:?} -> {:?}", signature.arguments, signature.ret);
                    let id = self.import_adapter(import_id, signature, adapter_kind)?;
                    self.aux
                        .import_comments
                        .insert(id, format!("generic import `{display}`: {mono}"));
                    // `assert_no_shim` and `suspending` are rejected in
                    // combination with `experimental_generic_mono` by the macro (see
                    // `parser.rs`), so both are always false on this path.
                    self.finish_import_binding(id, aux_import, catch, variadic, false, false);
                }
            }

            duplicate_import_map.extend(orig_func_ids.into_iter().map(|f| (f, import_func_id)));
        }

        self.handle_duplicate_imports(&duplicate_import_map);
        Ok(())
    }

    /// The same name function from the same module may be imported at different
    /// points in a program. The compiler may synthesize two `import`
    /// statements, both with the same module/name, to match these two function
    /// imports. This is handled here.
    ///
    /// Almost all of our handling of directives and such is string-based (eew)
    /// instead of ID based due to the way the macro works right now. This means
    /// that we don't work well with these duplicate imports. As a result when
    /// we see these duplicate imports we fixup the module to ensure that only
    /// one import is used, deleting all the other imports. This is what's
    /// wanted anyway in terms of semantics.
    ///
    /// The map provided here is a map where the key is a function id to replace
    /// and the value is what to replace it with.
    fn handle_duplicate_imports(&mut self, map: &HashMap<FunctionId, FunctionId>) {
        // Nothing to replace: skip the full-module instruction walk entirely.
        // This matters because this pass now runs a second time for generic
        // imports (`bind_generic_imports`), so avoiding a redundant traversal
        // when either map is empty keeps large modules cheap.
        if map.is_empty() {
            return;
        }

        struct Replace<'a> {
            map: &'a HashMap<FunctionId, FunctionId>,
        }
        impl VisitorMut for Replace<'_> {
            fn visit_function_id_mut(&mut self, function: &mut FunctionId) {
                if let Some(replacement) = self.map.get(function) {
                    *function = *replacement;
                }
            }
        }
        let mut replace = Replace { map };
        for (_id, func) in self.module.funcs.iter_local_mut() {
            let entry = func.entry_block();
            walrus::ir::dfs_pre_order_mut(&mut replace, func, entry);
        }
        for elems in self.module.elements.iter_mut() {
            match &mut elems.items {
                ElementItems::Functions(funcs) => {
                    for func in funcs.iter_mut() {
                        replace.visit_function_id_mut(func);
                    }
                }
                ElementItems::Expressions(_, exprs) => {
                    for expr in exprs {
                        if let ConstExpr::RefFunc(func) = expr {
                            replace.visit_function_id_mut(func);
                        }
                    }
                }
            }
        }
    }

    // Discover the C `main(i32, argv) -> i32` shim and, if it exists, make it
    // run at module start. Argv is the size of a pointer.
    fn discover_main(&mut self) -> Result<(), Error> {
        let memory64 = self.memory64();
        let argv_ty = if memory64 {
            walrus::ValType::I64
        } else {
            walrus::ValType::I32
        };

        // find a `main(i32, argv) -> i32`
        let main_id = self
            .module
            .exports
            .iter()
            .filter_map(|export| match export.item {
                walrus::ExportItem::Function(id) => Some((export, self.module.funcs.get(id))),
                _ => None,
            })
            .find(|(export, func)| {
                // name has to be `main`
                let name_matches = export.name == "main";
                // type has to be `(i32, argv) -> i32`
                let ty = self.module.types.get(func.ty());
                let type_matches = ty.params() == [walrus::ValType::I32, argv_ty]
                    && ty.results() == [walrus::ValType::I32];
                // Having the correct name and signature doesn't necessarily mean that it's
                // actually a `main` function. Unfortunately, there doesn't seem to be any 100%
                // reliable way to make sure that it is, but we can at least rule out any
                // `#[wasm_bindgen]` exported functions.
                let unknown = !self
                    .adapters
                    .exports
                    .iter()
                    .any(|(export_id, _)| *export_id == export.id());
                name_matches && type_matches && unknown
            })
            .map(|(_, func)| func.id());

        let main_id = match main_id {
            Some(x) => x,
            None => return Ok(()),
        };

        // build a wrapper to zero out the arguments and ignore the return value
        let mut wrapper = walrus::FunctionBuilder::new(&mut self.module.types, &[], &[]);
        let mut body = wrapper.func_body();
        body.i32_const(0); // argc
        if memory64 {
            body.i64_const(0);
        } else {
            body.i32_const(0);
        }
        body.call(main_id).drop().return_();
        let wrapper = wrapper.finish(vec![], &mut self.module.funcs);

        // call that wrapper when the module starts
        self.add_start_function(wrapper)?;

        Ok(())
    }

    // Ensure that the `start` function for this module calls the
    // `__wbindgen_init_externref_table` function. This'll ensure that all
    // instances of this module have the initial slots of the externref table
    // initialized correctly.
    //
    // Note that this is disabled if WebAssembly interface types are enabled
    // since that's a slightly different environment for now which doesn't have
    // quite the same initialization.
    //
    // TODO: can we just move this to the JS generation?
    fn inject_externref_initialization(&mut self) -> Result<(), Error> {
        if !self.externref_enabled {
            return Ok(());
        }

        let ty = self.module.types.add(&[], &[]);
        let (import, import_id) =
            self.module
                .add_import_func(PLACEHOLDER_MODULE, "__wbindgen_init_externref_table", ty);

        if self.module.start.is_some() {
            let builder = wasm_conventions::get_or_insert_start_builder(self.module);
            builder.func_body().call_at(0, import);
        } else {
            self.module.start = Some(import);
        }

        let adapter_id = self.import_adapter(
            import_id,
            Function {
                shim_idx: 0,
                arguments: vec![],
                ret: Descriptor::Unit,
                inner_ret: None,
            },
            AdapterJsImportKind::Normal,
        )?;
        self.aux.import_map.insert(
            adapter_id,
            AuxImport::Intrinsic(Intrinsic::InitExternrefTable),
        );

        Ok(())
    }

    fn link_module(
        &mut self,
        id: ImportId,
        module: &decode::ImportModule,
        offset: usize,
        local_modules: &[LocalModule],
        inline_js: &[&str],
    ) -> Result<(), Error> {
        let descriptor = Function {
            shim_idx: 0,
            arguments: Vec::new(),
            ret: Descriptor::String,
            inner_ret: None,
        };
        let id = self.import_adapter(id, descriptor, AdapterJsImportKind::Normal)?;
        let (path, content) = match module {
            decode::ImportModule::Named(n) => (
                format!("snippets/{n}"),
                local_modules
                    .iter()
                    .find(|m| m.identifier == *n)
                    .map(|m| m.contents),
            ),
            decode::ImportModule::RawNamed(n) => (n.to_string(), None),
            decode::ImportModule::Inline(idx) => (
                format!(
                    "snippets/{}/inline{}.js",
                    self.unique_crate_identifier,
                    *idx as usize + offset
                ),
                Some(inline_js[*idx as usize]),
            ),
        };
        self.aux
            .import_map
            .insert(id, AuxImport::LinkTo(path, content.map(str::to_string)));
        Ok(())
    }

    /// The per-crate hash the macro mangled this program's export shim
    /// symbols with; carried as the suffix of `unique_crate_identifier`.
    fn crate_hash(&self) -> &str {
        wasm_bindgen_shared::crate_hash(self.unique_crate_identifier)
    }

    /// The mangled symbol the macro emitted for a canonical shim name of the
    /// current program's crate.
    fn mangled(&self, base: &str) -> String {
        wasm_bindgen_shared::mangled_symbol(base, self.crate_hash())
    }

    /// Final JS identity of a struct/enum defined by the current program's
    /// crate (inherent impls always live in the defining crate, so class
    /// strings on exports resolve through the same crate identity).
    fn item_identity(&self, qualified: &str) -> String {
        self.item_identities
            .get(&(
                self.unique_crate_identifier.to_string(),
                qualified.to_string(),
            ))
            .cloned()
            .unwrap_or_else(|| qualified.to_string())
    }

    /// Final JS identity of a struct/enum referenced by name. Rust type
    /// descriptors carry their defining crate; other named descriptors use
    /// the current program's crate.
    fn resolve_item_ref(
        &self,
        qualified: &mut String,
        defining_crate: Option<&str>,
    ) -> Result<(), Error> {
        let defining_crate = defining_crate.unwrap_or(self.unique_crate_identifier);
        if let Some(identity) = self
            .item_identities
            .get(&(defining_crate.to_string(), qualified.to_string()))
        {
            *qualified = identity.clone();
        } else if self.ambiguous_items.contains(qualified.as_str()) {
            bail!(
                "`{qualified}` is exported as `#[wasm_bindgen(private)]` by multiple \
                 crates, so this reference to it by name is ambiguous"
            );
        }
        Ok(())
    }

    /// Rewrite struct/enum names embedded in a descriptor to their final JS
    /// identities as seen from the current program's crate.
    fn resolve_descriptor(&self, descriptor: &mut Descriptor) -> Result<(), Error> {
        descriptor.visit_named_types_mut(&mut |name, defining_crate| {
            self.resolve_item_ref(name, defining_crate)
        })
    }

    /// See [`Context::resolve_descriptor`].
    fn resolve_descriptor_function(&self, function: &mut Function) -> Result<(), Error> {
        function.visit_named_types_mut(&mut |name, defining_crate| {
            self.resolve_item_ref(name, defining_crate)
        })
    }

    /// The macro emits every export shim under a crate-hash mangled symbol so
    /// that same-named exports from different crates link cleanly; restore
    /// the export's canonical name now that it has been matched. A conflict
    /// on the canonical name means two crates genuinely exported the same
    /// JS-visible name (previously a cryptic wasm-ld duplicate-symbol
    /// failure).
    fn demangle_export(&mut self, mangled: &str, canonical: &str) -> Result<(), Error> {
        let Some((export_id, func_id)) = self.function_exports.remove(mangled) else {
            return Ok(());
        };
        if self.function_exports.contains_key(canonical) {
            bail!(
                "the name `{canonical}` is exported by multiple crates in this build; \
                 rename one side with `js_name`/`js_namespace`"
            );
        }
        self.module.exports.get_mut(export_id).name = canonical.to_string();
        // The name-section entry is the mangled symbol too; restore it so
        // stack traces and disassembly show the canonical name. Guarded by an
        // exact match since wasm-ld may have merged identical shims, in which
        // case the function carries some other export's name.
        let func = self.module.funcs.get_mut(func_id);
        if func.name.as_deref() == Some(mangled) {
            func.name = Some(canonical.to_string());
        }
        self.function_exports
            .insert(canonical.to_string(), (export_id, func_id));
        self.demangled_exports.push(export_id);
        Ok(())
    }

    /// Restore the canonical name of a placeholder-module import emitted
    /// under a crate-hash mangled symbol (a struct's `new`/`unwrap` shim).
    /// On a genuine collision (same-named `private` structs) later copies
    /// keep their mangled name, which is fine: import shim names are
    /// internal and JS-side glue is wired up per import entry.
    fn demangle_import(&mut self, mangled: &str, canonical: &str) -> String {
        let Some(&(import_id, func_id)) = self.function_imports.get(mangled) else {
            return mangled.to_string();
        };
        if self.function_imports.contains_key(canonical) {
            return mangled.to_string();
        }
        self.module.imports.get_mut(import_id).name = canonical.to_string();
        let func = self.module.funcs.get_mut(func_id);
        if func.name.as_deref() == Some(mangled) {
            func.name = Some(canonical.to_string());
        }
        self.function_imports.remove(mangled);
        self.function_imports
            .insert(canonical.to_string(), (import_id, func_id));
        canonical.to_string()
    }

    /// wasm-ld emits exports sorted by symbol name, but that order was
    /// computed over the mangled symbols; re-sort the demangled exports by
    /// their final names (within their own positions in the export section)
    /// so the output is ordered exactly as it would be with unmangled
    /// symbols, independent of per-crate hash values.
    fn restore_export_order(&mut self) {
        let demangled = std::mem::take(&mut self.demangled_exports)
            .into_iter()
            .collect::<std::collections::HashSet<_>>();
        // The positions being re-sorted into, in export-section order.
        let positions = self
            .module
            .exports
            .iter()
            .map(|e| e.id())
            .filter(|id| demangled.contains(id))
            .collect::<Vec<_>>();
        let mut entries = positions
            .iter()
            .map(|&id| {
                let export = self.module.exports.get(id);
                (id, export.name.clone(), export.item)
            })
            .collect::<Vec<_>>();
        entries.sort_by(|a, b| a.1.cmp(&b.1));

        // Moving an entry to another position moves it to another
        // `ExportId`, so fix up everything that referenced the old one.
        let mut old_to_new = HashMap::new();
        for (&position, (old_id, name, item)) in positions.iter().zip(entries) {
            old_to_new.insert(old_id, position);
            if let walrus::ExportItem::Function(f) = item {
                self.function_exports.insert(name.clone(), (position, f));
            }
            let export = self.module.exports.get_mut(position);
            export.name = name;
            export.item = item;
        }
        for (export_id, _) in self.adapters.exports.iter_mut() {
            if let Some(&new_id) = old_to_new.get(export_id) {
                *export_id = new_id;
            }
        }
        for adapter in self.adapters.adapters.values_mut() {
            let AdapterKind::Local { instructions } = &mut adapter.kind else {
                continue;
            };
            for instruction in instructions {
                if let Instruction::CallExport(export_id) = &mut instruction.instr {
                    if let Some(&new_id) = old_to_new.get(export_id) {
                        *export_id = new_id;
                    }
                }
            }
        }
    }

    fn program(&mut self, program: decode::Program<'a>) -> Result<(), Error> {
        self.unique_crate_identifier = program.unique_crate_identifier;
        let decode::Program {
            exports,
            enums,
            imports,
            structs,
            typescript_custom_sections,
            local_modules,
            inline_js,
            unique_crate_identifier,
            package_json,
            linked_modules,
        } = program;

        for module in local_modules
            .iter()
            .filter(|module| self.linked_modules || !module.linked_module)
        {
            // All local modules we find should be unique, but the same module
            // may have showed up in a few different blocks. If that's the case
            // all the same identifiers should have the same contents.
            if let Some(prev) = self
                .aux
                .local_modules
                .insert(module.identifier.to_string(), module.contents.to_string())
            {
                assert_eq!(prev, module.contents);
            }
        }
        if let Some(s) = package_json {
            self.aux.package_jsons.insert(s.into());
        }
        for export in exports {
            self.export(export)?;
        }

        let offset = self
            .aux
            .snippets
            .get(unique_crate_identifier)
            .map(|s| s.len())
            .unwrap_or(0);
        for module in linked_modules {
            if let Some((id, _)) = self.function_imports.remove(module.link_function_name) {
                self.link_module(
                    id,
                    &module.module,
                    offset,
                    &local_modules[..],
                    &inline_js[..],
                )?;
            }
        }

        // Register vendor prefixes for all types before we walk over all the
        // imports to ensure that if a vendor prefix is listed somewhere it'll
        // apply to all the imports.
        for import in imports.iter() {
            if let decode::ImportKind::Type(ty) = &import.kind {
                if ty.vendor_prefixes.is_empty() {
                    continue;
                }
                self.vendor_prefixes
                    .entry(ty.name.to_string())
                    .or_default()
                    .extend(ty.vendor_prefixes.iter().map(|s| s.to_string()));
            }
        }
        for import in imports {
            self.import(import)?;
        }

        for enum_ in enums {
            self.enum_(enum_)?;
        }
        for struct_ in structs {
            self.struct_(struct_)?;
        }

        // Collect custom sections to be sorted later when all CGUs encountered
        self.aux
            .extra_typescript
            .extend(typescript_custom_sections.iter().map(|s| s.to_string()));
        self.aux
            .snippets
            .entry(unique_crate_identifier.to_string())
            .or_default()
            .extend(inline_js.iter().map(|s| s.to_string()));
        Ok(())
    }

    fn export(&mut self, export: decode::Export<'_>) -> Result<(), Error> {
        // The canonical wasm shim symbol (see `ast::Export::export_name`)
        // prefixes the `js_namespace` for both class and free-function
        // exports; the macro emits it mangled with the per-crate hash.
        // Mirror both here so we can find the descriptor and the export.
        let canonical_name = match &export.class {
            Some(class) => {
                let base = struct_function_export_name(class, export.function.name);
                if let Some(ref ns) = export.js_namespace {
                    format!("{}__{base}", ns.join("__"))
                } else {
                    base
                }
            }
            None => {
                let base_name = export.function.name.to_string();
                if let Some(ref ns) = export.js_namespace {
                    format!("{}__{base_name}", ns.join("__"))
                } else {
                    base_name
                }
            }
        };
        let wasm_name = self.mangled(&canonical_name);

        // Resolve the class identity up front: when the class was remapped
        // (a colliding `private` struct), the export's restored wasm name
        // follows the identity rather than the canonical form.
        let class_identity = export.class.as_ref().map(|class| {
            let qualified =
                wasm_bindgen_shared::qualified_name(export.js_namespace.as_deref(), class);
            (self.item_identity(&qualified), qualified)
        });
        let final_name = match &class_identity {
            Some((identity, qualified)) if identity != qualified => {
                struct_function_export_name(identity, export.function.name)
            }
            _ => canonical_name,
        };

        let mut descriptor = match self.descriptors.remove(&wasm_name) {
            None => {
                self.demangle_export(&wasm_name, &final_name)?;
                return Ok(());
            }
            Some(d) => d.unwrap_function(),
        };
        self.resolve_descriptor_function(&mut descriptor)?;

        let Some((export_id, id)) = self.function_exports.get(&wasm_name).copied() else {
            bail!("{wasm_name} symbol is missing, \
                may be because there are multiple exports with the same name but different signatures, \
                and discarded by wasm-ld.");
        };
        self.demangle_export(&wasm_name, &final_name)?;

        match export.start {
            decode::StartKind::Public => {
                self.add_start_function(id)?;
            }
            decode::StartKind::Private => {
                self.add_start_function(id)?;
                return Ok(());
            }
            decode::StartKind::None => {}
        }

        let classless_this = matches!(
            &export.method_kind,
            decode::MethodKind::Operation(op) if matches!(op.kind, decode::OperationKind::RegularThis)
        );

        let kind = match class_identity {
            Some((class, _)) => {
                // Carry the class identity (the namespaced `qualified_name`
                // form `ns__Foo`, remapped for colliding `private` structs)
                // downstream so it matches the key the struct registered
                // under in `exported_classes`. With just the bare `class`
                // string, an impl whose struct lives in a `js_namespace`
                // would not resolve back to its registered
                // `exported_classes` entry and the constructor + methods
                // would land on a fresh empty entry instead.
                match export.method_kind {
                    decode::MethodKind::Constructor => {
                        verify_constructor_return(&class, &descriptor.ret)?;
                        AuxExportKind::Constructor(class)
                    }
                    decode::MethodKind::Operation(op) => {
                        if !op.is_static {
                            // Make the first argument be the index of the receiver.
                            descriptor.arguments.insert(0, Descriptor::I32);
                        }

                        let (name, kind) = match op.kind {
                            decode::OperationKind::Getter(f) => (f, AuxExportedMethodKind::Getter),
                            decode::OperationKind::Setter(f) => (f, AuxExportedMethodKind::Setter),
                            _ => (export.function.name, AuxExportedMethodKind::Method),
                        };

                        AuxExportKind::Method {
                            class,
                            name: name.to_owned(),
                            receiver: if op.is_static {
                                AuxReceiverKind::None
                            } else if export.consumed {
                                AuxReceiverKind::Owned
                            } else {
                                AuxReceiverKind::Borrowed
                            },
                            kind,
                        }
                    }
                }
            }
            _ => {
                if classless_this {
                    AuxExportKind::FunctionThis(export.function.name.to_string())
                } else {
                    AuxExportKind::Function(export.function.name.to_string())
                }
            }
        };

        let args = Some(
            export
                .function
                .args
                .into_iter()
                .map(|v| AuxFunctionArgumentData {
                    name: v.name,
                    ty_override: v.ty_override.map(String::from),
                    optional: v.optional,
                    desc: v.desc.map(String::from),
                })
                .collect::<Vec<_>>(),
        );
        let id = self.export_adapter(export_id, descriptor)?;
        self.aux.export_map.insert(
            id,
            AuxExport {
                debug_name: wasm_name,
                comments: concatenate_comments(&export.comments),
                args,
                asyncness: export.function.asyncness,
                jspi: export.function.jspi,
                kind,
                js_namespace: export
                    .js_namespace
                    .map(|ns| ns.iter().map(|s| s.to_string()).collect()),
                generate_typescript: export.function.generate_typescript,
                generate_jsdoc: export.function.generate_jsdoc,
                variadic: export.function.variadic,
                fn_ret_ty_override: export.function.ret_ty_override.map(String::from),
                fn_ret_desc: export.function.ret_desc.map(String::from),
            },
        );
        Ok(())
    }

    fn add_start_function(&mut self, id: FunctionId) -> Result<(), Error> {
        self.start_found = true;

        // Skip this when we're generating tests
        if !self.support_start {
            return Ok(());
        }

        if let Some(thread_count) = self.thread_count {
            let builder = wasm_conventions::get_or_insert_start_builder(self.module);
            thread_count.wrap_start(builder, id);
        } else if self.module.start.is_some() {
            let builder = wasm_conventions::get_or_insert_start_builder(self.module);
            builder.func_body().call(id);
        } else {
            self.module.start = Some(id);
        }

        Ok(())
    }

    fn import(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
        match &import.kind {
            decode::ImportKind::Function(_) => self.import_function(import),
            decode::ImportKind::Static(_) => self.import_static(import),
            decode::ImportKind::String(s) => self.import_string(s),
            decode::ImportKind::Type(_) => self.import_type(import),
            decode::ImportKind::Enum(e) => self.string_enum(e),
            decode::ImportKind::DynamicUnion(e) => self.dynamic_union(e),
        }
    }

    fn import_function(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
        let decode::ImportKind::Function(function) = import.kind else {
            unreachable!();
        };
        let decode::ImportFunction {
            shim,
            catch,
            variadic,
            method,
            structural,
            function,
            assert_no_shim,
            suspending,
            generic_per_mono,
        } = function;
        let generate_typescript = import.generate_typescript;

        // Generic (per-monomorphisation) imports have no single descriptor
        // shim; each concrete instantiation is discovered by the interpreter
        // via the `__wbindgen_describe_generic_import` marker. Here we only
        // record the JS-binding metadata keyed by `shim`, to be applied to each
        // discovered monomorphisation in `bind_generic_imports`.
        //
        // The `AuxImport` / `AdapterJsImportKind` are determined the same way as
        // for a normal import (the logic below is descriptor-independent), so
        // methods, constructors, statics, getters, setters and structural
        // accessors all bind identically to their non-generic counterparts.
        if generic_per_mono {
            // The macro still emits a named descriptor export for this shim, but
            // purely to anchor the defining crate's `__wasm_bindgen_unstable`
            // section into the link (see `DescribeImport::try_to_tokens`). Its
            // contents describe a trivial `fn()` rather than the real signature,
            // so drop it here to make sure it can never be mistaken for one.
            self.descriptors.remove(shim);

            // Reexport is applied per named descriptor shim in the normal path;
            // a generic import has no single shim (one binding is manufactured
            // per monomorphisation), so reexport has no well-defined target.
            // The macro rejects this combination up front with a span, so this
            // should be unreachable; kept as belt-and-braces in case an AST
            // reaches the CLI from an older or hand-built macro.
            if import.reexport.is_some() {
                bail!(
                    "#[wasm_bindgen] `experimental_generic_mono` imports cannot be reexported; \
                     remove the reexport or use the type-erasure generic path"
                );
            }
            let (aux_import, adapter_kind) = match method {
                Some(data) => {
                    let class =
                        self.determine_import(&import.module, &import.js_namespace, data.class)?;
                    match data.kind {
                        decode::MethodKind::Constructor => (
                            AuxImport::Value(AuxValue::Bare(class)),
                            AdapterJsImportKind::Constructor,
                        ),
                        decode::MethodKind::Operation(op) => {
                            let (aux, is_method) =
                                self.determine_import_op(class, &function, structural, op)?;
                            let kind = if is_method {
                                AdapterJsImportKind::Method
                            } else {
                                AdapterJsImportKind::Normal
                            };
                            (aux, kind)
                        }
                    }
                }
                None => {
                    let js_import =
                        self.determine_import(&import.module, &import.js_namespace, function.name)?;
                    (
                        AuxImport::Value(AuxValue::Bare(js_import)),
                        AdapterJsImportKind::Normal,
                    )
                }
            };
            // The shim key is a hash over `(namespace + js_namespace, signature
            // tokens, module, cfg_attrs)` and the imported function's
            // `wasm.name` (which is the `js_name` when one is given). A number of
            // attributes are still *not* hashed, though: `catch`, `variadic`,
            // `slice_to_array`, `structural`/`final`, and the getter/setter
            // accessor kind. Two generic imports that agree on everything hashed
            // and differ only in one of those therefore land on the same key.
            //
            // Two *identical* declarations also land on the same key, which is
            // benign and must be deduplicated rather than rejected. So compare
            // the resulting metadata rather than treating any key clash as an
            // error: silently keeping one of two genuinely different bindings
            // would mis-bind every monomorphisation of the other, but silently
            // keeping one of two identical bindings is exactly right.
            //
            // The mismatch case is user-reachable, so say what to actually do
            // about it.
            //
            // The display string ends up in a `//` comment in the generated JS
            // (see `import_comments`), so strip anything that could terminate
            // that comment and inject executable code. Doing it here covers
            // every consumer of the string, including the diagnostic below.
            let display = sanitize_js_comment(&match &import.js_namespace {
                Some(ns) => format!("{}.{}", ns.join("."), function.name),
                None => function.name.to_string(),
            });
            let meta = GenericImportMeta {
                aux_import,
                adapter_kind,
                catch,
                variadic,
                display: display.clone(),
            };
            match self.generic_import_bindings.entry(shim.to_string()) {
                Entry::Vacant(entry) => {
                    entry.insert(meta);
                }
                Entry::Occupied(entry) => {
                    // Landing on an occupied key is not on its own an error. The
                    // same import declared in two modules of one crate hashes to
                    // one key and produces byte-identical metadata; the ordinary
                    // import path deduplicates that case silently (via
                    // `function_imports`), and so must this one. Keep the entry
                    // already installed and let both monomorphisation groups
                    // resolve through it -- the bindings are the same, so which
                    // one wins is immaterial.
                    let previous = entry.get();
                    if previous.identity() != meta.identity() {
                        let previous = &previous.display;
                        bail!(
                            "two `experimental_generic_mono` imports collided on the shim key `{shim}`: \
                             `{previous}` and `{display}`.\n\
                             \n\
                             This happens when two generic imports have the same Rust function \
                             name, `js_name`, `js_namespace`, signature and module, and differ \
                             only in an attribute that does not contribute to the shim key — \
                             `catch`, `variadic`, `slice_to_array`, `structural`/`final`, or the \
                             getter/setter accessor kind. Binding both would be ambiguous, \
                             because a monomorphisation names only this key.\n\
                             \n\
                             To fix it, make the two imports distinguishable on the Rust side: \
                             give them different Rust function names (adding `js_name` to keep \
                             the JS-visible name), or different signatures."
                        );
                    }
                }
            }
            return Ok(());
        }

        let (import_id, _id) = match self.function_imports.get(shim) {
            Some(pair) => *pair,
            None => {
                if let Some(reexport_name) = import.reexport {
                    self.aux.reexports.insert(
                        reexport_name,
                        (
                            self.determine_import(
                                &import.module,
                                &import.js_namespace,
                                function.name,
                            )?,
                            generate_typescript,
                        ),
                    );
                }
                return Ok(());
            }
        };
        let mut descriptor = match self.descriptors.remove(shim) {
            None => {
                return Ok(());
            }
            Some(d) => d.unwrap_function(),
        };
        self.resolve_descriptor_function(&mut descriptor)?;

        // Perform two functions here. First we're saving off our adapter
        // signature, indicating what we think our import is going to be. Next
        // we're saving off other metadata indicating where this item is going
        // to be imported from. The `import_map` table will record, for each
        // import, what is getting hooked up to that slot of the import table
        // to the WebAssembly instance.
        let (id, aux_import) = match method {
            Some(data) => {
                let class =
                    self.determine_import(&import.module, &import.js_namespace, data.class)?;
                match data.kind {
                    // NB: `structural` is ignored for constructors since the
                    // js type isn't expected to change anyway.
                    decode::MethodKind::Constructor => {
                        let id = self.import_adapter(
                            import_id,
                            descriptor,
                            AdapterJsImportKind::Constructor,
                        )?;
                        (id, AuxImport::Value(AuxValue::Bare(class)))
                    }
                    decode::MethodKind::Operation(op) => {
                        let (import, method) =
                            self.determine_import_op(class, &function, structural, op)?;
                        let kind = if method {
                            AdapterJsImportKind::Method
                        } else {
                            AdapterJsImportKind::Normal
                        };
                        (self.import_adapter(import_id, descriptor, kind)?, import)
                    }
                }
            }

            // NB: `structural` is ignored for free functions since it's
            // expected that the binding isn't changing anyway.
            None => {
                let id = self.import_adapter(import_id, descriptor, AdapterJsImportKind::Normal)?;
                let aux_import = match import.module {
                    Some(ImportModule::RawNamed(PLACEHOLDER_MODULE)) => {
                        let intrinsic = function.name.parse()?;
                        if let Intrinsic::FunctionTable = intrinsic {
                            self.aux.function_table = self.module.tables.main_function_table()?;
                        }
                        if let Intrinsic::Reinit = intrinsic {
                            self.aux.uses_reinit = true;
                        }
                        AuxImport::Intrinsic(intrinsic)
                    }
                    _ => {
                        let js_import = self.determine_import(
                            &import.module,
                            &import.js_namespace,
                            function.name,
                        )?;

                        if let Some(reexport_name) = import.reexport {
                            self.aux
                                .reexports
                                .insert(reexport_name, (js_import.clone(), generate_typescript));
                        }

                        AuxImport::Value(AuxValue::Bare(js_import))
                    }
                };
                (id, aux_import)
            }
        };

        self.finish_import_binding(id, aux_import, catch, variadic, assert_no_shim, suspending);

        Ok(())
    }

    /// Finalize an import binding shared by the normal ([`import_function`])
    /// and generic ([`bind_generic_imports`]) paths: record the `catch` /
    /// `variadic` / `assert_no_shim` flags and register the `AuxImport`.
    ///
    /// `id` is the import adapter itself (used for the `variadic` flag), while
    /// `catch`/`assert_no_shim` apply to the most recently generated adapter
    /// shim (`self.adapters.implements.last()`); this distinction must be kept
    /// in sync with `js/mod.rs`. Neither of these affects the WebIDL interface.
    ///
    /// [`import_function`]: Self::import_function
    /// [`bind_generic_imports`]: Self::bind_generic_imports
    fn finish_import_binding(
        &mut self,
        id: AdapterId,
        aux_import: AuxImport,
        catch: bool,
        variadic: bool,
        assert_no_shim: bool,
        suspending: bool,
    ) {
        if variadic {
            self.aux.imports_with_variadic.insert(id);
        }

        // For `catch` once we see that we'll need an internal intrinsic later
        // for JS glue generation, so be sure to find that here.
        let adapter = self.adapters.implements.last().unwrap().2;
        if catch {
            self.aux.imports_with_catch.insert(adapter);
            if self.aux.exn_store.is_none() {
                self.find_exn_store();
            }
        }
        if assert_no_shim {
            self.aux.imports_with_assert_no_shim.insert(adapter);
        }
        if suspending {
            self.aux.imports_with_suspending.insert(adapter);
        }

        self.aux.import_map.insert(id, aux_import);
    }

    /// The `bool` returned indicates whether the imported value should be
    /// invoked as a method (first arg is implicitly `this`) or if the imported
    /// value is a simple function-like shim
    fn determine_import_op(
        &mut self,
        mut class: JsImport,
        function: &decode::Function<'_>,
        structural: bool,
        op: decode::Operation<'_>,
    ) -> Result<(AuxImport, bool), Error> {
        match op.kind {
            decode::OperationKind::Regular => {
                if op.is_static {
                    Ok((
                        AuxImport::ValueWithThis(class, function.name.to_string()),
                        false,
                    ))
                } else if structural {
                    Ok((
                        AuxImport::StructuralMethod(function.name.to_string()),
                        false,
                    ))
                } else {
                    class.fields.push("prototype".to_string());
                    class.fields.push(function.name.to_string());
                    Ok((AuxImport::Value(AuxValue::Bare(class)), true))
                }
            }

            decode::OperationKind::RegularThis => {
                bail!("RegularThis operation kind should only appear on exports, not imports")
            }

            decode::OperationKind::Getter(field) => {
                if structural {
                    if op.is_static {
                        Ok((
                            AuxImport::StructuralClassGetter(class, field.to_string()),
                            false,
                        ))
                    } else {
                        Ok((AuxImport::StructuralGetter(field.to_string()), false))
                    }
                } else {
                    let val = if op.is_static {
                        AuxValue::ClassGetter(class, field.to_string())
                    } else {
                        AuxValue::Getter(class, field.to_string())
                    };
                    Ok((AuxImport::Value(val), true))
                }
            }

            decode::OperationKind::Setter(field) => {
                if structural {
                    if op.is_static {
                        Ok((
                            AuxImport::StructuralClassSetter(class, field.to_string()),
                            false,
                        ))
                    } else {
                        Ok((AuxImport::StructuralSetter(field.to_string()), false))
                    }
                } else {
                    let val = if op.is_static {
                        AuxValue::ClassSetter(class, field.to_string())
                    } else {
                        AuxValue::Setter(class, field.to_string())
                    };
                    Ok((AuxImport::Value(val), true))
                }
            }

            decode::OperationKind::IndexingGetter => {
                if !structural {
                    bail!("indexing getters must always be structural");
                }
                if op.is_static {
                    Ok((AuxImport::IndexingGetterOfClass(class), false))
                } else {
                    Ok((AuxImport::IndexingGetterOfObject, false))
                }
            }

            decode::OperationKind::IndexingSetter => {
                if !structural {
                    bail!("indexing setters must always be structural");
                }
                if op.is_static {
                    Ok((AuxImport::IndexingSetterOfClass(class), false))
                } else {
                    Ok((AuxImport::IndexingSetterOfObject, false))
                }
            }

            decode::OperationKind::IndexingDeleter => {
                if !structural {
                    bail!("indexing deleters must always be structural");
                }
                if op.is_static {
                    Ok((AuxImport::IndexingDeleterOfClass(class), false))
                } else {
                    Ok((AuxImport::IndexingDeleterOfObject, false))
                }
            }
        }
    }

    fn import_static(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
        let decode::ImportKind::Static(static_) = import.kind else {
            unreachable!();
        };
        let generate_typescript = import.generate_typescript;
        let (import_id, _id) = match self.function_imports.get(static_.shim) {
            Some(pair) => *pair,
            None => {
                if let Some(reexport_name) = import.reexport {
                    self.aux.reexports.insert(
                        reexport_name,
                        (
                            self.determine_import(
                                &import.module,
                                &import.js_namespace,
                                static_.name,
                            )?,
                            generate_typescript,
                        ),
                    );
                }
                return Ok(());
            }
        };

        let mut descriptor = match self.descriptors.remove(static_.shim) {
            None => return Ok(()),
            Some(d) => d,
        };
        self.resolve_descriptor(&mut descriptor)?;
        let optional = matches!(descriptor, Descriptor::Option(_));

        // Register the signature of this imported shim
        let id = self.import_adapter(
            import_id,
            Function {
                arguments: Vec::new(),
                shim_idx: 0,
                ret: descriptor,
                inner_ret: None,
            },
            AdapterJsImportKind::Normal,
        )?;

        // And then save off that this function is is an instanceof shim for an
        // imported item.
        let js = self.determine_import(&import.module, &import.js_namespace, static_.name)?;

        if let Some(reexport_name) = import.reexport {
            self.aux
                .reexports
                .insert(reexport_name, (js.clone(), generate_typescript));
        }

        self.aux
            .import_map
            .insert(id, AuxImport::Static { js, optional });
        Ok(())
    }

    fn import_string(&mut self, string: &decode::ImportString<'_>) -> Result<(), Error> {
        let (import_id, _id) = match self.function_imports.get(string.shim) {
            Some(pair) => *pair,
            None => return Ok(()),
        };

        // Register the signature of this imported shim
        let id = self.import_adapter(
            import_id,
            Function {
                arguments: Vec::new(),
                shim_idx: 0,
                ret: Descriptor::Externref,
                inner_ret: None,
            },
            AdapterJsImportKind::Normal,
        )?;

        // And then save off that this function is is an instanceof shim for an
        // imported item.
        self.aux
            .import_map
            .insert(id, AuxImport::String(string.string.to_owned()));
        Ok(())
    }

    fn import_type(&mut self, import: decode::Import<'_>) -> Result<(), Error> {
        let decode::ImportKind::Type(type_) = import.kind else {
            unreachable!();
        };

        let generate_typescript = import.generate_typescript;
        let (import_id, _id) = match self.function_imports.get(type_.instanceof_shim) {
            Some(pair) => *pair,
            None => {
                // If this type has a reexport attribute but no instanceof shim (e.g., from inline_js),
                // we still need to return a JsImport so it can be re-exported
                if let Some(reexport_name) = import.reexport {
                    self.aux.reexports.insert(
                        reexport_name,
                        (
                            self.determine_import(
                                &import.module,
                                &import.js_namespace,
                                type_.name,
                            )?,
                            generate_typescript,
                        ),
                    );
                }
                return Ok(());
            }
        };

        // Register the signature of this imported shim
        let id = self.import_adapter(
            import_id,
            Function {
                arguments: vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
                shim_idx: 0,
                ret: Descriptor::Boolean,
                inner_ret: None,
            },
            AdapterJsImportKind::Normal,
        )?;

        // And then save off that this function is is an instanceof shim for an
        // imported item.
        let js_import = self.determine_import(&import.module, &import.js_namespace, type_.name)?;
        if let Some(reexport_name) = import.reexport {
            self.aux
                .reexports
                .insert(reexport_name, (js_import.clone(), generate_typescript));
        }
        self.aux
            .import_map
            .insert(id, AuxImport::Instanceof(js_import));
        Ok(())
    }

    fn string_enum(&mut self, string_enum: &decode::StringEnum<'_>) -> Result<(), Error> {
        let aux = AuxStringEnum {
            name: string_enum.name.to_string(),
            comments: concatenate_comments(&string_enum.comments),
            variant_values: string_enum
                .variant_values
                .iter()
                .map(|v| v.to_string())
                .collect(),
            generate_typescript: string_enum.generate_typescript,
            private: string_enum.private,
            js_namespace: string_enum
                .js_namespace
                .as_ref()
                .map(|ns| ns.iter().map(|s| s.to_string()).collect()),
        };
        let mut result = Ok(());
        self.aux
            .string_enums
            .entry(aux.name.clone())
            .and_modify(|existing| {
                result = Err(anyhow!("duplicate string enums:\n{existing:?}\n{aux:?}"));
            })
            .or_insert(aux);
        result
    }

    fn dynamic_union(&mut self, dynamic_union: &decode::DynamicUnion<'_>) -> Result<(), Error> {
        use crate::wit::AuxDynamicUnionVariant;

        let mut variants = Vec::new();

        // Add all string variants as pre-rendered literals.
        for string_variant in dynamic_union.variant_strings.iter() {
            variants.push(AuxDynamicUnionVariant::Literal(format!(
                "\"{string_variant}\""
            )));
        }

        // Add all type variants by looking up their descriptors. Defer the
        // TypeScript rendering to JS-generation time so that renamed types
        // resolve through `qualified_to_identifier`.
        for idx in 0..dynamic_union.variant_type_cnt {
            let descriptor_name = self.mangled(&wasm_bindgen_shared::dynamic_union_variant(
                dynamic_union.name,
                idx,
            ));
            let mut descriptor = self.descriptors.remove(&descriptor_name).ok_or_else(|| {
                anyhow!("dynamic union variant descriptor not found: {descriptor_name}")
            })?;
            self.resolve_descriptor(&mut descriptor)?;

            let mut builder = self.instruction_builder(false);
            builder.outgoing(&descriptor)?;
            if builder.output.len() != 1 {
                bail!(
                    "Expected exactly one AdapterType for dynamic union variant, got {}",
                    builder.output.len()
                );
            }
            variants.push(AuxDynamicUnionVariant::Type(builder.output[0].clone()));
        }

        let aux = AuxDynamicUnion {
            name: dynamic_union.name.to_string(),
            comments: concatenate_comments(&dynamic_union.comments),
            variants,
            generate_typescript: dynamic_union.generate_typescript,
            private: dynamic_union.private,
            fallback: dynamic_union.fallback,
        };
        let mut result = Ok(());
        self.aux
            .dynamic_unions
            .entry(aux.name.clone())
            .and_modify(|existing| {
                result = Err(anyhow!("duplicate dynamic unions:\n{existing:?}\n{aux:?}"));
            })
            .or_insert(aux);
        result
    }

    fn enum_(&mut self, enum_: decode::Enum<'_>) -> Result<(), Error> {
        let signed = enum_.signed;
        let qualified_name =
            wasm_bindgen_shared::qualified_name(enum_.js_namespace.as_deref(), enum_.name);
        // Like structs, colliding `private` enums are remapped to a numbered
        // identity; downstream code recomputes the qualified identity from
        // the bare name, so the suffix lives there.
        let identity = self.item_identity(&qualified_name);
        let bare_identity = format!("{}{}", enum_.name, &identity[qualified_name.len()..]);
        let aux = AuxEnum {
            name: bare_identity,
            comments: concatenate_comments(&enum_.comments),
            variants: enum_
                .variants
                .iter()
                .map(|v| {
                    let value = if signed {
                        v.value as i32 as i64
                    } else {
                        v.value as i64
                    };
                    (v.name.to_string(), value, concatenate_comments(&v.comments))
                })
                .collect(),
            generate_typescript: enum_.generate_typescript,
            js_namespace: enum_
                .js_namespace
                .as_ref()
                .map(|ns| ns.iter().map(|s| s.to_string()).collect()),
            private: enum_.private,
        };
        let mut result = Ok(());
        self.aux
            .enums
            .entry(identity)
            .and_modify(|existing| {
                result = Err(anyhow!("duplicate enums:\n{existing:?}\n{aux:?}"));
            })
            .or_insert(aux);
        result
    }

    fn struct_(&mut self, struct_: decode::Struct<'_>) -> Result<(), Error> {
        let qualified_name =
            wasm_bindgen_shared::qualified_name(struct_.js_namespace.as_deref(), struct_.name);
        // The class identity downstream; remapped to a numbered identity
        // (`Name2`, `Name3`, ...) when this is a colliding `private` struct
        // so that any number of same-named private classes can coexist.
        let identity = self.item_identity(&qualified_name);
        for field in struct_.fields {
            let getter = self.mangled(&wasm_bindgen_shared::struct_field_get(
                &qualified_name,
                field.name,
            ));
            let setter = self.mangled(&wasm_bindgen_shared::struct_field_set(
                &qualified_name,
                field.name,
            ));
            let mut descriptor = match self.descriptors.remove(&getter) {
                None => continue,
                Some(d) => d,
            };
            self.resolve_descriptor(&mut descriptor)?;

            // Register a webidl transformation for the getter
            let (getter_id, _) = self.function_exports[&getter];
            self.demangle_export(
                &getter,
                &wasm_bindgen_shared::struct_field_get(&identity, field.name),
            )?;
            let getter_descriptor = Function {
                arguments: vec![Descriptor::I32],
                shim_idx: 0,
                ret: descriptor.clone(),
                inner_ret: Some(descriptor.clone()),
            };
            let getter_id = self.export_adapter(getter_id, getter_descriptor)?;
            self.aux.export_map.insert(
                getter_id,
                AuxExport {
                    debug_name: format!("getter for `{}::{}`", struct_.name, field.name),
                    args: None,
                    asyncness: false,
                    jspi: false,
                    comments: concatenate_comments(&field.comments),
                    kind: AuxExportKind::Method {
                        class: identity.clone(),
                        name: field.name.to_string(),
                        receiver: AuxReceiverKind::Borrowed,
                        kind: AuxExportedMethodKind::Getter,
                    },
                    js_namespace: None,
                    generate_typescript: field.generate_typescript,
                    generate_jsdoc: field.generate_jsdoc,
                    variadic: false,
                    fn_ret_ty_override: None,
                    fn_ret_desc: None,
                },
            );

            // If present, register information for the setter as well.
            if field.readonly {
                continue;
            }

            let (setter_id, _) = self.function_exports[&setter];
            self.demangle_export(
                &setter,
                &wasm_bindgen_shared::struct_field_set(&identity, field.name),
            )?;
            let setter_descriptor = Function {
                arguments: vec![Descriptor::I32, descriptor],
                shim_idx: 0,
                ret: Descriptor::Unit,
                inner_ret: None,
            };
            let setter_id = self.export_adapter(setter_id, setter_descriptor)?;
            self.aux.export_map.insert(
                setter_id,
                AuxExport {
                    debug_name: format!("setter for `{}::{}`", struct_.name, field.name),
                    args: None,
                    asyncness: false,
                    jspi: false,
                    comments: concatenate_comments(&field.comments),
                    kind: AuxExportKind::Method {
                        class: identity.clone(),
                        name: field.name.to_string(),
                        receiver: AuxReceiverKind::Borrowed,
                        kind: AuxExportedMethodKind::Setter,
                    },
                    js_namespace: None,
                    generate_typescript: field.generate_typescript,
                    generate_jsdoc: field.generate_jsdoc,
                    variadic: false,
                    fn_ret_ty_override: None,
                    fn_ret_desc: None,
                },
            );
        }
        // The bare-name form of the identity: downstream code recomputes the
        // qualified identity as `qualified_name(js_namespace, name)`, so the
        // remapped suffix has to live on the bare name.
        let bare_identity = format!("{}{}", struct_.name, &identity[qualified_name.len()..]);

        // Restore the canonical (identity-derived) names of the `free` and
        // `upcast` exports; JS generation and the sp-restore transform
        // resolve both by recomputing these names from the class identity.
        let free = self.mangled(&wasm_bindgen_shared::free_function(&qualified_name));
        self.demangle_export(&free, &wasm_bindgen_shared::free_function(&identity))?;

        let mut parent_identity = None;
        if struct_.extends.is_some() || struct_.extends_js_class.is_some() {
            // Mirrors `parent_qualified_name` in JS generation and the
            // upcast symbol construction in the macro: the parent's bare JS
            // name is `extends_js_class`, defaulting to the last segment of
            // the `extends` Rust path.
            if let Some(bare) = struct_.extends_js_class.or(struct_.extends) {
                let mut parent = wasm_bindgen_shared::qualified_name(
                    struct_.extends_js_namespace.as_deref(),
                    bare,
                );
                let parent_qualified = parent.clone();
                // The macro only records the parent's JS name, not its
                // defining crate, so this resolves against the current
                // program's crate: a cross-crate `extends` of a renamed
                // `private` parent is not covered by the descriptor fix.
                self.resolve_item_ref(&mut parent, None)
                    .with_context(|| format!("resolving the parent class of `{qualified_name}`"))?;
                let upcast = self.mangled(&wasm_bindgen_shared::upcast_function(
                    &qualified_name,
                    &parent_qualified,
                ));
                self.demangle_export(
                    &upcast,
                    &wasm_bindgen_shared::upcast_function(&identity, &parent),
                )?;
                parent_identity = Some(parent);
            }
        }

        let aux = AuxStruct {
            name: bare_identity,
            comments: concatenate_comments(&struct_.comments),
            is_inspectable: struct_.is_inspectable,
            generate_typescript: struct_.generate_typescript,
            js_namespace: struct_
                .js_namespace
                .as_ref()
                .map(|ns| ns.iter().map(|s| s.to_string()).collect()),
            private: struct_.private,
            extends: struct_.extends.map(|s| s.to_string()),
            // The resolved parent identity: `parent_qualified_name` in JS
            // generation prefers this over `extends`, so a remapped parent
            // resolves to its final class identity.
            extends_js_class: parent_identity.map(|parent| {
                parent
                    .rsplit_once("__")
                    .map(|(_, bare)| bare.to_string())
                    .unwrap_or(parent)
            }),
            extends_js_namespace: struct_
                .extends_js_namespace
                .as_ref()
                .map(|ns| ns.iter().map(|s| s.to_string()).collect()),
        };
        self.aux.structs.push(aux);

        let ptr_desc = if self.memory64() {
            Descriptor::I64AsF64
        } else {
            Descriptor::I32
        };

        let wrap_constructor = self.demangle_import(
            &self.mangled(&wasm_bindgen_shared::new_function(&qualified_name)),
            &wasm_bindgen_shared::new_function(&identity),
        );
        self.add_aux_import_to_import_map(
            &wrap_constructor,
            vec![ptr_desc.clone()],
            Descriptor::Externref,
            // Class identity downstream must match the `exported_classes`
            // key.
            AuxImport::WrapInExportedClass(identity.clone()),
        )?;

        let unwrap_fn = self.demangle_import(
            &self.mangled(&wasm_bindgen_shared::unwrap_function(&qualified_name)),
            &wasm_bindgen_shared::unwrap_function(&identity),
        );
        self.add_aux_import_to_import_map(
            &unwrap_fn,
            vec![Descriptor::Ref(Box::new(Descriptor::Externref))],
            ptr_desc,
            AuxImport::UnwrapExportedClass(identity),
        )?;

        Ok(())
    }

    fn add_aux_import_to_import_map(
        &mut self,
        fn_name: &str,
        arguments: Vec<Descriptor>,
        ret: Descriptor,
        aux_import: AuxImport,
    ) -> Result<(), Error> {
        if let Some((import_id, _id)) = self.function_imports.get(fn_name).cloned() {
            let signature = Function {
                shim_idx: 0,
                arguments,
                ret,
                inner_ret: None,
            };
            let id = self.import_adapter(import_id, signature, AdapterJsImportKind::Normal)?;
            self.aux.import_map.insert(id, aux_import);
        }

        Ok(())
    }

    fn determine_import(
        &self,
        module: &Option<ImportModule<'_>>,
        js_namespace: &Option<Vec<String>>,
        item: &str,
    ) -> Result<JsImport, Error> {
        // Similar to `--target no-modules`, only allow vendor prefixes
        // basically for web apis, shouldn't be necessary for things like npm
        // packages or other imported items.
        let vendor_prefixes = self.vendor_prefixes.get(item);
        if let Some(vendor_prefixes) = vendor_prefixes {
            assert!(!vendor_prefixes.is_empty());

            if let Some(decode::ImportModule::Inline(_) | decode::ImportModule::Named(_)) = module {
                bail!(
                    "local JS snippets do not support vendor prefixes for \
                     the import of `{item}` with a polyfill of `{}`",
                    vendor_prefixes[0]
                );
            }
            if let Some(decode::ImportModule::RawNamed(module)) = module {
                bail!(
                    "import of `{item}` from `{module}` has a polyfill of `{}` listed, but
                     vendor prefixes aren't supported when importing from modules",
                    vendor_prefixes[0],
                );
            }
            if let Some(ns) = js_namespace {
                bail!(
                    "import of `{item}` through js namespace `{}` isn't supported \
                     right now when it lists a polyfill",
                    ns.join(".")
                );
            }
            return Ok(JsImport {
                name: JsImportName::VendorPrefixed {
                    name: item.to_string(),
                    prefixes: vendor_prefixes.clone(),
                },
                fields: Vec::new(),
            });
        }

        let (name, fields) = match js_namespace {
            Some(ref ns) => {
                let mut tail = ns[1..].to_owned();
                tail.push(item.to_string());
                (ns[0].to_owned(), tail)
            }
            None => (item.to_owned(), Vec::new()),
        };

        let name = match module {
            Some(decode::ImportModule::Named(module)) => JsImportName::LocalModule {
                module: module.to_string(),
                name,
            },
            Some(decode::ImportModule::RawNamed(module)) => JsImportName::Module {
                module: module.to_string(),
                name,
            },
            Some(decode::ImportModule::Inline(idx)) => {
                let offset = self
                    .aux
                    .snippets
                    .get(self.unique_crate_identifier)
                    .map(|s| s.len())
                    .unwrap_or(0);
                JsImportName::InlineJs {
                    unique_crate_identifier: self.unique_crate_identifier.to_string(),
                    snippet_idx_in_crate: *idx as usize + offset,
                    name,
                }
            }
            None => JsImportName::Global { name },
        };
        Ok(JsImport { name, fields })
    }

    /// Perform a small verification pass over the module to perform some
    /// internal sanity checks.
    fn verify(&self) -> Result<(), Error> {
        // First up verify that all imports in the Wasm module from our
        // `$PLACEHOLDER_MODULE` are connected to an adapter via the
        // `implements` section.
        let mut implemented = HashMap::new();
        for (core, _, adapter) in self.adapters.implements.iter() {
            implemented.insert(core, adapter);
        }
        for import in self.module.imports.iter() {
            if import.module != PLACEHOLDER_MODULE {
                continue;
            }
            match import.kind {
                walrus::ImportKind::Function(_) => {}
                _ => bail!("import from `{PLACEHOLDER_MODULE}` was not a function"),
            }

            // These are special intrinsics which were handled in the descriptor
            // phase, but we don't have an implementation for them. We don't
            // need to error about them in this verification pass though,
            // having them lingering in the module is normal.
            if import.name == "__wbindgen_describe"
                || import.name == "__wbindgen_describe_generic_import"
            {
                continue;
            }
            if implemented.remove(&import.id()).is_none() {
                bail!("import of `{}` doesn't have an adapter listed", import.name);
            }
        }
        if !implemented.is_empty() {
            bail!("more implementations listed than imports");
        }

        // Next up verify that all imported adapter functions have a listing of
        // where they're imported from.
        let mut imports_counted = 0;
        for (id, adapter) in self.adapters.adapters.iter() {
            let name = match &adapter.kind {
                AdapterKind::Import { name, .. } => name,
                AdapterKind::Local { .. } => continue,
            };
            if !self.aux.import_map.contains_key(id) {
                bail!("import of `{name}` doesn't have an import map item listed");
            }

            imports_counted += 1;
        }
        // Make sure there's no extraneous adapters that weren't actually
        // imported in the module.
        if self.aux.import_map.len() != imports_counted {
            bail!("import map is larger than the number of imports");
        }

        // Make sure all entries in the export map have an adapter.
        // The adapter may not be in the statically known exports list if it's
        // generated by us from a closure table entry.
        for id in self.aux.export_map.keys() {
            ensure!(
                self.adapters.adapters.contains_key(id),
                "export map has an entry that the adapters map does not"
            );
        }

        Ok(())
    }

    /// Creates an import adapter for the `import` which will have the given
    /// `signature`.
    ///
    /// Note that the JS function imported will be invoked as `kind`.
    fn import_adapter(
        &mut self,
        import: ImportId,
        mut signature: Function,
        kind: AdapterJsImportKind,
    ) -> Result<AdapterId, Error> {
        let import = self.module.imports.get(import);
        let import_name = import.name.clone();
        let import_id = import.id();
        let core_id = match import.kind {
            walrus::ImportKind::Function(f) => f,
            _ => bail!("bound import must be assigned to function"),
        };
        let memory64 = self.memory64();
        self.normalize_memory64_signature(&mut signature, core_id);

        // Name this import in any binding failure below. Imports manufactured by
        // `bind_generic_imports` are named after a sort index that means nothing
        // to a user, so use the name recorded for them there.
        let display_name = self
            .generic_import_display_names
            .get(&import_id)
            .cloned()
            .unwrap_or_else(|| import_name.clone());

        // Process the returned type first to see if it needs an out-pointer. This
        // happens if the results of the incoming arguments translated to Wasm take
        // up more than one type.
        let mut ret = self.instruction_builder(true);
        ret.incoming(&signature.ret).with_context(|| {
            format!("failed to generate a binding for the return value of `{display_name}`")
        })?;
        let uses_retptr = ret.output.len() > 1;

        // Process the argument next, allocating space of the return value if one
        // was present. Additionally configure the `module` and `adapters` to allow
        // usage of closures going out to the import.
        let mut args = ret.cx.instruction_builder(false);
        if uses_retptr {
            args.input.push(if memory64 {
                AdapterType::F64
            } else {
                AdapterType::I32
            });
        }
        for (i, arg) in signature.arguments.iter().enumerate() {
            // Without this an unsupported type is reported with nothing to locate
            // it by. Note the closure form keeps the happy path allocation-free.
            args.outgoing(arg).with_context(|| {
                format!(
                    "failed to generate a binding for argument {} of `{display_name}`",
                    i + 1
                )
            })?;
        }

        // Build up the list of instructions for our adapter function. We start out
        // with all the outgoing instructions which convert all Wasm params to the
        // desired types to call our import...
        let mut instructions = args.instructions;

        // ... and then we actually call our import. We synthesize an adapter
        // definition for it with the appropriate types here on the fly.
        let f = args.cx.adapters.append(
            args.output,
            ret.input,
            vec![],
            AdapterKind::Import {
                name: import_name,
                kind,
            },
        );
        instructions.push(InstructionData {
            instr: Instruction::CallAdapter(f),
            stack_change: StackChange::Unknown,
        });

        // ... and then we follow up with a conversion of the incoming type
        // back to wasm.
        instructions.extend(ret.instructions);

        // ... and if a return pointer is in use then we need to store the types on
        // the stack into the Wasm return pointer. Note that we iterate in reverse
        // here because the last result is the top value on the stack.
        let results = if uses_retptr {
            let mem = args.cx.memory()?;
            for (i, ty) in ret.output.into_iter().enumerate().rev() {
                instructions.push(InstructionData {
                    instr: Instruction::StoreRetptr { offset: i, ty, mem },
                    stack_change: StackChange::Modified {
                        pushed: 0,
                        popped: 1,
                    },
                });
            }
            Vec::new()
        } else {
            ret.output
        };
        let id = args.cx.adapters.append(
            args.input,
            results,
            vec![],
            AdapterKind::Local { instructions },
        );
        args.cx.adapters.implements.push((import_id, core_id, id));
        Ok(f)
    }

    /// Creates an adapter function for the `export` given to have the
    /// `signature` specified.
    fn export_adapter(
        &mut self,
        mut export: ExportId,
        mut signature: Function,
    ) -> Result<AdapterId, Error> {
        // Same export might be requested multiple times due to codegen-units,
        // or because wasm-ld ICF merged invoke functions for different closure
        // types into the same function. Only reuse an existing adapter if the
        // signature also matches (ignoring shim_idx which varies across
        // codegen-units).
        let sig_key = (
            signature.arguments.clone(),
            signature.ret.clone(),
            signature.inner_ret.clone(),
        );
        if let Some((_, adapter_id)) = self
            .adapters
            .exports
            .iter()
            .find(|(export_id, _)| *export_id == export)
        {
            if self.export_adapter_sigs.get(adapter_id) == Some(&sig_key) {
                // Same ExportId and signature (codegen-units duplicate).
                return Ok(*adapter_id);
            } else {
                // Same ExportId but different signature: ICF merged two different
                // closure types. Create a new export with a unique name so each
                // adapter gets its own JS function.
                let old_export = self.module.exports.get(export);
                let name = format!("{}_{}", old_export.name, self.adapters.exports.len());
                let func_id = match old_export.item {
                    walrus::ExportItem::Function(f) => f,
                    _ => unreachable!(),
                };
                export = self.module.exports.add(&name, func_id);
            }
        }

        let core_id = match self.module.exports.get(export).item {
            walrus::ExportItem::Function(f) => f,
            _ => bail!("bound export must be assigned to function"),
        };
        self.normalize_memory64_signature(&mut signature, core_id);

        // Name the export in any binding failure below; see `import_adapter` for
        // why this context matters.
        let export_name = self.module.exports.get(export).name.clone();

        // Figure out how to translate all the incoming arguments ...
        let mut args = self.instruction_builder(false);
        for (i, arg) in signature.arguments.iter().enumerate() {
            args.incoming(arg).with_context(|| {
                format!(
                    "failed to generate a binding for argument {} of `{export_name}`",
                    i + 1
                )
            })?;
        }

        // ... then the returned value being translated back

        let inner_ret_output = if let Some(sig_inner_ret) = &signature.inner_ret {
            let mut inner_ret = args.cx.instruction_builder(true);
            inner_ret.outgoing(sig_inner_ret).with_context(|| {
                format!("failed to generate a binding for the return value of `{export_name}`")
            })?;
            inner_ret.output
        } else {
            vec![]
        };

        let mut ret = args.cx.instruction_builder(true);
        ret.outgoing(&signature.ret).with_context(|| {
            format!("failed to generate a binding for the return value of `{export_name}`")
        })?;
        let uses_retptr = ret.input.len() > 1;

        // Our instruction stream starts out with the return pointer as the first
        // argument to the Wasm function, if one is in use. Then we convert
        // everything to Wasm types.
        //
        // After calling the core Wasm function we need to load all the return
        // pointer arguments if there were any, otherwise we simply convert
        // everything into the outgoing arguments.
        let mut instructions = Vec::new();
        if uses_retptr {
            let size = ret.input.iter().fold(0, |sum, ty| {
                let size = match ty {
                    AdapterType::I32 => 4,
                    AdapterType::I64 => 8,
                    AdapterType::F32 => 4,
                    AdapterType::F64 => 8,
                    _ => panic!("unsupported type in retptr {ty:?}"),
                };
                let sum_rounded_up = (sum + (size - 1)) & (!(size - 1));
                sum_rounded_up + size
            });
            // Round the number of bytes up to a 16-byte alignment to ensure the
            // stack pointer is always 16-byte aligned (which LLVM currently
            // requires).
            let size = (size + 15) & (!15);
            instructions.push(InstructionData {
                instr: Instruction::Retptr { size },
                stack_change: StackChange::Modified {
                    pushed: 1,
                    popped: 0,
                },
            });
        }
        instructions.extend(args.instructions);
        instructions.push(InstructionData {
            instr: Instruction::CallExport(export),
            stack_change: StackChange::Unknown,
        });
        if uses_retptr {
            let mem = ret.cx.memory()?;
            let mut unpacker = StructUnpacker::new();
            for ty in ret.input.into_iter() {
                let offset = unpacker.read_ty(&ty)?;
                instructions.push(InstructionData {
                    instr: Instruction::LoadRetptr { offset, ty, mem },
                    stack_change: StackChange::Modified {
                        pushed: 1,
                        popped: 0,
                    },
                });
            }
        }
        instructions.extend(ret.instructions);

        let id = ret.cx.adapters.append(
            args.input,
            ret.output,
            inner_ret_output,
            AdapterKind::Local { instructions },
        );

        self.adapters.exports.push((export, id));
        self.export_adapter_sigs.insert(id, sig_key);

        Ok(id)
    }

    fn normalize_memory64_signature(&self, signature: &mut Function, core_id: FunctionId) {
        if !self.memory64() {
            return;
        }

        let ty = self.module.funcs.get(core_id).ty();
        let (params, results) = self.module.types.params_results(ty);

        for (descriptor, wasm) in signature.arguments.iter_mut().zip(params.iter().copied()) {
            Self::normalize_memory64_descriptor(descriptor, wasm);
        }

        if let [result] = results {
            Self::normalize_memory64_descriptor(&mut signature.ret, *result);
        }
    }

    fn normalize_memory64_descriptor(descriptor: &mut Descriptor, wasm: walrus::ValType) {
        if wasm != walrus::ValType::F64 {
            return;
        }

        if let Descriptor::Option(inner) = descriptor {
            match inner.as_mut() {
                Descriptor::I64 => **inner = Descriptor::I64AsF64,
                Descriptor::U64 => **inner = Descriptor::U64AsF64,
                _ => {}
            }
        }
    }

    fn instruction_builder<'b>(&'b mut self, return_position: bool) -> InstructionBuilder<'b, 'a> {
        InstructionBuilder {
            cx: self,
            input: Vec::new(),
            output: Vec::new(),
            instructions: Vec::new(),
            return_position,
        }
    }

    fn malloc(&self) -> Result<FunctionId, Error> {
        self.function_exports
            .get("__wbindgen_malloc")
            .cloned()
            .map(|p| p.1)
            .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_malloc` in module"))
    }

    fn realloc(&self) -> Option<FunctionId> {
        self.function_exports
            .get("__wbindgen_realloc")
            .cloned()
            .map(|p| p.1)
    }

    fn free(&self) -> Result<FunctionId, Error> {
        self.function_exports
            .get("__wbindgen_free")
            .cloned()
            .map(|p| p.1)
            .ok_or_else(|| anyhow!("failed to find declaration of `__wbindgen_free` in module"))
    }

    fn thread_destroy(&self) -> Option<FunctionId> {
        self.function_exports
            .get("__wbindgen_thread_destroy")
            .cloned()
            .map(|p| p.1)
    }

    fn memory(&self) -> Result<MemoryId, Error> {
        self.memory
            .ok_or_else(|| anyhow!("failed to find memory declaration in module"))
    }

    /// Returns `true` if the module's primary memory is 64-bit.
    fn memory64(&self) -> bool {
        self.memory
            .map(|id| self.module.memories.get(id).memory64)
            .unwrap_or(false)
    }

    /// Removes the export item for all `__wbindgen` intrinsics which are
    /// generally only purely internal helpers.
    ///
    /// References to these functions are preserved through adapter instructions
    /// if necessary, otherwise they can all be gc'd out. By the time this
    /// function is called our discovery of these intrinsics has completed and
    /// there's no need to keep around these exports.
    fn unexport_intrinsics(&mut self) {
        let mut to_remove = Vec::new();
        for export in self.module.exports.iter() {
            match export.name.as_str() {
                n if n.starts_with("__wbindgen") => {
                    to_remove.push(export.id());
                }
                _ => {}
            }
        }
        for id in to_remove {
            self.module.exports.delete(id);
        }
    }

    /// Attempts to locate the `__wbindgen_exn_store` intrinsic and stores it in
    /// our auxiliary information.
    ///
    /// This is only invoked if the intrinsic will actually be needed for JS
    /// glue generation somewhere.
    fn find_exn_store(&mut self) {
        self.aux.exn_store = self
            .module
            .exports
            .iter()
            .find(|e| e.name == "__wbindgen_exn_store")
            .and_then(|e| match e.item {
                walrus::ExportItem::Function(f) => Some(f),
                _ => None,
            });
    }

    fn find_destroy_closure(&mut self) {
        self.aux.destroy_closure = self
            .module
            .exports
            .iter()
            .find(|e| e.name == "__wbindgen_destroy_closure")
            .and_then(|e| match e.item {
                walrus::ExportItem::Function(f) => Some(f),
                _ => None,
            });
    }
}

/// Verifies exported constructor return value is not a JS primitive type
fn verify_constructor_return(class: &str, ret: &Descriptor) -> Result<(), Error> {
    match ret {
        Descriptor::I8
        | Descriptor::U8
        | Descriptor::ClampedU8
        | Descriptor::I16
        | Descriptor::U16
        | Descriptor::I32
        | Descriptor::U32
        | Descriptor::F32
        | Descriptor::F64
        | Descriptor::I64
        | Descriptor::U64
        | Descriptor::Boolean
        | Descriptor::Char
        | Descriptor::CachedString
        | Descriptor::String
        | Descriptor::Option(_)
        | Descriptor::Enum { .. }
        | Descriptor::Unit
        | Descriptor::RawPointer => {
            bail!("The constructor for class `{class}` tries to return a JS primitive type, which would cause the return value to be ignored. Use a builder instead (remove the `constructor` attribute).");
        }
        Descriptor::Result(ref d) | Descriptor::Ref(ref d) | Descriptor::RefMut(ref d) => {
            verify_constructor_return(class, d)
        }
        _ => Ok(()),
    }
}

/// Extract all of the `Program`s encoded in our custom section.
///
/// `program_storage` is used to squirrel away the raw bytes of the custom
///  section, so that they can be referenced by the `Program`s we return.
pub fn extract_programs<'a>(
    module: &mut Module,
    program_storage: &'a mut Vec<Vec<u8>>,
) -> Result<Vec<decode::Program<'a>>, Error> {
    let my_version = wasm_bindgen_shared::version();
    assert!(program_storage.is_empty());

    while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") {
        log::debug!(
            "custom section '{}' looks like a Wasm bindgen section",
            raw.name
        );
        program_storage.push(raw.data);
    }

    let mut ret = Vec::new();
    for program in program_storage.iter() {
        let mut payload = &program[..];
        while let Some(data) = get_remaining(&mut payload) {
            // Historical versions of wasm-bindgen have used JSON as the custom
            // data section format. Newer versions, however, are using a custom
            // serialization protocol that looks much more like the Wasm spec.
            //
            // We, however, want a sanity check to ensure that if we're running
            // against the wrong wasm-bindgen we get a nicer error than an
            // internal decode error. To that end we continue to verify a tiny
            // bit of json at the beginning of each blob before moving to the
            // next blob. This should keep us compatible with older wasm-bindgen
            // instances as well as forward-compatible for now.
            //
            // Note, though, that as `wasm-pack` picks up steam it's hoped we
            // can just delete this entirely. The `wasm-pack` project already
            // manages versions for us, so we in theory should need this check
            // less and less over time.
            if let Some(their_version) = verify_schema_matches(data)? {
                bail!(
                    "

it looks like the Rust project used to create this Wasm file was linked against
version of wasm-bindgen that uses a different bindgen format than this binary:

  rust Wasm file schema version: {their_version}
     this binary schema version: {my_version}

Currently the bindgen format is unstable enough that these two schema versions
must exactly match. You can accomplish this by either updating this binary or
the wasm-bindgen dependency in the Rust project.

You should be able to update the wasm-bindgen dependency with:

    cargo update -p wasm-bindgen --precise {my_version}

don't forget to recompile your Wasm file! Alternatively, you can update the
binary with:

    cargo install -f wasm-bindgen-cli --version {their_version}

if this warning fails to go away though and you're not sure what to do feel free
to open an issue at https://github.com/wasm-bindgen/wasm-bindgen/issues!
"
                );
            }
            let next = get_remaining(&mut payload).unwrap();
            log::debug!("found a program of length {}", next.len());
            ret.push(<decode::Program as decode::Decode>::decode_all(next));
        }
    }

    // If the module clearly went through the wasm-bindgen macro (shim imports
    // or descriptor exports are present) but no programs were found, the
    // custom section was stripped by some post-link processing. Give an
    // actionable error now rather than a confusing missing-adapter error
    // later.
    if ret.is_empty() {
        let has_shims = module
            .imports
            .iter()
            .any(|i| i.module == PLACEHOLDER_MODULE)
            || module
                .exports
                .iter()
                .any(|e| e.name.starts_with("__wbindgen_describe"));
        if has_shims {
            bail!(
                "the `__wasm_bindgen_unstable` custom section is missing, but the module \
                 still contains wasm-bindgen shims

This section carries the metadata wasm-bindgen needs to process the module, and
is usually lost by running a post-processing tool that strips custom sections,
such as `llvm-objcopy --strip-all` (which removes all custom sections since
LLVM 23) or `wasm-strip`, on the module before `wasm-bindgen`.

Make sure `wasm-bindgen` runs on the Wasm file directly produced by the linker,
before any such stripping tool."
            );
        }
    }

    Ok(ret)
}

fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> {
    if data.is_empty() {
        return None;
    }
    let len = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
    let (a, b) = data[4..].split_at(len);
    *data = b;
    Some(a)
}

fn verify_schema_matches(data: &[u8]) -> Result<Option<&str>, Error> {
    macro_rules! bad {
        () => {
            bail!("failed to decode what looked like wasm-bindgen data")
        };
    }
    let data = match str::from_utf8(data) {
        Ok(s) => s,
        Err(_) => bad!(),
    };
    log::debug!("found version specifier {data}");
    if !data.starts_with('{') || !data.ends_with('}') {
        bad!()
    }
    let needle = "\"schema_version\":\"";
    let rest = match data.find(needle) {
        Some(i) => &data[i + needle.len()..],
        None => bad!(),
    };
    let their_schema_version = match rest.find('"') {
        Some(i) => &rest[..i],
        None => bad!(),
    };
    if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION {
        return Ok(None);
    }
    let needle = "\"version\":\"";
    let rest = match data.find(needle) {
        Some(i) => &data[i + needle.len()..],
        None => bad!(),
    };
    let their_version = match rest.find('"') {
        Some(i) => &rest[..i],
        None => bad!(),
    };
    Ok(Some(their_version))
}

fn concatenate_comments(comments: &[&str]) -> String {
    comments.join("\n")
}

/// Make a user-controlled string safe to embed in a generated JS comment.
///
/// `js_name`/`js_namespace` are arbitrary strings from an attribute, and the
/// generic-import machinery echoes them into a `//` line comment in the emitted
/// `*_bg.js`. A line terminator would close that comment and hand the rest of
/// the string to the JS parser as code, and `*/` would close a block comment, so
/// neutralise both. All four of JS's line terminators are covered: `\n`, `\r`,
/// U+2028 LINE SEPARATOR and U+2029 PARAGRAPH SEPARATOR.
fn sanitize_js_comment(s: &str) -> String {
    s.replace(['\n', '\r', '\u{2028}', '\u{2029}'], " ")
        .replace("*/", "* /")
}

/// The C struct packing algorithm, in terms of u32.
struct StructUnpacker {
    next_offset: usize,
}

impl StructUnpacker {
    fn new() -> Self {
        Self { next_offset: 0 }
    }
    fn align_up(&mut self, alignment_pow2: usize) -> usize {
        let mask = alignment_pow2 - 1;
        self.next_offset = (self.next_offset + mask) & (!mask);
        self.next_offset
    }
    fn append(&mut self, quads: usize, alignment_pow2: usize) -> usize {
        let ret = self.align_up(alignment_pow2);
        self.next_offset += quads;
        ret
    }
    /// Returns the offset for this member, with the offset in multiples of u32.
    fn read_ty(&mut self, ty: &AdapterType) -> Result<usize, Error> {
        let (quads, alignment) = match ty {
            AdapterType::I32 | AdapterType::U32 | AdapterType::F32 => (1, 1),
            AdapterType::I64 | AdapterType::U64 | AdapterType::F64 => (2, 2),
            other => bail!("invalid aggregate return type {other:?}"),
        };
        Ok(self.append(quads, alignment))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_struct_packer() {
        let mut unpacker = StructUnpacker::new();
        let i32___ = &AdapterType::I32;
        let double = &AdapterType::F64;
        let mut read_ty = |ty| unpacker.read_ty(ty).unwrap();
        assert_eq!(read_ty(i32___), 0); // u32
        assert_eq!(read_ty(i32___), 1); // u32
        assert_eq!(read_ty(double), 2); // f64, already aligned
        assert_eq!(read_ty(i32___), 4); // u32, already aligned
        assert_eq!(read_ty(double), 6); // f64, NOT already aligned, skips up to offset 6
    }

    #[test]
    fn normalize_memory64_descriptor_only_rewrites_explicit_number_abi_options() {
        let mut number_option = Descriptor::Option(Box::new(Descriptor::I64));
        Context::normalize_memory64_descriptor(&mut number_option, walrus::ValType::F64);
        assert_eq!(
            number_option,
            Descriptor::Option(Box::new(Descriptor::I64AsF64))
        );

        let mut exact_i64 = Descriptor::I64;
        Context::normalize_memory64_descriptor(&mut exact_i64, walrus::ValType::I64);
        assert_eq!(exact_i64, Descriptor::I64);

        let mut exact_option = Descriptor::Option(Box::new(Descriptor::I64));
        Context::normalize_memory64_descriptor(&mut exact_option, walrus::ValType::I64);
        assert_eq!(exact_option, Descriptor::Option(Box::new(Descriptor::I64)));
    }

    /// Build a minimal module containing a 32-bit or 64-bit memory and an exported
    /// C `main` shim with the given signature `(i32 argc, argv) -> i32`.
    fn make_main_module(memory64: bool, argv_ty: walrus::ValType) -> (Module, MemoryId) {
        let mut config = walrus::ModuleConfig::new();
        config.generate_producers_section(false);
        let mut module = walrus::Module::with_config(config);

        let mem = module.memories.add_local(false, memory64, 1, None, None);

        // Build `main(i32 argc, argv) -> i32`.
        let argc = module.locals.add(walrus::ValType::I32);
        let argv = module.locals.add(argv_ty);
        let mut builder = walrus::FunctionBuilder::new(
            &mut module.types,
            &[walrus::ValType::I32, argv_ty],
            &[walrus::ValType::I32],
        );
        builder.func_body().i32_const(0);
        let main_id = builder.finish(vec![argc, argv], &mut module.funcs);
        module.exports.add("main", main_id);

        (module, mem)
    }

    fn run_discover_main(module: &mut Module, memory: MemoryId) -> bool {
        let mut cx = Context {
            start_found: false,
            module,
            adapters: Default::default(),
            aux: Default::default(),
            function_exports: Default::default(),
            function_imports: Default::default(),
            memory: Some(memory),
            vendor_prefixes: Default::default(),
            unique_crate_identifier: "",
            descriptors: Default::default(),
            externref_enabled: false,
            thread_count: None,
            support_start: true,
            linked_modules: false,
            export_adapter_sigs: Default::default(),
            generic_import_bindings: Default::default(),
            pending_generic_imports: Default::default(),
            generic_import_display_names: Default::default(),
            item_identities: Default::default(),
            ambiguous_items: Default::default(),
            demangled_exports: Default::default(),
        };
        cx.discover_main().unwrap();
        cx.start_found
    }

    /// Collect argv and argc constants.
    fn start_fn_consts(module: &Module) -> Vec<walrus::ir::Value> {
        let start = module
            .start
            .expect("discover_main should register a start function");
        let func = module.funcs.get(start);
        let walrus::FunctionKind::Local(local) = &func.kind else {
            panic!("start wrapper should be a local function");
        };
        local
            .block(local.entry_block())
            .instrs
            .iter()
            .filter_map(|(instr, _)| match instr {
                walrus::ir::Instr::Const(c) => Some(c.value),
                _ => None,
            })
            .collect()
    }

    /// On wasm64 the C `main` shim is `main(i32 argc, i64 argv) -> i32`
    #[test]
    fn discover_main_handles_wasm64_argv_pointer() {
        let (mut module, mem) = make_main_module(true, walrus::ValType::I64);
        assert!(
            run_discover_main(&mut module, mem),
            "wasm64 `main(i32, i64) -> i32` should be discovered as the entry point"
        );

        assert!(
            matches!(
                start_fn_consts(&module).as_slice(),
                [walrus::ir::Value::I32(0), walrus::ir::Value::I64(0)]
            ),
            "wasm64 start wrapper should pass (i32 argc, i64 argv) = (0, 0)"
        );
    }

    /// On wasm32 the C `main` shim is `main(i32 argc, i32 argv) -> i32`
    #[test]
    fn discover_main_handles_wasm32_argv_pointer() {
        let (mut module, mem) = make_main_module(false, walrus::ValType::I32);
        assert!(
            run_discover_main(&mut module, mem),
            "wasm32 `main(i32, i32) -> i32` should be discovered as the entry point"
        );
        assert!(
            matches!(
                start_fn_consts(&module).as_slice(),
                [walrus::ir::Value::I32(0), walrus::ir::Value::I32(0)]
            ),
            "wasm32 start wrapper should pass (i32 argc, i32 argv) = (0, 0)"
        );
    }

    /// Don't mistake a `main(i32, i32) -> i32` as the entrypoint on wasm64
    #[test]
    fn discover_main_ignores_wrong_argv_width_on_wasm64() {
        let (mut module, mem) = make_main_module(true, walrus::ValType::I32);
        assert!(
            !run_discover_main(&mut module, mem),
            "on wasm64 a 32-bit-argv `main` must not be treated as the entry point"
        );
    }
}
