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>>, 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)>>, /// A map of each wasm import and what JS to hook up to it. wasm_import_definitions: HashMap, /// A map from an import to the name we've locally imported it as. imported_names: HashMap, /// 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, exported_classes: Option>, /// 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, /// A mapping of a index for memories as we see them. Used in function /// names. memory_indices: HashMap, table_indices: HashMap, } #[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, /// Map from field name to type as a string plus whether it has a setter typescript_fields: HashMap, } 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, Error> { Ok(Context { globals: String::new(), imports_post: String::new(), typescript: "/* tslint: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>) -> bool { self.exposed_globals.as_mut().unwrap().insert(name.into()) } fn export( &mut self, export_name: &str, contents: &str, comments: Option, ) -> 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 { 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}}\n\ */\n\ export default function init \ (module_or_path{}: RequestInfo | BufferSource | WebAssembly.Module{}): Promise; ", 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'); }" } _ => "", }; 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::>(); 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::>(); 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 \"\"; } }()); 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 { 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 { 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 { 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 { 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::>(); 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 js = 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"), })?; let ts = builder.typescript_signature(); let js_doc = builder.js_doc_comments(); // 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{}", js), Some(docs))?; self.globals.push_str("\n"); self.typescript.push_str("export function "); self.typescript.push_str(&name); self.typescript.push_str(&ts); 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", "", &js, &ts); } AuxExportKind::Getter { class, field } => { let ret_ty = builder.ts_ret.as_ref().unwrap().ty.clone(); let exported = require_class(&mut self.exported_classes, class); exported.push_getter(&docs, field, &js, &ret_ty); } AuxExportKind::Setter { class, field } => { let arg_ty = builder.ts_args[0].ty.clone(); let exported = require_class(&mut self.exported_classes, class); exported.push_setter(&docs, field, &js, &arg_ty); } AuxExportKind::StaticFunction { class, name } => { let exported = require_class(&mut self.exported_classes, class); exported.push(&docs, name, "static ", &js, &ts); } AuxExportKind::Method { class, name, .. } => { let exported = require_class(&mut self.exported_classes, class); exported.push(&docs, name, "", &js, &ts); } } } Kind::Import(core) => { self.wasm_import_definitions .insert(core, format!("function{}", js)); } Kind::Adapter => { self.globals.push_str("function "); self.globals.push_str(&self.adapter_name(id)); self.globals.push_str(&js); 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 { // 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 { 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::>() .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 { 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 { 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) -> 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 { 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 { 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>, 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 = 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) } }