wasm-bindgen-cli-support 0.2.58

Shared support for the wasm-bindgen-cli package, an internal dependency
Documentation
use crate::descriptor::VectorKind;
use crate::intrinsic::Intrinsic;
use crate::wit::{Adapter, AdapterId, AdapterJsImportKind, AuxValue};
use crate::wit::{AdapterKind, Instruction, InstructionData};
use crate::wit::{AuxEnum, AuxExport, AuxExportKind, AuxImport, AuxStruct};
use crate::wit::{JsImport, JsImportName, NonstandardWitSection, WasmBindgenAux};
use crate::{Bindgen, EncodeInto, OutputMode};
use anyhow::{anyhow, bail, Context as _, Error};
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt;
use std::fs;
use std::path::{Path, PathBuf};
use walrus::{FunctionId, ImportId, MemoryId, Module, TableId};

mod binding;

pub struct Context<'a> {
    globals: String,
    imports_post: String,
    typescript: String,
    exposed_globals: Option<HashSet<Cow<'static, str>>>,
    next_export_idx: usize,
    config: &'a Bindgen,
    pub module: &'a mut Module,
    aux: &'a WasmBindgenAux,
    wit: &'a NonstandardWitSection,

    /// A map representing the `import` statements we'll be generating in the JS
    /// glue. The key is the module we're importing from and the value is the
    /// list of identifier we're importing from the module, with optional
    /// renames for each identifier.
    js_imports: HashMap<String, Vec<(String, Option<String>)>>,

    /// A map of each wasm import and what JS to hook up to it.
    wasm_import_definitions: HashMap<ImportId, String>,

    /// A map from an import to the name we've locally imported it as.
    imported_names: HashMap<JsImportName, String>,

    /// A set of all defined identifiers through either exports or imports to
    /// the number of times they've been used, used to generate new
    /// identifiers.
    defined_identifiers: HashMap<String, usize>,

    exported_classes: Option<BTreeMap<String, ExportedClass>>,

    /// A map of the name of npm dependencies we've loaded so far to the path
    /// they're defined in as well as their version specification.
    pub npm_dependencies: HashMap<String, (PathBuf, String)>,

    /// A mapping of a index for memories as we see them. Used in function
    /// names.
    memory_indices: HashMap<MemoryId, usize>,
    table_indices: HashMap<TableId, usize>,
}

#[derive(Default)]
pub struct ExportedClass {
    comments: String,
    contents: String,
    typescript: String,
    has_constructor: bool,
    wrap_needed: bool,
    /// Whether to generate helper methods for inspecting the class
    is_inspectable: bool,
    /// All readable properties of the class
    readable_properties: Vec<String>,
    /// Map from field name to type as a string plus whether it has a setter
    typescript_fields: HashMap<String, (String, bool)>,
}

const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
// Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate
const INITIAL_HEAP_OFFSET: usize = 32;

impl<'a> Context<'a> {
    pub fn new(
        module: &'a mut Module,
        config: &'a Bindgen,
        wit: &'a NonstandardWitSection,
        aux: &'a WasmBindgenAux,
    ) -> Result<Context<'a>, Error> {
        Ok(Context {
            globals: String::new(),
            imports_post: String::new(),
            typescript: "/* tslint:disable */\n/* eslint-disable */\n".to_string(),
            exposed_globals: Some(Default::default()),
            imported_names: Default::default(),
            js_imports: Default::default(),
            defined_identifiers: Default::default(),
            wasm_import_definitions: Default::default(),
            exported_classes: Some(Default::default()),
            config,
            module,
            npm_dependencies: Default::default(),
            next_export_idx: 0,
            wit,
            aux,
            memory_indices: Default::default(),
            table_indices: Default::default(),
        })
    }

    fn should_write_global(&mut self, name: impl Into<Cow<'static, str>>) -> bool {
        self.exposed_globals.as_mut().unwrap().insert(name.into())
    }

    fn export(
        &mut self,
        export_name: &str,
        contents: &str,
        comments: Option<String>,
    ) -> Result<(), Error> {
        let definition_name = generate_identifier(export_name, &mut self.defined_identifiers);
        if contents.starts_with("class") && definition_name != export_name {
            bail!("cannot shadow already defined class `{}`", export_name);
        }

        let contents = contents.trim();
        if let Some(ref c) = comments {
            self.globals.push_str(c);
            self.typescript.push_str(c);
        }
        let global = match self.config.mode {
            OutputMode::Node {
                experimental_modules: false,
            } => {
                if contents.starts_with("class") {
                    format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
                } else {
                    format!("module.exports.{} = {};\n", export_name, contents)
                }
            }
            OutputMode::NoModules { .. } => {
                if contents.starts_with("class") {
                    format!("{}\n__exports.{1} = {1};\n", contents, export_name)
                } else {
                    format!("__exports.{} = {};\n", export_name, contents)
                }
            }
            OutputMode::Bundler { .. }
            | OutputMode::Node {
                experimental_modules: true,
            }
            | OutputMode::Web => {
                if contents.starts_with("function") {
                    let body = &contents[8..];
                    if export_name == definition_name {
                        format!("export function {}{}\n", export_name, body)
                    } else {
                        format!(
                            "function {}{}\nexport {{ {} as {} }};\n",
                            definition_name, body, definition_name, export_name,
                        )
                    }
                } else if contents.starts_with("class") {
                    assert_eq!(export_name, definition_name);
                    format!("export {}\n", contents)
                } else {
                    assert_eq!(export_name, definition_name);
                    format!("export const {} = {};\n", export_name, contents)
                }
            }
        };
        self.global(&global);
        Ok(())
    }

    pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> {
        // Finalize all bindings for JS classes. This is where we'll generate JS
        // glue for all classes as well as finish up a few final imports like
        // `__wrap` and such.
        self.write_classes()?;

        // Initialization is just flat out tricky and not something we
        // understand super well. To try to handle various issues that have come
        // up we always remove the `start` function if one is present. The JS
        // bindings glue then manually calls the start function (if it was
        // previously present).
        let needs_manual_start = self.unstart_start_function();

        // Cause any future calls to `should_write_global` to panic, making sure
        // we don't ask for items which we can no longer emit.
        drop(self.exposed_globals.take().unwrap());

        self.finalize_js(module_name, needs_manual_start)
    }

    /// Performs the task of actually generating the final JS module, be it
    /// `--target no-modules`, `--target web`, or for bundlers. This is the very
    /// last step performed in `finalize`.
    fn finalize_js(
        &mut self,
        module_name: &str,
        needs_manual_start: bool,
    ) -> Result<(String, String), Error> {
        let mut ts = self.typescript.clone();
        let mut js = String::new();
        if self.config.mode.no_modules() {
            js.push_str("(function() {\n");
        }

        // Depending on the output mode, generate necessary glue to actually
        // import the wasm file in one way or another.
        let mut init = (String::new(), String::new());
        let mut footer = String::new();
        let mut imports = self.js_import_header()?;
        match &self.config.mode {
            // In `--target no-modules` mode we need to both expose a name on
            // the global object as well as generate our own custom start
            // function.
            OutputMode::NoModules { global } => {
                js.push_str("const __exports = {};\n");
                js.push_str("let wasm;\n");
                init = self.gen_init(needs_manual_start, None)?;
                footer.push_str(&format!(
                    "self.{} = Object.assign(init, __exports);\n",
                    global
                ));
            }

            // With normal CommonJS node we need to defer requiring the wasm
            // until the end so most of our own exports are hooked up
            OutputMode::Node {
                experimental_modules: false,
            } => {
                js.push_str("let wasm;\n");

                for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
                    let import = self.module.imports.get_mut(*id);
                    import.module = format!("./{}.js", module_name);
                    footer.push_str("\nmodule.exports.");
                    footer.push_str(&import.name);
                    footer.push_str(" = ");
                    footer.push_str(js.trim());
                    footer.push_str(";\n");
                }

                footer.push_str(&format!("wasm = require('./{}_bg');\n", module_name));
                if needs_manual_start {
                    footer.push_str("wasm.__wbindgen_start();\n");
                }
            }

            // With Bundlers and modern ES6 support in Node we can simply import
            // the wasm file as if it were an ES module and let the
            // bundler/runtime take care of it.
            OutputMode::Bundler { .. }
            | OutputMode::Node {
                experimental_modules: true,
            } => {
                imports.push_str(&format!(
                    "import * as wasm from './{}_bg.wasm';\n",
                    module_name
                ));
                for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
                    let import = self.module.imports.get_mut(*id);
                    import.module = format!("./{}.js", module_name);
                    footer.push_str("\nexport const ");
                    footer.push_str(&import.name);
                    footer.push_str(" = ");
                    footer.push_str(js.trim());
                    footer.push_str(";\n");
                }
                if needs_manual_start {
                    footer.push_str("\nwasm.__wbindgen_start();\n");
                }
            }

            // With a browser-native output we're generating an ES module, but
            // browsers don't support natively importing wasm right now so we
            // expose the same initialization function as `--target no-modules`
            // as the default export of the module.
            OutputMode::Web => {
                self.imports_post.push_str("let wasm;\n");
                init = self.gen_init(needs_manual_start, Some(&mut imports))?;
                footer.push_str("export default init;\n");
            }
        }

        let (init_js, init_ts) = init;

        ts.push_str(&init_ts);

        // Emit all the JS for importing all our functionality
        assert!(
            !self.config.mode.uses_es_modules() || js.is_empty(),
            "ES modules require imports to be at the start of the file"
        );
        js.push_str(&imports);
        js.push_str("\n");
        js.push_str(&self.imports_post);
        js.push_str("\n");

        // Emit all our exports from this module
        js.push_str(&self.globals);
        js.push_str("\n");

        // Generate the initialization glue, if there was any
        js.push_str(&init_js);
        js.push_str("\n");
        js.push_str(&footer);
        js.push_str("\n");
        if self.config.mode.no_modules() {
            js.push_str("})();\n");
        }

        while js.contains("\n\n\n") {
            js = js.replace("\n\n\n", "\n\n");
        }

        Ok((js, ts))
    }

    fn js_import_header(&self) -> Result<String, Error> {
        let mut imports = String::new();
        match &self.config.mode {
            OutputMode::NoModules { .. } => {
                for (module, _items) in self.js_imports.iter() {
                    bail!(
                        "importing from `{}` isn't supported with `--target no-modules`",
                        module
                    );
                }
            }

            OutputMode::Node {
                experimental_modules: false,
            } => {
                for (module, items) in crate::sorted_iter(&self.js_imports) {
                    imports.push_str("const { ");
                    for (i, (item, rename)) in items.iter().enumerate() {
                        if i > 0 {
                            imports.push_str(", ");
                        }
                        imports.push_str(item);
                        if let Some(other) = rename {
                            imports.push_str(": ");
                            imports.push_str(other)
                        }
                    }
                    imports.push_str(" } = require(String.raw`");
                    imports.push_str(module);
                    imports.push_str("`);\n");
                }
            }

            OutputMode::Bundler { .. }
            | OutputMode::Node {
                experimental_modules: true,
            }
            | OutputMode::Web => {
                for (module, items) in crate::sorted_iter(&self.js_imports) {
                    imports.push_str("import { ");
                    for (i, (item, rename)) in items.iter().enumerate() {
                        if i > 0 {
                            imports.push_str(", ");
                        }
                        imports.push_str(item);
                        if let Some(other) = rename {
                            imports.push_str(" as ");
                            imports.push_str(other)
                        }
                    }
                    imports.push_str(" } from '");
                    imports.push_str(module);
                    imports.push_str("';\n");
                }
            }
        }
        Ok(imports)
    }

    fn ts_for_init_fn(has_memory: bool, has_module_or_path_optional: bool) -> String {
        let (memory_doc, memory_param) = if has_memory {
            (
                "* @param {WebAssembly.Memory} maybe_memory\n",
                ", maybe_memory: WebAssembly.Memory",
            )
        } else {
            ("", "")
        };
        let arg_optional = if has_module_or_path_optional { "?" } else { "" };
        format!(
            "\n\
            /**\n\
            * If `module_or_path` is {{RequestInfo}}, makes a request and\n\
            * for everything else, calls `WebAssembly.instantiate` directly.\n\
            *\n\
            * @param {{RequestInfo | BufferSource | WebAssembly.Module}} module_or_path\n\
            {}\
            *\n\
            * @returns {{Promise<any>}}\n\
            */\n\
            export default function init \
                (module_or_path{}: RequestInfo | BufferSource | WebAssembly.Module{}): Promise<any>;
        ",
            memory_doc, arg_optional, memory_param
        )
    }

    fn gen_init(
        &mut self,
        needs_manual_start: bool,
        mut imports: Option<&mut String>,
    ) -> Result<(String, String), Error> {
        let module_name = "wbg";
        let mut init_memory_arg = "";
        let mut init_memory1 = String::new();
        let mut init_memory2 = String::new();
        let mut has_memory = false;
        if let Some(mem) = self.module.memories.iter().next() {
            if let Some(id) = mem.import {
                self.module.imports.get_mut(id).module = module_name.to_string();
                let mut memory = String::from("new WebAssembly.Memory({");
                memory.push_str(&format!("initial:{}", mem.initial));
                if let Some(max) = mem.maximum {
                    memory.push_str(&format!(",maximum:{}", max));
                }
                if mem.shared {
                    memory.push_str(",shared:true");
                }
                memory.push_str("})");
                self.imports_post.push_str("let memory;\n");
                init_memory1 = format!("memory = imports.{}.memory = maybe_memory;", module_name);
                init_memory2 = format!("memory = imports.{}.memory = {};", module_name, memory);
                init_memory_arg = ", maybe_memory";
                has_memory = true;
            }
        }

        let default_module_path = match self.config.mode {
            OutputMode::Web => {
                "\
                    if (typeof module === 'undefined') {
                        module = import.meta.url.replace(/\\.js$/, '_bg.wasm');
                    }"
            }
            OutputMode::NoModules { .. } => {
                "\
                    if (typeof module === 'undefined') {
                        let src;
                        if (self.document === undefined) {
                            src = self.location.href;
                        } else {
                            src = self.document.currentScript.src;
                        }
                        module = src.replace(/\\.js$/, '_bg.wasm');
                    }"
            }
            _ => "",
        };

        let ts = Self::ts_for_init_fn(has_memory, !default_module_path.is_empty());

        // Initialize the `imports` object for all import definitions that we're
        // directed to wire up.
        let mut imports_init = String::new();
        if self.wasm_import_definitions.len() > 0 {
            imports_init.push_str("imports.");
            imports_init.push_str(module_name);
            imports_init.push_str(" = {};\n");
        }
        for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
            let import = self.module.imports.get_mut(*id);
            import.module = module_name.to_string();
            imports_init.push_str("imports.");
            imports_init.push_str(module_name);
            imports_init.push_str(".");
            imports_init.push_str(&import.name);
            imports_init.push_str(" = ");
            imports_init.push_str(js.trim());
            imports_init.push_str(";\n");
        }

        let extra_modules = self
            .module
            .imports
            .iter()
            .filter(|i| !self.wasm_import_definitions.contains_key(&i.id()))
            .filter(|i| {
                // Importing memory is handled specially in this area, so don't
                // consider this a candidate for importing from extra modules.
                match i.kind {
                    walrus::ImportKind::Memory(_) => false,
                    _ => true,
                }
            })
            .map(|i| &i.module)
            .collect::<BTreeSet<_>>();
        for (i, extra) in extra_modules.iter().enumerate() {
            let imports = match &mut imports {
                Some(list) => list,
                None => bail!(
                    "cannot import from modules (`{}`) with `--no-modules`",
                    extra
                ),
            };
            imports.push_str(&format!("import * as __wbg_star{} from '{}';\n", i, extra));
            imports_init.push_str(&format!("imports['{}'] = __wbg_star{};\n", extra, i));
        }

        let js = format!(
            "\
                function init(module{init_memory_arg}) {{
                    {default_module_path}
                    let result;
                    const imports = {{}};
                    {imports_init}
                    if ((typeof URL === 'function' && module instanceof URL) || typeof module === 'string' || (typeof Request === 'function' && module instanceof Request)) {{
                        {init_memory2}
                        const response = fetch(module);
                        if (typeof WebAssembly.instantiateStreaming === 'function') {{
                            result = WebAssembly.instantiateStreaming(response, imports)
                                .catch(e => {{
                                    return response
                                        .then(r => {{
                                            if (r.headers.get('Content-Type') != 'application/wasm') {{
                                                console.warn(\"`WebAssembly.instantiateStreaming` failed \
                                                                because your server does not serve wasm with \
                                                                `application/wasm` MIME type. Falling back to \
                                                                `WebAssembly.instantiate` which is slower. Original \
                                                                error:\\n\", e);
                                                return r.arrayBuffer();
                                            }} else {{
                                                throw e;
                                            }}
                                        }})
                                        .then(bytes => WebAssembly.instantiate(bytes, imports));
                                }});
                        }} else {{
                            result = response
                                .then(r => r.arrayBuffer())
                                .then(bytes => WebAssembly.instantiate(bytes, imports));
                        }}
                    }} else {{
                        {init_memory1}
                        result = WebAssembly.instantiate(module, imports)
                            .then(result => {{
                                if (result instanceof WebAssembly.Instance) {{
                                    return {{ instance: result, module }};
                                }} else {{
                                    return result;
                                }}
                            }});
                    }}
                    return result.then(({{instance, module}}) => {{
                        wasm = instance.exports;
                        init.__wbindgen_wasm_module = module;
                        {start}
                        return wasm;
                    }});
                }}
            ",
            init_memory_arg = init_memory_arg,
            default_module_path = default_module_path,
            init_memory1 = init_memory1,
            init_memory2 = init_memory2,
            start = if needs_manual_start {
                "wasm.__wbindgen_start();"
            } else {
                ""
            },
            imports_init = imports_init,
        );

        Ok((js, ts))
    }

    fn write_classes(&mut self) -> Result<(), Error> {
        for (class, exports) in self.exported_classes.take().unwrap() {
            self.write_class(&class, &exports)?;
        }
        Ok(())
    }

    fn write_class(&mut self, name: &str, class: &ExportedClass) -> Result<(), Error> {
        let mut dst = format!("class {} {{\n", name);
        let mut ts_dst = format!("export {}", dst);

        if self.config.debug && !class.has_constructor {
            dst.push_str(
                "
                    constructor() {
                        throw new Error('cannot invoke `new` directly');
                    }
                ",
            );
        }

        if class.wrap_needed {
            dst.push_str(&format!(
                "
                static __wrap(ptr) {{
                    const obj = Object.create({}.prototype);
                    obj.ptr = ptr;
                    {}
                    return obj;
                }}
                ",
                name,
                if self.config.weak_refs {
                    format!("{}FinalizationGroup.register(obj, obj.ptr, obj.ptr);", name)
                } else {
                    String::new()
                },
            ));
        }

        if self.config.weak_refs {
            self.global(&format!(
                "
                const {}FinalizationGroup = new FinalizationGroup((items) => {{
                    for (const ptr of items) {{
                        wasm.{}(ptr);
                    }}
                }});
                ",
                name,
                wasm_bindgen_shared::free_function(&name),
            ));
        }

        // If the class is inspectable, generate `toJSON` and `toString`
        // to expose all readable properties of the class. Otherwise,
        // the class shows only the "ptr" property when logged or serialized
        if class.is_inspectable {
            // Creates a `toJSON` method which returns an object of all readable properties
            // This object looks like { a: this.a, b: this.b }
            dst.push_str(&format!(
                "
                toJSON() {{
                    return {{{}}};
                }}

                toString() {{
                    return JSON.stringify(this);
                }}
                ",
                class
                    .readable_properties
                    .iter()
                    .fold(String::from("\n"), |fields, field_name| {
                        format!("{}{name}: this.{name},\n", fields, name = field_name)
                    })
            ));

            if self.config.mode.nodejs() {
                // `util.inspect` must be imported in Node.js to define [inspect.custom]
                let module_name = self.import_name(&JsImport {
                    name: JsImportName::Module {
                        module: "util".to_string(),
                        name: "inspect".to_string(),
                    },
                    fields: Vec::new(),
                })?;

                // Node.js supports a custom inspect function to control the
                // output of `console.log` and friends. The constructor is set
                // to display the class name as a typical JavaScript class would
                dst.push_str(&format!(
                    "
                    [{}.custom]() {{
                        return Object.assign(Object.create({{constructor: this.constructor}}), this.toJSON());
                    }}
                    ",
                    module_name
                ));
            }
        }

        dst.push_str(&format!(
            "
            free() {{
                const ptr = this.ptr;
                this.ptr = 0;
                {}
                wasm.{}(ptr);
            }}
            ",
            if self.config.weak_refs {
                format!("{}FinalizationGroup.unregister(ptr);", name)
            } else {
                String::new()
            },
            wasm_bindgen_shared::free_function(&name),
        ));
        ts_dst.push_str("  free(): void;\n");
        dst.push_str(&class.contents);
        ts_dst.push_str(&class.typescript);

        let mut fields = class.typescript_fields.keys().collect::<Vec<_>>();
        fields.sort(); // make sure we have deterministic output
        for name in fields {
            let (ty, has_setter) = &class.typescript_fields[name];
            ts_dst.push_str("  ");
            if !has_setter {
                ts_dst.push_str("readonly ");
            }
            ts_dst.push_str(name);
            ts_dst.push_str(": ");
            ts_dst.push_str(ty);
            ts_dst.push_str(";\n");
        }
        dst.push_str("}\n");
        ts_dst.push_str("}\n");

        self.export(&name, &dst, Some(class.comments.clone()))?;
        self.typescript.push_str(&ts_dst);

        Ok(())
    }

    fn expose_drop_ref(&mut self) {
        if !self.should_write_global("drop_ref") {
            return;
        }
        self.expose_global_heap();
        self.expose_global_heap_next();

        // Note that here we check if `idx` shouldn't actually be dropped. This
        // is due to the fact that `JsValue::null()` and friends can be passed
        // by value to JS where we'll automatically call this method. Those
        // constants, however, cannot be dropped. See #1054 for removing this
        // branch.
        //
        // Otherwise the free operation here is pretty simple, just appending to
        // the linked list of heap slots that are free.
        self.global(&format!(
            "
            function dropObject(idx) {{
                if (idx < {}) return;
                heap[idx] = heap_next;
                heap_next = idx;
            }}
            ",
            INITIAL_HEAP_OFFSET + INITIAL_HEAP_VALUES.len(),
        ));
    }

    fn expose_global_heap(&mut self) {
        if !self.should_write_global("heap") {
            return;
        }
        assert!(!self.config.anyref);
        self.global(&format!("const heap = new Array({});", INITIAL_HEAP_OFFSET));
        self.global("heap.fill(undefined);");
        self.global(&format!("heap.push({});", INITIAL_HEAP_VALUES.join(", ")));
    }

    fn expose_global_heap_next(&mut self) {
        if !self.should_write_global("heap_next") {
            return;
        }
        self.expose_global_heap();
        self.global("let heap_next = heap.length;");
    }

    fn expose_get_object(&mut self) {
        if !self.should_write_global("get_object") {
            return;
        }
        self.expose_global_heap();

        // Accessing a heap object is just a simple index operation due to how
        // the stack/heap are laid out.
        self.global("function getObject(idx) { return heap[idx]; }");
    }

    fn expose_not_defined(&mut self) {
        if !self.should_write_global("not_defined") {
            return;
        }
        self.global(
            "function notDefined(what) { return () => { throw new Error(`${what} is not defined`); }; }"
        );
    }

    fn expose_assert_num(&mut self) {
        if !self.should_write_global("assert_num") {
            return;
        }
        self.global(&format!(
            "
            function _assertNum(n) {{
                if (typeof(n) !== 'number') throw new Error('expected a number argument');
            }}
            "
        ));
    }

    fn expose_assert_bool(&mut self) {
        if !self.should_write_global("assert_bool") {
            return;
        }
        self.global(&format!(
            "
            function _assertBoolean(n) {{
                if (typeof(n) !== 'boolean') {{
                    throw new Error('expected a boolean argument');
                }}
            }}
            "
        ));
    }

    fn expose_wasm_vector_len(&mut self) {
        if !self.should_write_global("wasm_vector_len") {
            return;
        }
        self.global("let WASM_VECTOR_LEN = 0;");
    }

    fn expose_pass_string_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        self.expose_wasm_vector_len();

        let debug = if self.config.debug {
            "
                if (typeof(arg) !== 'string') throw new Error('expected a string argument');
            "
        } else {
            ""
        };

        // If we are targeting Node.js, it doesn't have `encodeInto` yet
        // but it does have `Buffer::write` which has similar semantics but
        // doesn't require creating intermediate view using `subarray`
        // and also has `Buffer::byteLength` to calculate size upfront.
        if self.config.mode.nodejs() {
            let get_buf = self.expose_node_buffer_memory(memory);
            let ret = MemView {
                name: "passStringToWasm",
                num: get_buf.num,
            };
            if !self.should_write_global(ret.to_string()) {
                return Ok(ret);
            }

            self.global(&format!(
                "
                    function {}(arg, malloc) {{
                        {}
                        const len = Buffer.byteLength(arg);
                        const ptr = malloc(len);
                        {}().write(arg, ptr, len);
                        WASM_VECTOR_LEN = len;
                        return ptr;
                    }}
                ",
                ret, debug, get_buf,
            ));

            return Ok(ret);
        }

        let mem = self.expose_uint8_memory(memory);
        let ret = MemView {
            name: "passStringToWasm",
            num: mem.num,
        };
        if !self.should_write_global(ret.to_string()) {
            return Ok(ret);
        }
        self.expose_text_encoder()?;

        // The first implementation we have for this is to use
        // `TextEncoder#encode` which has been around for quite some time.
        let encode = "function (arg, view) {
            const buf = cachedTextEncoder.encode(arg);
            view.set(buf);
            return {
                read: arg.length,
                written: buf.length
            };
        }";

        // Another possibility is to use `TextEncoder#encodeInto` which is much
        // newer and isn't implemented everywhere yet. It's more efficient,
        // however, becaues it allows us to elide an intermediate allocation.
        let encode_into = "function (arg, view) {
            return cachedTextEncoder.encodeInto(arg, view);
        }";

        // Looks like `encodeInto` doesn't currently work when the memory passed
        // in is backed by a `SharedArrayBuffer`, so force usage of `encode` if
        // a `SharedArrayBuffer` is in use.
        let shared = self.module.memories.get(memory).shared;

        match self.config.encode_into {
            EncodeInto::Always if !shared => {
                self.global(&format!(
                    "
                    const encodeString = {};
                ",
                    encode_into
                ));
            }
            EncodeInto::Test if !shared => {
                self.global(&format!(
                    "
                    const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
                        ? {}
                        : {});
                ",
                    encode_into, encode
                ));
            }
            _ => {
                self.global(&format!(
                    "
                    const encodeString = {};
                ",
                    encode
                ));
            }
        }

        // A fast path that directly writes char codes into WASM memory as long
        // as it finds only ASCII characters.
        //
        // This is much faster for common ASCII strings because it can avoid
        // calling out into C++ TextEncoder code.
        //
        // This might be not very intuitive, but such calls are usually more
        // expensive in mainstream engines than staying in the JS, and
        // charCodeAt on ASCII strings is usually optimised to raw bytes.
        let encode_as_ascii = format!(
            "\
                if (realloc === undefined) {{
                    const buf = cachedTextEncoder.encode(arg);
                    const ptr = malloc(buf.length);
                    {mem}().subarray(ptr, ptr + buf.length).set(buf);
                    WASM_VECTOR_LEN = buf.length;
                    return ptr;
                }}

                let len = arg.length;
                let ptr = malloc(len);

                const mem = {mem}();

                let offset = 0;

                for (; offset < len; offset++) {{
                    const code = arg.charCodeAt(offset);
                    if (code > 0x7F) break;
                    mem[ptr + offset] = code;
                }}
            ",
            mem = mem,
        );

        // TODO:
        // When converting a JS string to UTF-8, the maximum size is `arg.length * 3`,
        // so we just allocate that. This wastes memory, so we should investigate
        // looping over the string to calculate the precise size, or perhaps using
        // `shrink_to_fit` on the Rust side.
        self.global(&format!(
            "function {name}(arg, malloc, realloc) {{
                {debug}
                {ascii}
                if (offset !== len) {{
                    if (offset !== 0) {{
                        arg = arg.slice(offset);
                    }}
                    ptr = realloc(ptr, len, len = offset + arg.length * 3);
                    const view = {mem}().subarray(ptr + offset, ptr + len);
                    const ret = encodeString(arg, view);
                    {debug_end}
                    offset += ret.written;
                }}

                WASM_VECTOR_LEN = offset;
                return ptr;
            }}",
            name = ret,
            debug = debug,
            ascii = encode_as_ascii,
            mem = mem,
            debug_end = if self.config.debug {
                "if (ret.read !== arg.length) throw new Error('failed to pass whole string');"
            } else {
                ""
            },
        ));

        Ok(ret)
    }

    fn expose_pass_array8_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let view = self.expose_uint8_memory(memory);
        self.pass_array_to_wasm("passArray8ToWasm", view, 1)
    }

    fn expose_pass_array16_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let view = self.expose_uint16_memory(memory);
        self.pass_array_to_wasm("passArray16ToWasm", view, 2)
    }

    fn expose_pass_array32_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let view = self.expose_uint32_memory(memory);
        self.pass_array_to_wasm("passArray32ToWasm", view, 4)
    }

    fn expose_pass_array64_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let view = self.expose_uint64_memory(memory);
        self.pass_array_to_wasm("passArray64ToWasm", view, 8)
    }

    fn expose_pass_array_f32_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let view = self.expose_f32_memory(memory);
        self.pass_array_to_wasm("passArrayF32ToWasm", view, 4)
    }

    fn expose_pass_array_f64_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let view = self.expose_f64_memory(memory);
        self.pass_array_to_wasm("passArrayF64ToWasm", view, 8)
    }

    fn expose_pass_array_jsvalue_to_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let mem = self.expose_uint32_memory(memory);
        let ret = MemView {
            name: "passArrayJsValueToWasm",
            num: mem.num,
        };
        if !self.should_write_global(ret.to_string()) {
            return Ok(ret);
        }
        self.expose_wasm_vector_len();
        match (self.aux.anyref_table, self.aux.anyref_alloc) {
            (Some(table), Some(alloc)) => {
                // TODO: using `addToAnyrefTable` goes back and forth between wasm
                // and JS a lot, we should have a bulk operation for this.
                let add = self.expose_add_to_anyref_table(table, alloc)?;
                self.global(&format!(
                    "
                        function {}(array, malloc) {{
                            const ptr = malloc(array.length * 4);
                            const mem = {}();
                            for (let i = 0; i < array.length; i++) {{
                                mem[ptr / 4 + i] = {}(array[i]);
                            }}
                            WASM_VECTOR_LEN = array.length;
                            return ptr;
                        }}
                    ",
                    ret, mem, add,
                ));
            }
            _ => {
                self.expose_add_heap_object();
                self.global(&format!(
                    "
                        function {}(array, malloc) {{
                            const ptr = malloc(array.length * 4);
                            const mem = {}();
                            for (let i = 0; i < array.length; i++) {{
                                mem[ptr / 4 + i] = addHeapObject(array[i]);
                            }}
                            WASM_VECTOR_LEN = array.length;
                            return ptr;
                        }}
                    ",
                    ret, mem,
                ));
            }
        }
        Ok(ret)
    }

    fn pass_array_to_wasm(
        &mut self,
        name: &'static str,
        view: MemView,
        size: usize,
    ) -> Result<MemView, Error> {
        let ret = MemView {
            name,
            num: view.num,
        };
        if !self.should_write_global(ret.to_string()) {
            return Ok(ret);
        }
        self.expose_wasm_vector_len();
        self.global(&format!(
            "
            function {}(arg, malloc) {{
                const ptr = malloc(arg.length * {size});
                {}().set(arg, ptr / {size});
                WASM_VECTOR_LEN = arg.length;
                return ptr;
            }}
            ",
            ret,
            view,
            size = size
        ));
        Ok(ret)
    }

    fn expose_text_encoder(&mut self) -> Result<(), Error> {
        if !self.should_write_global("text_encoder") {
            return Ok(());
        }
        self.expose_text_processor("TextEncoder", "('utf-8')")
    }

    fn expose_text_decoder(&mut self) -> Result<(), Error> {
        if !self.should_write_global("text_decoder") {
            return Ok(());
        }

        // `ignoreBOM` is needed so that the BOM will be preserved when sending a string from Rust to JS
        // `fatal` is needed to catch any weird encoding bugs when sending a string from Rust to JS
        self.expose_text_processor("TextDecoder", "('utf-8', { ignoreBOM: true, fatal: true })")?;

        // This is needed to workaround a bug in Safari
        // See: https://github.com/rustwasm/wasm-bindgen/issues/1825
        self.global("cachedTextDecoder.decode();");

        Ok(())
    }

    fn expose_text_processor(&mut self, s: &str, args: &str) -> Result<(), Error> {
        if self.config.mode.nodejs() {
            let name = self.import_name(&JsImport {
                name: JsImportName::Module {
                    module: "util".to_string(),
                    name: s.to_string(),
                },
                fields: Vec::new(),
            })?;
            self.global(&format!("let cached{} = new {}{};", s, name, args));
        } else if !self.config.mode.always_run_in_browser() {
            self.global(&format!(
                "
                    const l{0} = typeof {0} === 'undefined' ? \
                        require('util').{0} : {0};\
                ",
                s
            ));
            self.global(&format!("let cached{0} = new l{0}{1};", s, args));
        } else {
            self.global(&format!("let cached{0} = new {0}{1};", s, args));
        }

        Ok(())
    }

    fn expose_get_string_from_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        self.expose_text_decoder()?;
        let mem = self.expose_uint8_memory(memory);
        let ret = MemView {
            name: "getStringFromWasm",
            num: mem.num,
        };

        if !self.should_write_global(ret.to_string()) {
            return Ok(ret);
        }

        // Typically we try to give a raw view of memory out to `TextDecoder` to
        // avoid copying too much data. If, however, a `SharedArrayBuffer` is
        // being used it looks like that is rejected by `TextDecoder` or
        // otherwise doesn't work with it. When we detect a shared situation we
        // use `slice` which creates a new array instead of `subarray` which
        // creates just a view. That way in shared mode we copy more data but in
        // non-shared mode there's no need to copy the data except for the
        // string itself.
        let is_shared = self.module.memories.get(memory).shared;
        let method = if is_shared { "slice" } else { "subarray" };

        self.global(&format!(
            "
            function {}(ptr, len) {{
                return cachedTextDecoder.decode({}().{}(ptr, ptr + len));
            }}
            ",
            ret, mem, method
        ));
        Ok(ret)
    }

    fn expose_get_cached_string_from_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        self.expose_get_object();
        let get = self.expose_get_string_from_wasm(memory)?;
        let ret = MemView {
            name: "getCachedStringFromWasm",
            num: get.num,
        };

        if !self.should_write_global(ret.to_string()) {
            return Ok(ret);
        }

        // This has support for both `&str` and `Option<&str>`.
        //
        // If `ptr` is not `0` then we know that it's a `&str` or `Some(&str)`, so we just decode it.
        //
        // If `ptr` is `0` then the `len` is a pointer to the cached `JsValue`, so we return that.
        //
        // If `ptr` and `len` are both `0` then that means it's `None`, in that case we rely upon
        // the fact that `getObject(0)` is guaranteed to be `undefined`.
        self.global(&format!(
            "
            function {}(ptr, len) {{
                if (ptr === 0) {{
                    return getObject(len);
                }} else {{
                    return {}(ptr, len);
                }}
            }}
            ",
            ret, get,
        ));
        Ok(ret)
    }

    fn expose_get_array_js_value_from_wasm(&mut self, memory: MemoryId) -> Result<MemView, Error> {
        let mem = self.expose_uint32_memory(memory);
        let ret = MemView {
            name: "getArrayJsValueFromWasm",
            num: mem.num,
        };
        if !self.should_write_global(ret.to_string()) {
            return Ok(ret);
        }
        match (self.aux.anyref_table, self.aux.anyref_drop_slice) {
            (Some(table), Some(drop)) => {
                let table = self.export_name_of(table);
                let drop = self.export_name_of(drop);
                self.global(&format!(
                    "
                    function {}(ptr, len) {{
                        const mem = {}();
                        const slice = mem.subarray(ptr / 4, ptr / 4 + len);
                        const result = [];
                        for (let i = 0; i < slice.length; i++) {{
                            result.push(wasm.{}.get(slice[i]));
                        }}
                        wasm.{}(ptr, len);
                        return result;
                    }}
                    ",
                    ret, mem, table, drop,
                ));
            }
            _ => {
                self.expose_take_object();
                self.global(&format!(
                    "
                    function {}(ptr, len) {{
                        const mem = {}();
                        const slice = mem.subarray(ptr / 4, ptr / 4 + len);
                        const result = [];
                        for (let i = 0; i < slice.length; i++) {{
                            result.push(takeObject(slice[i]));
                        }}
                        return result;
                    }}
                    ",
                    ret, mem,
                ));
            }
        }
        Ok(ret)
    }

    fn expose_get_array_i8_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_int8_memory(memory);
        self.arrayget("getArrayI8FromWasm", view, 1)
    }

    fn expose_get_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_uint8_memory(memory);
        self.arrayget("getArrayU8FromWasm", view, 1)
    }

    fn expose_get_clamped_array_u8_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_clamped_uint8_memory(memory);
        self.arrayget("getClampedArrayU8FromWasm", view, 1)
    }

    fn expose_get_array_i16_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_int16_memory(memory);
        self.arrayget("getArrayI16FromWasm", view, 2)
    }

    fn expose_get_array_u16_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_uint16_memory(memory);
        self.arrayget("getArrayU16FromWasm", view, 2)
    }

    fn expose_get_array_i32_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_int32_memory(memory);
        self.arrayget("getArrayI32FromWasm", view, 4)
    }

    fn expose_get_array_u32_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_uint32_memory(memory);
        self.arrayget("getArrayU32FromWasm", view, 4)
    }

    fn expose_get_array_i64_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_int64_memory(memory);
        self.arrayget("getArrayI64FromWasm", view, 8)
    }

    fn expose_get_array_u64_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_uint64_memory(memory);
        self.arrayget("getArrayU64FromWasm", view, 8)
    }

    fn expose_get_array_f32_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_f32_memory(memory);
        self.arrayget("getArrayF32FromWasm", view, 4)
    }

    fn expose_get_array_f64_from_wasm(&mut self, memory: MemoryId) -> MemView {
        let view = self.expose_f64_memory(memory);
        self.arrayget("getArrayF64FromWasm", view, 8)
    }

    fn arrayget(&mut self, name: &'static str, view: MemView, size: usize) -> MemView {
        let ret = MemView {
            name,
            num: view.num,
        };
        if !self.should_write_global(name) {
            return ret;
        }
        self.global(&format!(
            "
            function {name}(ptr, len) {{
                return {mem}().subarray(ptr / {size}, ptr / {size} + len);
            }}
            ",
            name = ret,
            mem = view,
            size = size,
        ));
        return ret;
    }

    fn expose_node_buffer_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getNodeBufferMemory", "Buffer.from", memory)
    }

    fn expose_int8_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getInt8Memory", "new Int8Array", memory)
    }

    fn expose_uint8_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getUint8Memory", "new Uint8Array", memory)
    }

    fn expose_clamped_uint8_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getUint8ClampedMemory", "new Uint8ClampedArray", memory)
    }

    fn expose_int16_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getInt16Memory", "new Int16Array", memory)
    }

    fn expose_uint16_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getUint16Memory", "new Uint16Array", memory)
    }

    fn expose_int32_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getInt32Memory", "new Int32Array", memory)
    }

    fn expose_uint32_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getUint32Memory", "new Uint32Array", memory)
    }

    fn expose_int64_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getInt64Memory", "new BigInt64Array", memory)
    }

    fn expose_uint64_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getUint64Memory", "new BigUint64Array", memory)
    }

    fn expose_f32_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getFloat32Memory", "new Float32Array", memory)
    }

    fn expose_f64_memory(&mut self, memory: MemoryId) -> MemView {
        self.memview("getFloat64Memory", "new Float64Array", memory)
    }

    fn memview_function(&mut self, t: VectorKind, memory: MemoryId) -> MemView {
        match t {
            VectorKind::String => self.expose_uint8_memory(memory),
            VectorKind::I8 => self.expose_int8_memory(memory),
            VectorKind::U8 => self.expose_uint8_memory(memory),
            VectorKind::ClampedU8 => self.expose_clamped_uint8_memory(memory),
            VectorKind::I16 => self.expose_int16_memory(memory),
            VectorKind::U16 => self.expose_uint16_memory(memory),
            VectorKind::I32 => self.expose_int32_memory(memory),
            VectorKind::U32 => self.expose_uint32_memory(memory),
            VectorKind::I64 => self.expose_int64_memory(memory),
            VectorKind::U64 => self.expose_uint64_memory(memory),
            VectorKind::F32 => self.expose_f32_memory(memory),
            VectorKind::F64 => self.expose_f64_memory(memory),
            VectorKind::Anyref => self.expose_uint32_memory(memory),
        }
    }

    fn memview(&mut self, name: &'static str, js: &str, memory: walrus::MemoryId) -> MemView {
        let view = self.memview_memory(name, memory);
        if !self.should_write_global(name.to_string()) {
            return view;
        }
        let mem = self.export_name_of(memory);
        self.global(&format!(
            "
            let cache{name} = null;
            function {name}() {{
                if (cache{name} === null || cache{name}.buffer !== wasm.{mem}.buffer) {{
                    cache{name} = {js}(wasm.{mem}.buffer);
                }}
                return cache{name};
            }}
            ",
            name = view,
            js = js,
            mem = mem,
        ));
        return view;
    }

    fn memview_memory(&mut self, name: &'static str, memory: walrus::MemoryId) -> MemView {
        let next = self.memory_indices.len();
        let num = *self.memory_indices.entry(memory).or_insert(next);
        MemView { name, num }
    }

    fn memview_table(&mut self, name: &'static str, table: walrus::TableId) -> MemView {
        let next = self.table_indices.len();
        let num = *self.table_indices.entry(table).or_insert(next);
        MemView { name, num }
    }

    fn expose_assert_class(&mut self) {
        if !self.should_write_global("assert_class") {
            return;
        }
        self.global(
            "
            function _assertClass(instance, klass) {
                if (!(instance instanceof klass)) {
                    throw new Error(`expected instance of ${klass.name}`);
                }
                return instance.ptr;
            }
            ",
        );
    }

    fn expose_global_stack_pointer(&mut self) {
        if !self.should_write_global("stack_pointer") {
            return;
        }
        self.global(&format!("let stack_pointer = {};", INITIAL_HEAP_OFFSET));
    }

    fn expose_borrowed_objects(&mut self) {
        if !self.should_write_global("borrowed_objects") {
            return;
        }
        self.expose_global_heap();
        self.expose_global_stack_pointer();
        // Our `stack_pointer` points to where we should start writing stack
        // objects, and the `stack_pointer` is incremented in a `finally` block
        // after executing this. Once we've reserved stack space we write the
        // value. Eventually underflow will throw an exception, but JS sort of
        // just handles it today...
        self.global(
            "
            function addBorrowedObject(obj) {
                if (stack_pointer == 1) throw new Error('out of js stack');
                heap[--stack_pointer] = obj;
                return stack_pointer;
            }
            ",
        );
    }

    fn expose_take_object(&mut self) {
        if !self.should_write_global("take_object") {
            return;
        }
        self.expose_get_object();
        self.expose_drop_ref();
        self.global(
            "
            function takeObject(idx) {
                const ret = getObject(idx);
                dropObject(idx);
                return ret;
            }
            ",
        );
    }

    fn expose_add_heap_object(&mut self) {
        if !self.should_write_global("add_heap_object") {
            return;
        }
        self.expose_global_heap();
        self.expose_global_heap_next();
        let set_heap_next = if self.config.debug {
            String::from(
                "
                if (typeof(heap_next) !== 'number') throw new Error('corrupt heap');
                ",
            )
        } else {
            String::new()
        };

        // Allocating a slot on the heap first goes through the linked list
        // (starting at `heap_next`). Once that linked list is exhausted we'll
        // be pointing beyond the end of the array, at which point we'll reserve
        // one more slot and use that.
        self.global(&format!(
            "
            function addHeapObject(obj) {{
                if (heap_next === heap.length) heap.push(heap.length + 1);
                const idx = heap_next;
                heap_next = heap[idx];
                {}
                heap[idx] = obj;
                return idx;
            }}
            ",
            set_heap_next
        ));
    }

    fn expose_handle_error(&mut self) -> Result<(), Error> {
        if !self.should_write_global("handle_error") {
            return Ok(());
        }
        let store = self
            .aux
            .exn_store
            .ok_or_else(|| anyhow!("failed to find `__wbindgen_exn_store` intrinsic"))?;
        let store = self.export_name_of(store);
        match (self.aux.anyref_table, self.aux.anyref_alloc) {
            (Some(table), Some(alloc)) => {
                let add = self.expose_add_to_anyref_table(table, alloc)?;
                self.global(&format!(
                    "
                    function handleError(e) {{
                        const idx = {}(e);
                        wasm.{}(idx);
                    }}
                    ",
                    add, store,
                ));
            }
            _ => {
                self.expose_add_heap_object();
                self.global(&format!(
                    "
                    function handleError(e) {{
                        wasm.{}(addHeapObject(e));
                    }}
                    ",
                    store,
                ));
            }
        }
        Ok(())
    }

    fn expose_log_error(&mut self) {
        if !self.should_write_global("log_error") {
            return;
        }
        self.global(
            "\
            function logError(e) {
                let error = (function () {
                    try {
                        return e instanceof Error \
                            ? `${e.message}\\n\\nStack:\\n${e.stack}` \
                            : e.toString();
                    } catch(_) {
                        return \"<failed to stringify thrown value>\";
                    }
                }());
                console.error(\"wasm-bindgen: imported JS function that \
                                was not marked as `catch` threw an error:\", \
                                error);
                throw e;
            }
            ",
        );
    }

    fn pass_to_wasm_function(&mut self, t: VectorKind, memory: MemoryId) -> Result<MemView, Error> {
        match t {
            VectorKind::String => self.expose_pass_string_to_wasm(memory),
            VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => {
                self.expose_pass_array8_to_wasm(memory)
            }
            VectorKind::U16 | VectorKind::I16 => self.expose_pass_array16_to_wasm(memory),
            VectorKind::I32 | VectorKind::U32 => self.expose_pass_array32_to_wasm(memory),
            VectorKind::I64 | VectorKind::U64 => self.expose_pass_array64_to_wasm(memory),
            VectorKind::F32 => self.expose_pass_array_f32_to_wasm(memory),
            VectorKind::F64 => self.expose_pass_array_f64_to_wasm(memory),
            VectorKind::Anyref => self.expose_pass_array_jsvalue_to_wasm(memory),
        }
    }

    fn expose_get_vector_from_wasm(
        &mut self,
        ty: VectorKind,
        memory: MemoryId,
    ) -> Result<MemView, Error> {
        Ok(match ty {
            VectorKind::String => self.expose_get_string_from_wasm(memory)?,
            VectorKind::I8 => self.expose_get_array_i8_from_wasm(memory),
            VectorKind::U8 => self.expose_get_array_u8_from_wasm(memory),
            VectorKind::ClampedU8 => self.expose_get_clamped_array_u8_from_wasm(memory),
            VectorKind::I16 => self.expose_get_array_i16_from_wasm(memory),
            VectorKind::U16 => self.expose_get_array_u16_from_wasm(memory),
            VectorKind::I32 => self.expose_get_array_i32_from_wasm(memory),
            VectorKind::U32 => self.expose_get_array_u32_from_wasm(memory),
            VectorKind::I64 => self.expose_get_array_i64_from_wasm(memory),
            VectorKind::U64 => self.expose_get_array_u64_from_wasm(memory),
            VectorKind::F32 => self.expose_get_array_f32_from_wasm(memory),
            VectorKind::F64 => self.expose_get_array_f64_from_wasm(memory),
            VectorKind::Anyref => self.expose_get_array_js_value_from_wasm(memory)?,
        })
    }

    fn expose_get_inherited_descriptor(&mut self) {
        if !self.should_write_global("get_inherited_descriptor") {
            return;
        }
        // It looks like while rare some browsers will move descriptors up the
        // property chain which runs the risk of breaking wasm-bindgen-generated
        // code because we're looking for precise descriptor functions rather
        // than relying on the prototype chain like most "normal JS" projects
        // do.
        //
        // As a result we have a small helper here which will walk the prototype
        // chain looking for a descriptor. For some more information on this see
        // #109
        self.global(
            "
            function GetOwnOrInheritedPropertyDescriptor(obj, id) {
              while (obj) {
                let desc = Object.getOwnPropertyDescriptor(obj, id);
                if (desc) return desc;
                obj = Object.getPrototypeOf(obj);
              }
              return {};
            }
            ",
        );
    }

    fn expose_u32_cvt_shim(&mut self) -> &'static str {
        let name = "u32CvtShim";
        if !self.should_write_global(name) {
            return name;
        }
        self.global(&format!("const {} = new Uint32Array(2);", name));
        name
    }

    fn expose_int64_cvt_shim(&mut self) -> &'static str {
        let name = "int64CvtShim";
        if !self.should_write_global(name) {
            return name;
        }
        let n = self.expose_u32_cvt_shim();
        self.global(&format!(
            "const {} = new BigInt64Array({}.buffer);",
            name, n
        ));
        name
    }

    fn expose_uint64_cvt_shim(&mut self) -> &'static str {
        let name = "uint64CvtShim";
        if !self.should_write_global(name) {
            return name;
        }
        let n = self.expose_u32_cvt_shim();
        self.global(&format!(
            "const {} = new BigUint64Array({}.buffer);",
            name, n
        ));
        name
    }

    fn expose_is_like_none(&mut self) {
        if !self.should_write_global("is_like_none") {
            return;
        }
        self.global(
            "
            function isLikeNone(x) {
                return x === undefined || x === null;
            }
        ",
        );
    }

    fn global(&mut self, s: &str) {
        let s = s.trim();

        // Ensure a blank line between adjacent items, and ensure everything is
        // terminated with a newline.
        while !self.globals.ends_with("\n\n\n") && !self.globals.ends_with("*/\n") {
            self.globals.push_str("\n");
        }
        self.globals.push_str(s);
        self.globals.push_str("\n");
    }

    fn require_class_wrap(&mut self, name: &str) {
        require_class(&mut self.exported_classes, name).wrap_needed = true;
    }

    fn import_name(&mut self, import: &JsImport) -> Result<String, Error> {
        if let Some(name) = self.imported_names.get(&import.name) {
            let mut name = name.clone();
            for field in import.fields.iter() {
                name.push_str(".");
                name.push_str(field);
            }
            return Ok(name.clone());
        }

        let js_imports = &mut self.js_imports;
        let mut add_module_import = |module: String, name: &str, actual: &str| {
            let rename = if name == actual {
                None
            } else {
                Some(actual.to_string())
            };
            js_imports
                .entry(module)
                .or_insert(Vec::new())
                .push((name.to_string(), rename));
        };

        let mut name = match &import.name {
            JsImportName::Module { module, name } => {
                let unique_name = generate_identifier(name, &mut self.defined_identifiers);
                add_module_import(module.clone(), name, &unique_name);
                unique_name
            }

            JsImportName::LocalModule { module, name } => {
                let unique_name = generate_identifier(name, &mut self.defined_identifiers);
                let module = self.config.local_module_name(module);
                add_module_import(module, name, &unique_name);
                unique_name
            }

            JsImportName::InlineJs {
                unique_crate_identifier,
                snippet_idx_in_crate,
                name,
            } => {
                let module = self
                    .config
                    .inline_js_module_name(unique_crate_identifier, *snippet_idx_in_crate);
                let unique_name = generate_identifier(name, &mut self.defined_identifiers);
                add_module_import(module, name, &unique_name);
                unique_name
            }

            JsImportName::VendorPrefixed { name, prefixes } => {
                self.imports_post.push_str("const l");
                self.imports_post.push_str(&name);
                self.imports_post.push_str(" = ");
                switch(&mut self.imports_post, name, "", prefixes);
                self.imports_post.push_str(";\n");

                fn switch(dst: &mut String, name: &str, prefix: &str, left: &[String]) {
                    if left.len() == 0 {
                        dst.push_str(prefix);
                        return dst.push_str(name);
                    }
                    dst.push_str("(typeof ");
                    dst.push_str(prefix);
                    dst.push_str(name);
                    dst.push_str(" !== 'undefined' ? ");
                    dst.push_str(prefix);
                    dst.push_str(name);
                    dst.push_str(" : ");
                    switch(dst, name, &left[0], &left[1..]);
                    dst.push_str(")");
                }
                format!("l{}", name)
            }

            JsImportName::Global { name } => {
                let unique_name = generate_identifier(name, &mut self.defined_identifiers);
                if unique_name != *name {
                    bail!("cannot import `{}` from two locations", name);
                }
                unique_name
            }
        };
        self.imported_names
            .insert(import.name.clone(), name.clone());

        // After we've got an actual name handle field projections
        for field in import.fields.iter() {
            name.push_str(".");
            name.push_str(field);
        }
        Ok(name)
    }

    /// If a start function is present, it removes it from the `start` section
    /// of the wasm module and then moves it to an exported function, named
    /// `__wbindgen_start`.
    fn unstart_start_function(&mut self) -> bool {
        let start = match self.module.start.take() {
            Some(id) => id,
            None => return false,
        };
        self.module.exports.add("__wbindgen_start", start);
        true
    }

    fn expose_add_to_anyref_table(
        &mut self,
        table: TableId,
        alloc: FunctionId,
    ) -> Result<MemView, Error> {
        let view = self.memview_table("addToAnyrefTable", table);
        assert!(self.config.anyref);
        if !self.should_write_global(view.to_string()) {
            return Ok(view);
        }
        let alloc = self.export_name_of(alloc);
        let table = self.export_name_of(table);
        self.global(&format!(
            "
                function {}(obj) {{
                    const idx = wasm.{}();
                    wasm.{}.set(idx, obj);
                    return idx;
                }}
            ",
            view, alloc, table,
        ));

        Ok(view)
    }

    pub fn generate(&mut self) -> Result<(), Error> {
        for (id, adapter) in crate::sorted_iter(&self.wit.adapters) {
            let instrs = match &adapter.kind {
                AdapterKind::Import { .. } => continue,
                AdapterKind::Local { instructions } => instructions,
            };
            self.generate_adapter(*id, adapter, instrs)?;
        }

        let mut pairs = self.aux.export_map.iter().collect::<Vec<_>>();
        pairs.sort_by_key(|(k, _)| *k);
        check_duplicated_getter_and_setter_names(&pairs)?;

        for e in self.aux.enums.iter() {
            self.generate_enum(e)?;
        }

        for s in self.aux.structs.iter() {
            self.generate_struct(s)?;
        }

        self.typescript.push_str(&self.aux.extra_typescript);

        for path in self.aux.package_jsons.iter() {
            self.process_package_json(path)?;
        }

        Ok(())
    }

    fn generate_adapter(
        &mut self,
        id: AdapterId,
        adapter: &Adapter,
        instrs: &[InstructionData],
    ) -> Result<(), Error> {
        enum Kind<'a> {
            Export(&'a AuxExport),
            Import(walrus::ImportId),
            Adapter,
        }

        let kind = match self.aux.export_map.get(&id) {
            Some(export) => Kind::Export(export),
            None => {
                let core = self.wit.implements.iter().find(|pair| pair.2 == id);
                match core {
                    Some((core, _, _)) => Kind::Import(*core),
                    None => Kind::Adapter,
                }
            }
        };

        let catch = self.aux.imports_with_catch.contains(&id);
        if let Kind::Import(core) = kind {
            if !catch && self.attempt_direct_import(core, instrs)? {
                return Ok(());
            }
        }

        // Construct a JS shim builder, and configure it based on the kind of
        // export that we're generating.
        let mut builder = binding::Builder::new(self);
        builder.log_error(match kind {
            Kind::Export(_) | Kind::Adapter => false,
            Kind::Import(_) => builder.cx.config.debug,
        });
        builder.catch(catch);
        let mut arg_names = &None;
        match kind {
            Kind::Export(export) => {
                arg_names = &export.arg_names;
                match &export.kind {
                    AuxExportKind::Function(_) => {}
                    AuxExportKind::StaticFunction { .. } => {}
                    AuxExportKind::Constructor(class) => builder.constructor(class),
                    AuxExportKind::Getter { .. } | AuxExportKind::Setter { .. } => {
                        builder.method(false)
                    }
                    AuxExportKind::Method { consumed, .. } => builder.method(*consumed),
                }
            }
            Kind::Import(_) => {}
            Kind::Adapter => {}
        }

        // Process the `binding` and generate a bunch of JS/TypeScript/etc.
        let binding::JsFunction {
            ts_sig,
            ts_arg_tys,
            ts_ret_ty,
            js_doc,
            code,
        } = builder
            .process(&adapter, instrs, arg_names)
            .with_context(|| match kind {
                Kind::Export(e) => format!("failed to generate bindings for `{}`", e.debug_name),
                Kind::Import(i) => {
                    let i = builder.cx.module.imports.get(i);
                    format!(
                        "failed to generate bindings for import of `{}::{}`",
                        i.module, i.name
                    )
                }
                Kind::Adapter => format!("failed to generates bindings for adapter"),
            })?;

        // Once we've got all the JS then put it in the right location depending
        // on what's being exported.
        match kind {
            Kind::Export(export) => {
                let docs = format_doc_comments(&export.comments, Some(js_doc));
                match &export.kind {
                    AuxExportKind::Function(name) => {
                        self.export(&name, &format!("function{}", code), Some(docs))?;
                        self.globals.push_str("\n");
                        self.typescript.push_str("export function ");
                        self.typescript.push_str(&name);
                        self.typescript.push_str(&ts_sig);
                        self.typescript.push_str(";\n");
                    }
                    AuxExportKind::Constructor(class) => {
                        let exported = require_class(&mut self.exported_classes, class);
                        if exported.has_constructor {
                            bail!("found duplicate constructor for class `{}`", class);
                        }
                        exported.has_constructor = true;
                        exported.push(&docs, "constructor", "", &code, &ts_sig);
                    }
                    AuxExportKind::Getter { class, field } => {
                        let ret_ty = ts_ret_ty.unwrap();
                        let exported = require_class(&mut self.exported_classes, class);
                        exported.push_getter(&docs, field, &code, &ret_ty);
                    }
                    AuxExportKind::Setter { class, field } => {
                        let arg_ty = ts_arg_tys[0].clone();
                        let exported = require_class(&mut self.exported_classes, class);
                        exported.push_setter(&docs, field, &code, &arg_ty);
                    }
                    AuxExportKind::StaticFunction { class, name } => {
                        let exported = require_class(&mut self.exported_classes, class);
                        exported.push(&docs, name, "static ", &code, &ts_sig);
                    }
                    AuxExportKind::Method { class, name, .. } => {
                        let exported = require_class(&mut self.exported_classes, class);
                        exported.push(&docs, name, "", &code, &ts_sig);
                    }
                }
            }
            Kind::Import(core) => {
                self.wasm_import_definitions
                    .insert(core, format!("function{}", code));
            }
            Kind::Adapter => {
                self.globals.push_str("function ");
                self.globals.push_str(&self.adapter_name(id));
                self.globals.push_str(&code);
                self.globals.push_str("\n\n");
            }
        }
        return Ok(());
    }

    /// Returns whether we should disable the logic, in debug mode, to catch an
    /// error, log it, and rethrow it. This is only intended for user-defined
    /// imports, not all imports of everything.
    fn import_never_log_error(&self, import: &AuxImport) -> bool {
        match import {
            // Some intrinsics are intended to exactly throw errors, and in
            // general we shouldn't have exceptions in our intrinsics to debug,
            // so skip these.
            AuxImport::Intrinsic(_) => true,

            // Otherwise assume everything else gets a debug log of errors
            // thrown in debug mode.
            _ => false,
        }
    }

    /// Attempts to directly hook up the `id` import in the wasm module with
    /// the `instrs` specified.
    ///
    /// If this succeeds it returns `Ok(true)`, otherwise if it cannot be
    /// directly imported then `Ok(false)` is returned.
    fn attempt_direct_import(
        &mut self,
        id: ImportId,
        instrs: &[InstructionData],
    ) -> Result<bool, Error> {
        // First up extract the ID of the single called adapter, if any.
        let mut call = None;
        for instr in instrs {
            match instr.instr {
                Instruction::CallAdapter(id) => {
                    if call.is_some() {
                        return Ok(false);
                    } else {
                        call = Some(id);
                    }
                }
                Instruction::CallExport(_)
                | Instruction::CallTableElement(_)
                | Instruction::Standard(wit_walrus::Instruction::CallCore(_))
                | Instruction::Standard(wit_walrus::Instruction::CallAdapter(_)) => {
                    return Ok(false)
                }
                _ => {}
            }
        }
        let adapter = match call {
            Some(id) => id,
            None => return Ok(false),
        };
        match &self.wit.adapters[&adapter].kind {
            AdapterKind::Import { kind, .. } => match kind {
                AdapterJsImportKind::Normal => {}
                // method/constructors need glue because we either need to
                // invoke them as `new` or we need to invoke them with
                // method-call syntax to get the `this` parameter right.
                AdapterJsImportKind::Method | AdapterJsImportKind::Constructor => return Ok(false),
            },
            // This is an adapter-to-adapter call, so it needs a shim.
            AdapterKind::Local { .. } => return Ok(false),
        }

        // Next up check to make sure that this import is to a bare JS value
        // itself, no extra fluff intended.
        let js = match &self.aux.import_map[&adapter] {
            AuxImport::Value(AuxValue::Bare(js)) => js,
            _ => return Ok(false),
        };

        // Make sure this isn't variadic in any way which means we need some
        // sort of adapter glue.
        if self.aux.imports_with_variadic.contains(&adapter) {
            return Ok(false);
        }

        // Ensure that every single instruction can be represented without JS
        // glue being generated, aka it's covered by the JS ECMAScript bindings
        // for wasm.
        if !self.representable_without_js_glue(instrs) {
            return Ok(false);
        }

        // If there's no field projection happening here and this is a direct
        // import from an ES-looking module, then we can actually just hook this
        // up directly in the wasm file itself. Note that this is covered in the
        // various output formats as well:
        //
        // * `bundler` - they think wasm is an ES module anyway
        // * `web` - we're sure to emit more `import` directives during
        //   `gen_init` and we update the import object accordingly.
        // * `nodejs` - the polyfill we have for requiring a wasm file as a node
        //   module will naturally emit `require` directives for the module
        //   listed on each wasm import.
        // * `no-modules` - imports aren't allowed here anyway from other
        //   modules and an error is generated.
        if js.fields.len() == 0 {
            match &js.name {
                JsImportName::Module { module, name } => {
                    let import = self.module.imports.get_mut(id);
                    import.module = module.clone();
                    import.name = name.clone();
                    return Ok(true);
                }
                JsImportName::LocalModule { module, name } => {
                    let module = self.config.local_module_name(module);
                    let import = self.module.imports.get_mut(id);
                    import.module = module;
                    import.name = name.clone();
                    return Ok(true);
                }
                JsImportName::InlineJs {
                    unique_crate_identifier,
                    snippet_idx_in_crate,
                    name,
                } => {
                    let module = self
                        .config
                        .inline_js_module_name(unique_crate_identifier, *snippet_idx_in_crate);
                    let import = self.module.imports.get_mut(id);
                    import.module = module;
                    import.name = name.clone();
                    return Ok(true);
                }

                // Fall through below to requiring a JS shim to create an item
                // that we can import. These are plucked from the global
                // environment so there's no way right now to describe these
                // imports in an ES module-like fashion.
                JsImportName::Global { .. } | JsImportName::VendorPrefixed { .. } => {}
            }
        }

        self.expose_not_defined();
        let name = self.import_name(js)?;
        let js = format!(
            "typeof {name} == 'function' ? {name} : notDefined('{name}')",
            name = name,
        );
        self.wasm_import_definitions.insert(id, js);
        Ok(true)
    }

    fn representable_without_js_glue(&self, instrs: &[InstructionData]) -> bool {
        use Instruction::*;
        let standard_enabled = self.config.wasm_interface_types;

        let mut last_arg = None;
        let mut saw_call = false;
        for instr in instrs {
            match instr.instr {
                // Is an adapter section getting emitted? If so, then every
                // standard operation is natively supported!
                Standard(_) if standard_enabled => {}

                // Fetching arguments is just that, a fetch, so no need for
                // glue. Note though that the arguments must be fetched in order
                // for this to actually work,
                Standard(wit_walrus::Instruction::ArgGet(i)) => {
                    if saw_call {
                        return false;
                    }
                    match (i, last_arg) {
                        (0, None) => last_arg = Some(0),
                        (n, Some(i)) if n == i + 1 => last_arg = Some(n),
                        _ => return false,
                    }
                }

                // Similarly calling a function is the same as in JS, no glue
                // needed.
                CallAdapter(_) => saw_call = true,

                // Conversions to wasm integers are always supported since
                // they're coerced into i32/f32/f64 appropriately.
                Standard(wit_walrus::Instruction::IntToWasm { .. }) => {}

                // Converts from wasm to JS, however, only supports most
                // integers. Converting into a u32 isn't supported because we
                // need to generate glue to change the sign.
                Standard(wit_walrus::Instruction::WasmToInt {
                    output: wit_walrus::ValType::U32,
                    ..
                }) => return false,
                Standard(wit_walrus::Instruction::WasmToInt { .. }) => {}

                // JS spec automatically coerces boolean values to i32 of 0 or 1
                // depending on true/false
                I32FromBool => {}

                _ => return false,
            }
        }

        return true;
    }

    /// Generates a JS snippet appropriate for invoking `import`.
    ///
    /// This is generating code for `binding` where `bindings` has more type
    /// infomation. The `args` array is the list of JS expressions representing
    /// the arguments to pass to JS. Finally `variadic` indicates whether the
    /// last argument is a list to be splatted in a variadic way, and `prelude`
    /// is a location to push some more initialization JS if necessary.
    ///
    /// The returned value here is a JS expression which evaluates to the
    /// purpose of `AuxImport`, which depends on the kind of import.
    fn invoke_import(
        &mut self,
        import: &AuxImport,
        kind: AdapterJsImportKind,
        args: &[String],
        variadic: bool,
        prelude: &mut String,
    ) -> Result<String, Error> {
        let variadic_args = |js_arguments: &[String]| {
            Ok(if !variadic {
                format!("{}", js_arguments.join(", "))
            } else {
                let (last_arg, args) = match js_arguments.split_last() {
                    Some(pair) => pair,
                    None => bail!("a function with no arguments cannot be variadic"),
                };
                if args.len() > 0 {
                    format!("{}, ...{}", args.join(", "), last_arg)
                } else {
                    format!("...{}", last_arg)
                }
            })
        };
        match import {
            AuxImport::Value(val) => match kind {
                AdapterJsImportKind::Constructor => {
                    let js = match val {
                        AuxValue::Bare(js) => self.import_name(js)?,
                        _ => bail!("invalid import set for constructor"),
                    };
                    Ok(format!("new {}({})", js, variadic_args(&args)?))
                }
                AdapterJsImportKind::Method => {
                    let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| {
                        format!(
                            "GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}",
                            anchor, extra, field, which
                        )
                    };
                    let js = match val {
                        AuxValue::Bare(js) => self.import_name(js)?,
                        AuxValue::Getter(class, field) => {
                            self.expose_get_inherited_descriptor();
                            let class = self.import_name(class)?;
                            descriptor(&class, ".prototype", field, "get")
                        }
                        AuxValue::ClassGetter(class, field) => {
                            self.expose_get_inherited_descriptor();
                            let class = self.import_name(class)?;
                            descriptor(&class, "", field, "get")
                        }
                        AuxValue::Setter(class, field) => {
                            self.expose_get_inherited_descriptor();
                            let class = self.import_name(class)?;
                            descriptor(&class, ".prototype", field, "set")
                        }
                        AuxValue::ClassSetter(class, field) => {
                            self.expose_get_inherited_descriptor();
                            let class = self.import_name(class)?;
                            descriptor(&class, "", field, "set")
                        }
                    };
                    Ok(format!("{}.call({})", js, variadic_args(&args)?))
                }
                AdapterJsImportKind::Normal => {
                    let js = match val {
                        AuxValue::Bare(js) => self.import_name(js)?,
                        _ => bail!("invalid import set for free function"),
                    };
                    Ok(format!("{}({})", js, variadic_args(&args)?))
                }
            },

            AuxImport::ValueWithThis(class, name) => {
                let class = self.import_name(class)?;
                Ok(format!("{}.{}({})", class, name, variadic_args(&args)?))
            }

            AuxImport::Instanceof(js) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 1);
                let js = self.import_name(js)?;
                Ok(format!("{} instanceof {}", args[0], js))
            }

            AuxImport::Static(js) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 0);
                self.import_name(js)
            }

            AuxImport::Closure {
                dtor,
                mutable,
                adapter,
                nargs,
            } => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 3);
                let arg_names = (0..*nargs)
                    .map(|i| format!("arg{}", i))
                    .collect::<Vec<_>>()
                    .join(", ");
                let mut js = format!("({}) => {{\n", arg_names);
                // First up with a closure we increment the internal reference
                // count. This ensures that the Rust closure environment won't
                // be deallocated while we're invoking it.
                js.push_str("state.cnt++;\n");

                let table = self.export_function_table()?;
                let dtor = format!("wasm.{}.get({})", table, dtor);
                let call = self.adapter_name(*adapter);

                if *mutable {
                    // For mutable closures they can't be invoked recursively.
                    // To handle that we swap out the `this.a` pointer with zero
                    // while we invoke it. If we finish and the closure wasn't
                    // destroyed, then we put back the pointer so a future
                    // invocation can succeed.
                    js.push_str("const a = state.a;\n");
                    js.push_str("state.a = 0;\n");
                    js.push_str("try {\n");
                    js.push_str(&format!("return {}(a, state.b, {});\n", call, arg_names));
                    js.push_str("} finally {\n");
                    js.push_str("if (--state.cnt === 0) ");
                    js.push_str(&dtor);
                    js.push_str("(a, state.b);\n");
                    js.push_str("else state.a = a;\n");
                    js.push_str("}\n");
                } else {
                    // For shared closures they can be invoked recursively so we
                    // just immediately pass through `this.a`. If we end up
                    // executing the destructor, however, we clear out the
                    // `this.a` pointer to prevent it being used again the
                    // future.
                    js.push_str("try {\n");
                    js.push_str(&format!(
                        "return {}(state.a, state.b, {});\n",
                        call, arg_names
                    ));
                    js.push_str("} finally {\n");
                    js.push_str("if (--state.cnt === 0) {\n");
                    js.push_str(&dtor);
                    js.push_str("(state.a, state.b);\n");
                    js.push_str("state.a = 0;\n");
                    js.push_str("}\n");
                    js.push_str("}\n");
                }
                js.push_str("}\n");

                prelude.push_str(&format!(
                    "
                        const state = {{ a: {arg0}, b: {arg1}, cnt: 1 }};
                        const real = {body};
                        real.original = state;
                    ",
                    body = js,
                    arg0 = &args[0],
                    arg1 = &args[1],
                ));
                Ok("real".to_string())
            }

            AuxImport::StructuralMethod(name) => {
                assert!(kind == AdapterJsImportKind::Normal);
                let (receiver, args) = match args.split_first() {
                    Some(pair) => pair,
                    None => bail!("structural method calls must have at least one argument"),
                };
                Ok(format!("{}.{}({})", receiver, name, variadic_args(args)?))
            }

            AuxImport::StructuralGetter(field) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 1);
                Ok(format!("{}.{}", args[0], field))
            }

            AuxImport::StructuralClassGetter(class, field) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 0);
                let class = self.import_name(class)?;
                Ok(format!("{}.{}", class, field))
            }

            AuxImport::StructuralSetter(field) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 2);
                Ok(format!("{}.{} = {}", args[0], field, args[1]))
            }

            AuxImport::StructuralClassSetter(class, field) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 1);
                let class = self.import_name(class)?;
                Ok(format!("{}.{} = {}", class, field, args[0]))
            }

            AuxImport::IndexingGetterOfClass(class) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 1);
                let class = self.import_name(class)?;
                Ok(format!("{}[{}]", class, args[0]))
            }

            AuxImport::IndexingGetterOfObject => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 2);
                Ok(format!("{}[{}]", args[0], args[1]))
            }

            AuxImport::IndexingSetterOfClass(class) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 2);
                let class = self.import_name(class)?;
                Ok(format!("{}[{}] = {}", class, args[0], args[1]))
            }

            AuxImport::IndexingSetterOfObject => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 3);
                Ok(format!("{}[{}] = {}", args[0], args[1], args[2]))
            }

            AuxImport::IndexingDeleterOfClass(class) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 1);
                let class = self.import_name(class)?;
                Ok(format!("delete {}[{}]", class, args[0]))
            }

            AuxImport::IndexingDeleterOfObject => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 2);
                Ok(format!("delete {}[{}]", args[0], args[1]))
            }

            AuxImport::WrapInExportedClass(class) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                assert_eq!(args.len(), 1);
                self.require_class_wrap(class);
                Ok(format!("{}.__wrap({})", class, args[0]))
            }

            AuxImport::Intrinsic(intrinsic) => {
                assert!(kind == AdapterJsImportKind::Normal);
                assert!(!variadic);
                self.invoke_intrinsic(intrinsic, args, prelude)
            }
        }
    }

    /// Same as `invoke_import` above, except more specialized and only used for
    /// generating the JS expression needed to implement a particular intrinsic.
    fn invoke_intrinsic(
        &mut self,
        intrinsic: &Intrinsic,
        args: &[String],
        prelude: &mut String,
    ) -> Result<String, Error> {
        let expr = match intrinsic {
            Intrinsic::JsvalEq => {
                assert_eq!(args.len(), 2);
                format!("{} === {}", args[0], args[1])
            }

            Intrinsic::IsFunction => {
                assert_eq!(args.len(), 1);
                format!("typeof({}) === 'function'", args[0])
            }

            Intrinsic::IsUndefined => {
                assert_eq!(args.len(), 1);
                format!("{} === undefined", args[0])
            }

            Intrinsic::IsNull => {
                assert_eq!(args.len(), 1);
                format!("{} === null", args[0])
            }

            Intrinsic::IsObject => {
                assert_eq!(args.len(), 1);
                prelude.push_str(&format!("const val = {};\n", args[0]));
                format!("typeof(val) === 'object' && val !== null")
            }

            Intrinsic::IsSymbol => {
                assert_eq!(args.len(), 1);
                format!("typeof({}) === 'symbol'", args[0])
            }

            Intrinsic::IsString => {
                assert_eq!(args.len(), 1);
                format!("typeof({}) === 'string'", args[0])
            }

            Intrinsic::IsFalsy => {
                assert_eq!(args.len(), 1);
                format!("!{}", args[0])
            }

            Intrinsic::ObjectCloneRef => {
                assert_eq!(args.len(), 1);
                args[0].clone()
            }

            Intrinsic::ObjectDropRef => {
                assert_eq!(args.len(), 1);
                args[0].clone()
            }

            Intrinsic::CallbackDrop => {
                assert_eq!(args.len(), 1);
                prelude.push_str(&format!("const obj = {}.original;\n", args[0]));
                prelude.push_str("if (obj.cnt-- == 1) {\n");
                prelude.push_str("obj.a = 0;\n");
                prelude.push_str("return true;\n");
                prelude.push_str("}\n");
                "false".to_string()
            }

            Intrinsic::CallbackForget => {
                assert_eq!(args.len(), 1);
                args[0].clone()
            }

            Intrinsic::NumberNew => {
                assert_eq!(args.len(), 1);
                args[0].clone()
            }

            Intrinsic::StringNew => {
                assert_eq!(args.len(), 1);
                args[0].clone()
            }

            Intrinsic::SymbolNamedNew => {
                assert_eq!(args.len(), 1);
                format!("Symbol({})", args[0])
            }

            Intrinsic::SymbolAnonymousNew => {
                assert_eq!(args.len(), 0);
                "Symbol()".to_string()
            }

            Intrinsic::NumberGet => {
                assert_eq!(args.len(), 1);
                prelude.push_str(&format!("const obj = {};\n", args[0]));
                format!("typeof(obj) === 'number' ? obj : undefined")
            }

            Intrinsic::StringGet => {
                assert_eq!(args.len(), 1);
                prelude.push_str(&format!("const obj = {};\n", args[0]));
                format!("typeof(obj) === 'string' ? obj : undefined")
            }

            Intrinsic::BooleanGet => {
                assert_eq!(args.len(), 1);
                prelude.push_str(&format!("const v = {};\n", args[0]));
                format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
            }

            Intrinsic::Throw => {
                assert_eq!(args.len(), 1);
                format!("throw new Error({})", args[0])
            }

            Intrinsic::Rethrow => {
                assert_eq!(args.len(), 1);
                format!("throw {}", args[0])
            }

            Intrinsic::Module => {
                assert_eq!(args.len(), 0);
                if !self.config.mode.no_modules() && !self.config.mode.web() {
                    bail!(
                        "`wasm_bindgen::module` is currently only supported with \
                         `--target no-modules` and `--target web`"
                    );
                }
                format!("init.__wbindgen_wasm_module")
            }

            Intrinsic::Memory => {
                assert_eq!(args.len(), 0);
                let mut memories = self.module.memories.iter();
                let memory = memories
                    .next()
                    .ok_or_else(|| anyhow!("no memory found to return in memory intrinsic"))?
                    .id();
                if memories.next().is_some() {
                    bail!(
                        "multiple memories found, unsure which to return \
                         from memory intrinsic"
                    );
                }
                drop(memories);
                format!("wasm.{}", self.export_name_of(memory))
            }

            Intrinsic::FunctionTable => {
                assert_eq!(args.len(), 0);
                let name = self.export_function_table()?;
                format!("wasm.{}", name)
            }

            Intrinsic::DebugString => {
                assert_eq!(args.len(), 1);
                self.expose_debug_string();
                format!("debugString({})", args[0])
            }

            Intrinsic::JsonParse => {
                assert_eq!(args.len(), 1);
                format!("JSON.parse({})", args[0])
            }

            Intrinsic::JsonSerialize => {
                assert_eq!(args.len(), 1);
                // Turns out `JSON.stringify(undefined) === undefined`, so if
                // we're passed `undefined` reinterpret it as `null` for JSON
                // purposes.
                prelude.push_str(&format!("const obj = {};\n", args[0]));
                "JSON.stringify(obj === undefined ? null : obj)".to_string()
            }

            Intrinsic::AnyrefHeapLiveCount => {
                assert_eq!(args.len(), 0);
                self.expose_global_heap();
                prelude.push_str(
                    "
                        let free_count = 0;
                        let next = heap_next;
                        while (next < heap.length) {
                            free_count += 1;
                            next = heap[next];
                        }
                    ",
                );
                format!(
                    "heap.length - free_count - {} - {}",
                    INITIAL_HEAP_OFFSET,
                    INITIAL_HEAP_VALUES.len(),
                )
            }

            Intrinsic::InitAnyrefTable => {
                let table = self
                    .aux
                    .anyref_table
                    .ok_or_else(|| anyhow!("must enable anyref to use anyref intrinsic"))?;
                let name = self.export_name_of(table);
                // Grow the table to insert our initial values, and then also
                // set the 0th slot to `undefined` since that's what we've
                // historically used for our ABI which is that the index of 0
                // returns `undefined` for types like `None` going out.
                let mut base = format!(
                    "
                      const table = wasm.{};
                      const offset = table.grow({});
                      table.set(0, undefined);
                    ",
                    name,
                    INITIAL_HEAP_VALUES.len(),
                );
                for (i, value) in INITIAL_HEAP_VALUES.iter().enumerate() {
                    base.push_str(&format!("table.set(offset + {}, {});\n", i, value));
                }
                base
            }
        };
        Ok(expr)
    }

    fn generate_enum(&mut self, enum_: &AuxEnum) -> Result<(), Error> {
        let mut variants = String::new();

        self.typescript
            .push_str(&format!("export enum {} {{", enum_.name));
        for (name, value) in enum_.variants.iter() {
            variants.push_str(&format!("{}:{},", name, value));
            self.typescript.push_str(&format!("\n  {},", name));
        }
        self.typescript.push_str("\n}\n");
        self.export(
            &enum_.name,
            &format!("Object.freeze({{ {} }})", variants),
            Some(format_doc_comments(&enum_.comments, None)),
        )?;

        Ok(())
    }

    fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> {
        let class = require_class(&mut self.exported_classes, &struct_.name);
        class.comments = format_doc_comments(&struct_.comments, None);
        class.is_inspectable = struct_.is_inspectable;
        Ok(())
    }

    fn process_package_json(&mut self, path: &Path) -> Result<(), Error> {
        if !self.config.mode.nodejs() && !self.config.mode.bundler() {
            bail!(
                "NPM dependencies have been specified in `{}` but \
                 this is only compatible with the `bundler` and `nodejs` targets",
                path.display(),
            );
        }

        let contents =
            fs::read_to_string(path).context(format!("failed to read `{}`", path.display()))?;
        let json: serde_json::Value = serde_json::from_str(&contents)?;
        let object = match json.as_object() {
            Some(s) => s,
            None => bail!(
                "expected `package.json` to have an JSON object in `{}`",
                path.display()
            ),
        };
        let mut iter = object.iter();
        let (key, value) = match iter.next() {
            Some(pair) => pair,
            None => return Ok(()),
        };
        if key != "dependencies" || iter.next().is_some() {
            bail!(
                "NPM manifest found at `{}` can currently only have one key, \
                 `dependencies`, and no other fields",
                path.display()
            );
        }
        let value = match value.as_object() {
            Some(s) => s,
            None => bail!(
                "expected `dependencies` to be a JSON object in `{}`",
                path.display()
            ),
        };

        for (name, value) in value.iter() {
            let value = match value.as_str() {
                Some(s) => s,
                None => bail!(
                    "keys in `dependencies` are expected to be strings in `{}`",
                    path.display()
                ),
            };
            if let Some((prev, _prev_version)) = self.npm_dependencies.get(name) {
                bail!(
                    "dependency on NPM package `{}` specified in two `package.json` files, \
                     which at the time is not allowed:\n  * {}\n  * {}",
                    name,
                    path.display(),
                    prev.display(),
                )
            }

            self.npm_dependencies
                .insert(name.to_string(), (path.to_path_buf(), value.to_string()));
        }

        Ok(())
    }

    fn expose_debug_string(&mut self) {
        if !self.should_write_global("debug_string") {
            return;
        }

        self.global(
            "
           function debugString(val) {
                // primitive types
                const type = typeof val;
                if (type == 'number' || type == 'boolean' || val == null) {
                    return  `${val}`;
                }
                if (type == 'string') {
                    return `\"${val}\"`;
                }
                if (type == 'symbol') {
                    const description = val.description;
                    if (description == null) {
                        return 'Symbol';
                    } else {
                        return `Symbol(${description})`;
                    }
                }
                if (type == 'function') {
                    const name = val.name;
                    if (typeof name == 'string' && name.length > 0) {
                        return `Function(${name})`;
                    } else {
                        return 'Function';
                    }
                }
                // objects
                if (Array.isArray(val)) {
                    const length = val.length;
                    let debug = '[';
                    if (length > 0) {
                        debug += debugString(val[0]);
                    }
                    for(let i = 1; i < length; i++) {
                        debug += ', ' + debugString(val[i]);
                    }
                    debug += ']';
                    return debug;
                }
                // Test for built-in
                const builtInMatches = /\\[object ([^\\]]+)\\]/.exec(toString.call(val));
                let className;
                if (builtInMatches.length > 1) {
                    className = builtInMatches[1];
                } else {
                    // Failed to match the standard '[object ClassName]'
                    return toString.call(val);
                }
                if (className == 'Object') {
                    // we're a user defined class or Object
                    // JSON.stringify avoids problems with cycles, and is generally much
                    // easier than looping through ownProperties of `val`.
                    try {
                        return 'Object(' + JSON.stringify(val) + ')';
                    } catch (_) {
                        return 'Object';
                    }
                }
                // errors
                if (val instanceof Error) {
                    return `${val.name}: ${val.message}\\n${val.stack}`;
                }
                // TODO we could test for more things here, like `Set`s and `Map`s.
                return className;
            }
        ",
        );
    }

    fn export_function_table(&mut self) -> Result<String, Error> {
        match self.module.tables.main_function_table()? {
            Some(id) => Ok(self.export_name_of(id)),
            None => bail!("no function table found in module"),
        }
    }

    fn export_name_of(&mut self, id: impl Into<walrus::ExportItem>) -> String {
        let id = id.into();
        let export = self.module.exports.iter().find(|e| {
            use walrus::ExportItem::*;

            match (e.item, id) {
                (Function(a), Function(b)) => a == b,
                (Table(a), Table(b)) => a == b,
                (Memory(a), Memory(b)) => a == b,
                (Global(a), Global(b)) => a == b,
                _ => false,
            }
        });
        if let Some(export) = export {
            return export.name.clone();
        }
        let default_name = format!("__wbindgen_export_{}", self.next_export_idx);
        self.next_export_idx += 1;
        let name = match id {
            walrus::ExportItem::Function(f) => match &self.module.funcs.get(f).name {
                Some(s) => to_js_identifier(s),
                None => default_name,
            },
            _ => default_name,
        };
        self.module.exports.add(&name, id);
        return name;

        // Not really an exhaustive list, but works for our purposes.
        fn to_js_identifier(name: &str) -> String {
            name.chars()
                .map(|c| {
                    if c.is_ascii() && (c.is_alphabetic() || c.is_numeric()) {
                        c
                    } else {
                        '_'
                    }
                })
                .collect()
        }
    }

    fn adapter_name(&self, id: AdapterId) -> String {
        format!("__wbg_adapter_{}", id.0)
    }
}

fn check_duplicated_getter_and_setter_names(
    exports: &[(&AdapterId, &AuxExport)],
) -> Result<(), Error> {
    let verify_exports =
        |first_class, first_field, second_class, second_field| -> Result<(), Error> {
            let both_are_in_the_same_class = first_class == second_class;
            let both_are_referencing_the_same_field = first_field == second_field;
            if both_are_in_the_same_class && both_are_referencing_the_same_field {
                bail!(format!(
                    "There can be only one getter/setter definition for `{}` in `{}`",
                    first_field, first_class
                ));
            }
            Ok(())
        };
    for (idx, (_, first_export)) in exports.iter().enumerate() {
        for (_, second_export) in exports.iter().skip(idx + 1) {
            match (&first_export.kind, &second_export.kind) {
                (
                    AuxExportKind::Getter {
                        class: first_class,
                        field: first_field,
                    },
                    AuxExportKind::Getter {
                        class: second_class,
                        field: second_field,
                    },
                ) => verify_exports(first_class, first_field, second_class, second_field)?,
                (
                    AuxExportKind::Setter {
                        class: first_class,
                        field: first_field,
                    },
                    AuxExportKind::Setter {
                        class: second_class,
                        field: second_field,
                    },
                ) => verify_exports(first_class, first_field, second_class, second_field)?,
                _ => {}
            }
        }
    }
    Ok(())
}

fn generate_identifier(name: &str, used_names: &mut HashMap<String, usize>) -> String {
    let cnt = used_names.entry(name.to_string()).or_insert(0);
    *cnt += 1;
    // We want to mangle `default` at once, so we can support default exports and don't generate
    // invalid glue code like this: `import { default } from './module';`.
    if *cnt == 1 && name != "default" {
        name.to_string()
    } else {
        format!("{}{}", name, cnt)
    }
}

fn format_doc_comments(comments: &str, js_doc_comments: Option<String>) -> String {
    let body: String = comments.lines().map(|c| format!("*{}\n", c)).collect();
    let doc = if let Some(docs) = js_doc_comments {
        docs.lines().map(|l| format!("* {} \n", l)).collect()
    } else {
        String::new()
    };
    format!("/**\n{}{}*/\n", body, doc)
}

fn require_class<'a>(
    exported_classes: &'a mut Option<BTreeMap<String, ExportedClass>>,
    name: &str,
) -> &'a mut ExportedClass {
    exported_classes
        .as_mut()
        .expect("classes already written")
        .entry(name.to_string())
        .or_insert_with(ExportedClass::default)
}

impl ExportedClass {
    fn push(&mut self, docs: &str, function_name: &str, function_prefix: &str, js: &str, ts: &str) {
        self.contents.push_str(docs);
        self.contents.push_str(function_prefix);
        self.contents.push_str(function_name);
        self.contents.push_str(js);
        self.contents.push_str("\n");
        self.typescript.push_str(docs);
        self.typescript.push_str("  ");
        self.typescript.push_str(function_prefix);
        self.typescript.push_str(function_name);
        self.typescript.push_str(ts);
        self.typescript.push_str(";\n");
    }

    /// Used for adding a getter to a class, mainly to ensure that TypeScript
    /// generation is handled specially.
    fn push_getter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
        self.push_accessor(docs, field, js, "get ", ret_ty);
        self.readable_properties.push(field.to_string());
    }

    /// Used for adding a setter to a class, mainly to ensure that TypeScript
    /// generation is handled specially.
    fn push_setter(&mut self, docs: &str, field: &str, js: &str, ret_ty: &str) {
        let has_setter = self.push_accessor(docs, field, js, "set ", ret_ty);
        *has_setter = true;
    }

    fn push_accessor(
        &mut self,
        docs: &str,
        field: &str,
        js: &str,
        prefix: &str,
        ret_ty: &str,
    ) -> &mut bool {
        self.contents.push_str(docs);
        self.contents.push_str(prefix);
        self.contents.push_str(field);
        self.contents.push_str(js);
        self.contents.push_str("\n");
        let (ty, has_setter) = self
            .typescript_fields
            .entry(field.to_string())
            .or_insert_with(Default::default);
        *ty = ret_ty.to_string();
        has_setter
    }
}

#[test]
fn test_generate_identifier() {
    let mut used_names: HashMap<String, usize> = HashMap::new();
    assert_eq!(
        generate_identifier("someVar", &mut used_names),
        "someVar".to_string()
    );
    assert_eq!(
        generate_identifier("someVar", &mut used_names),
        "someVar2".to_string()
    );
    assert_eq!(
        generate_identifier("default", &mut used_names),
        "default1".to_string()
    );
    assert_eq!(
        generate_identifier("default", &mut used_names),
        "default2".to_string()
    );
}

struct MemView {
    name: &'static str,
    num: usize,
}

impl fmt::Display for MemView {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}{}", self.name, self.num)
    }
}