Alex Crichton 7e5e401076 Add an accessor for the function table
This commit adds an intrinsics to the `wasm_bindgen` crate which
accesses the `WebAssembly.Table` which is the function table of the
module. Eventually the thinking is that a module would import its own
function table via native wasm functionality (via `anyref` and such),
but until that's implemented let's add a binding for it ourselves!

Closes #1427
2019-04-08 10:40:46 -07:00

3204 lines
113 KiB
Rust

use crate::decode;
use crate::descriptor::{Descriptor, VectorKind};
use crate::{Bindgen, EncodeInto, OutputMode};
use failure::{bail, Error, ResultExt};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::env;
use std::fs;
use walrus::{MemoryId, Module};
use wasm_bindgen_wasm_interpreter::Interpreter;
mod js2rust;
use self::js2rust::{ExportedShim, Js2Rust};
mod rust2js;
use self::rust2js::Rust2Js;
mod closures;
pub struct Context<'a> {
pub globals: String,
pub imports: String,
pub imports_post: String,
pub footer: String,
pub typescript: String,
pub exposed_globals: Option<HashSet<&'static str>>,
pub required_internal_exports: HashSet<&'static str>,
pub imported_functions: HashSet<&'a str>,
pub imported_statics: HashSet<&'a str>,
pub config: &'a Bindgen,
pub module: &'a mut Module,
pub start: Option<String>,
/// A map which maintains a list of what identifiers we've imported and what
/// they're named locally.
///
/// The `Option<String>` key is the module that identifiers were imported
/// from, `None` being the global module. The second key is a map of
/// identifiers we've already imported from the module to what they're
/// called locally.
pub imported_names: HashMap<ImportModule<'a>, HashMap<&'a str, String>>,
/// A set of all imported identifiers to the number of times they've been
/// imported, used to generate new identifiers.
pub imported_identifiers: HashMap<String, usize>,
/// A map of all imported shim functions which can actually be directly
/// imported from the containing module. The mapping here maps to a tuple,
/// where the first element is the module to import from and the second
/// element is the name to import from the module.
///
/// Note that for `direct_imports` no shims are generated in JS that
/// wasm-bindgen emits.
pub direct_imports: HashMap<&'a str, (&'a str, &'a str)>,
pub exported_classes: Option<BTreeMap<String, ExportedClass>>,
pub function_table_needed: bool,
pub interpreter: &'a mut Interpreter,
pub memory: MemoryId,
/// A map of all local modules we've found, from the identifier they're
/// known as to their actual JS contents.
pub local_modules: HashMap<&'a str, &'a str>,
/// A map of how many snippets we've seen from each unique crate identifier,
/// used to number snippets correctly when writing them to the filesystem
/// when there's multiple snippets within one crate that aren't all part of
/// the same `Program`.
pub snippet_offsets: HashMap<&'a str, usize>,
/// All package.json dependencies we've learned about so far
pub package_json_read: HashSet<&'a str>,
/// 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, (&'a str, String)>,
pub anyref: wasm_bindgen_anyref_xform::Context,
}
#[derive(Default)]
pub struct ExportedClass {
comments: String,
contents: String,
typescript: String,
has_constructor: bool,
wrap_needed: bool,
}
pub struct SubContext<'a, 'b: 'a> {
pub program: &'b decode::Program<'b>,
pub cx: &'a mut Context<'b>,
pub vendor_prefixes: HashMap<&'b str, Vec<&'b str>>,
}
pub enum ImportTarget {
Function(String),
Method(String),
Constructor(String),
StructuralMethod(String),
StructuralGetter(Option<String>, String),
StructuralSetter(Option<String>, String),
StructuralIndexingGetter(Option<String>),
StructuralIndexingSetter(Option<String>),
StructuralIndexingDeleter(Option<String>),
}
/// Return value of `determine_import` which is where we look at an imported
/// function AST and figure out where it's actually being imported from
/// (performing some validation checks and whatnot).
enum Import<'a> {
/// An item is imported from the global scope. The `name` is what's imported
/// and the optional `field` is the field on that item we're importing.
Global {
name: &'a str,
field: Option<&'a str>,
},
/// Same as `Global`, except the `name` is imported via an ESM import from
/// the specified `module` path.
Module {
module: &'a str,
name: &'a str,
field: Option<&'a str>,
},
/// Same as `Module`, except we're importing from a local module defined in
/// a local JS snippet.
LocalModule {
module: &'a str,
name: &'a str,
field: Option<&'a str>,
},
/// Same as `Module`, except we're importing from an `inline_js` attribute
InlineJs {
unique_crate_identifier: &'a str,
snippet_idx_in_crate: usize,
name: &'a str,
field: Option<&'a str>,
},
/// A global import which may have a number of vendor prefixes associated
/// with it, like `webkitAudioPrefix`. The `name` is the name to test
/// whether it's prefixed.
VendorPrefixed {
name: &'a str,
prefixes: Vec<&'a str>,
},
}
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> {
fn should_write_global(&mut self, name: &'static str) -> bool {
self.exposed_globals.as_mut().unwrap().insert(name)
}
fn export(&mut self, name: &str, contents: &str, comments: Option<String>) {
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!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
} else {
format!("module.exports.{} = {};\n", name, contents)
}
}
OutputMode::NoModules { .. } => {
if contents.starts_with("class") {
format!("{1}\n__exports.{0} = {0};\n", name, contents)
} else {
format!("__exports.{} = {};\n", name, contents)
}
}
OutputMode::Bundler { .. }
| OutputMode::Node {
experimental_modules: true,
} => {
if contents.starts_with("function") {
format!("export function {}{}\n", name, &contents[8..])
} else if contents.starts_with("class") {
format!("export {}\n", contents)
} else {
format!("export const {} = {};\n", name, contents)
}
}
OutputMode::Web => {
// In web mode there's no need to export the internals of
// wasm-bindgen as we're not using the module itself as the
// import object but rather the `__exports` map we'll be
// initializing below.
let export = if name.starts_with("__wbindgen")
|| name.starts_with("__wbg_")
|| name.starts_with("__widl_")
{
""
} else {
"export "
};
if contents.starts_with("function") {
format!("{}function {}{}\n", export, name, &contents[8..])
} else if contents.starts_with("class") {
format!("{}{}\n", export, contents)
} else {
format!("{}const {} = {};\n", export, name, contents)
}
}
};
self.global(&global);
if self.config.mode.web() {
self.global(&format!("__exports.{} = {0};", name));
}
}
fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> {
if !self.required_internal_exports.insert(name) {
return Ok(());
}
if self.module.exports.iter().any(|e| e.name == name) {
return Ok(());
}
bail!(
"the exported function `{}` is required to generate bindings \
but it was not found in the wasm file, perhaps the `std` feature \
of the `wasm-bindgen` crate needs to be enabled?",
name
);
}
pub fn finalize(&mut self, module_name: &str) -> Result<(String, String), Error> {
// Wire up all default intrinsics, those which don't have any sort of
// dependency on the clsoure/anyref/etc passes. This is where almost all
// intrinsics are wired up.
self.wire_up_initial_intrinsics()?;
// Next up, perform our closure rewriting pass. This is where we'll
// update invocations of the closure intrinsics we have to instead call
// appropriate JS functions which actually create closures.
closures::rewrite(self).with_context(|_| "failed to generate internal closure shims")?;
// 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()?;
// And now that we're almost ready, run the final "anyref" pass. This is
// where we transform a wasm module which doesn't actually use `anyref`
// anywhere to using the type internally. The transformation here is
// based off all the previous data we've collected so far for each
// import/export listed.
self.anyref.run(self.module)?;
// With our transforms finished, we can now wire up the final list of
// intrinsics which may depend on the passes run above.
self.wire_up_late_intrinsics()?;
// We're almost done here, so we can delete any internal exports (like
// `__wbindgen_malloc`) if none of our JS glue actually needed it.
self.unexport_unused_internal_exports();
// Handle the `start` function, if one was specified. If we're in a
// --test mode (such as wasm-bindgen-test-runner) then we skip this
// entirely. Otherwise we want to first add a start function to the
// `start` section if one is specified.
//
// Note that once a start function is added, if any, we immediately
// un-start it. This is done because we require that the JS glue
// initializes first, so we execute wasm startup manually once the JS
// glue is all in place.
let mut needs_manual_start = false;
if self.config.emit_start {
self.add_start_function()?;
needs_manual_start = self.unstart_start_function();
}
// If our JS glue needs to access the function table, then do so here.
// JS closure shim generation may access the function table as an
// example, but if there's no closures in the module there's no need to
// export it!
self.export_table()?;
// After all we've done, especially
// `unexport_unused_internal_exports()`, we probably have a bunch of
// garbage in the module that's no longer necessary, so delete
// everything that we don't actually need.
walrus::passes::gc::run(self.module);
// Almost there, but before we're done make sure to rewrite the `module`
// field of all imports in the wasm module. The field is currently
// always `__wbindgen_placeholder__` coming out of rustc, but we need to
// update that here to the shim file or an actual ES module.
self.rewrite_imports(module_name);
// We likely made a ton of modifications, so add ourselves to the
// producers section!
self.update_producers_section();
// 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());
Ok(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) -> (String, String) {
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());
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(&module_name, needs_manual_start);
self.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,
} => {
self.footer
.push_str(&format!("wasm = require('./{}_bg');\n", module_name));
if needs_manual_start {
self.footer.push_str("wasm.__wbindgen_start();\n");
}
js.push_str("var wasm;\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,
} => {
self.imports
.push_str(&format!("import * as wasm from './{}_bg';\n", module_name));
if needs_manual_start {
self.footer.push_str("wasm.__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("const __exports = {};\n");
self.imports_post.push_str("let wasm;\n");
init = self.gen_init(&module_name, needs_manual_start);
self.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(&self.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(&self.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");
}
(js, ts)
}
fn wire_up_initial_intrinsics(&mut self) -> Result<(), Error> {
self.bind("__wbindgen_string_new", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_string_new",
&[],
true,
);
me.expose_get_string_from_wasm();
Ok(format!(
"function(p, l) {{ return {}; }}",
me.add_heap_object("getStringFromWasm(p, l)")
))
})?;
self.bind("__wbindgen_number_new", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_number_new",
&[],
true,
);
Ok(format!(
"function(i) {{ return {}; }}",
me.add_heap_object("i")
))
})?;
self.bind("__wbindgen_number_get", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_number_get",
&[(0, false)],
false,
);
me.expose_uint8_memory();
Ok(format!(
"
function(n, invalid) {{
let obj = {};
if (typeof(obj) === 'number') return obj;
getUint8Memory()[invalid] = 1;
return 0;
}}
",
me.get_object("n"),
))
})?;
self.bind("__wbindgen_is_null", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_is_null",
&[(0, false)],
false,
);
Ok(format!(
"function(i) {{ return {} === null ? 1 : 0; }}",
me.get_object("i")
))
})?;
self.bind("__wbindgen_is_undefined", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_is_undefined",
&[(0, false)],
false,
);
Ok(format!(
"function(i) {{ return {} === undefined ? 1 : 0; }}",
me.get_object("i")
))
})?;
self.bind("__wbindgen_boolean_get", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_boolean_get",
&[(0, false)],
false,
);
Ok(format!(
"
function(i) {{
let v = {};
return typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
}}
",
me.get_object("i"),
))
})?;
self.bind("__wbindgen_symbol_new", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_symbol_new",
&[],
true,
);
me.expose_get_string_from_wasm();
let expr = "ptr === 0 ? Symbol() : Symbol(getStringFromWasm(ptr, len))";
Ok(format!(
"function(ptr, len) {{ return {}; }}",
me.add_heap_object(expr)
))
})?;
self.bind("__wbindgen_is_symbol", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_is_symbol",
&[(0, false)],
false,
);
Ok(format!(
"function(i) {{ return typeof({}) === 'symbol' ? 1 : 0; }}",
me.get_object("i")
))
})?;
self.bind("__wbindgen_is_object", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_is_object",
&[(0, false)],
false,
);
Ok(format!(
"
function(i) {{
const val = {};
return typeof(val) === 'object' && val !== null ? 1 : 0;
}}",
me.get_object("i"),
))
})?;
self.bind("__wbindgen_is_function", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_is_function",
&[(0, false)],
false,
);
Ok(format!(
"function(i) {{ return typeof({}) === 'function' ? 1 : 0; }}",
me.get_object("i")
))
})?;
self.bind("__wbindgen_is_string", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_is_string",
&[(0, false)],
false,
);
Ok(format!(
"function(i) {{ return typeof({}) === 'string' ? 1 : 0; }}",
me.get_object("i")
))
})?;
self.bind("__wbindgen_string_get", &|me| {
me.expose_pass_string_to_wasm()?;
me.expose_uint32_memory();
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_string_get",
&[(0, false)],
false,
);
Ok(format!(
"
function(i, len_ptr) {{
let obj = {};
if (typeof(obj) !== 'string') return 0;
const ptr = passStringToWasm(obj);
getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN;
return ptr;
}}
",
me.get_object("i"),
))
})?;
self.bind("__wbindgen_debug_string", &|me| {
me.expose_pass_string_to_wasm()?;
me.expose_uint32_memory();
let debug_str = "
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 += debug_str(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debug_str(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;
}
";
Ok(format!(
"
function(i, len_ptr) {{
const debug_str = {};
const toString = Object.prototype.toString;
const val = {};
const debug = debug_str(val);
const ptr = passStringToWasm(debug);
getUint32Memory()[len_ptr / 4] = WASM_VECTOR_LEN;
return ptr;
}}
",
debug_str,
me.get_object("i"),
))
})?;
self.bind("__wbindgen_cb_drop", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_cb_drop",
&[(0, true)],
false,
);
Ok(format!(
"
function(i) {{
const obj = {}.original;
if (obj.cnt-- == 1) {{
obj.a = 0;
return 1;
}}
return 0;
}}
",
me.take_object("i"),
))
})?;
self.bind("__wbindgen_cb_forget", &|me| {
Ok(if me.config.anyref {
// TODO: we should rewrite this in the anyref xform to not even
// call into JS
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_cb_drop",
&[(0, true)],
false,
);
String::from("function(obj) {}")
} else {
me.expose_drop_ref();
"dropObject".to_string()
})
})?;
self.bind("__wbindgen_json_parse", &|me| {
me.expose_get_string_from_wasm();
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_json_parse",
&[],
true,
);
let expr = "JSON.parse(getStringFromWasm(ptr, len))";
let expr = me.add_heap_object(expr);
Ok(format!("function(ptr, len) {{ return {}; }}", expr))
})?;
self.bind("__wbindgen_json_serialize", &|me| {
me.anyref.import_xform(
"__wbindgen_placeholder__",
"__wbindgen_json_serialize",
&[(0, false)],
false,
);
me.expose_pass_string_to_wasm()?;
me.expose_uint32_memory();
Ok(format!(
"
function(idx, ptrptr) {{
const ptr = passStringToWasm(JSON.stringify({}));
getUint32Memory()[ptrptr / 4] = ptr;
return WASM_VECTOR_LEN;
}}
",
me.get_object("idx"),
))
})?;
self.bind("__wbindgen_jsval_eq", &|me| {
Ok(format!(
"function(a, b) {{ return {} === {} ? 1 : 0; }}",
me.get_object("a"),
me.get_object("b")
))
})?;
self.bind("__wbindgen_memory", &|me| {
let mem = me.memory();
Ok(format!(
"function() {{ return {}; }}",
me.add_heap_object(mem)
))
})?;
self.bind("__wbindgen_module", &|me| {
if !me.config.mode.no_modules() && !me.config.mode.web() {
bail!(
"`wasm_bindgen::module` is currently only supported with \
`--target no-modules` and `--target web`"
);
}
Ok(format!(
"function() {{ return {}; }}",
me.add_heap_object("init.__wbindgen_wasm_module")
))
})?;
self.bind("__wbindgen_function_table", &|me| {
me.function_table_needed = true;
Ok(format!(
"function() {{ return {}; }}",
me.add_heap_object("wasm.__wbg_function_table")
))
})?;
self.bind("__wbindgen_rethrow", &|me| {
Ok(format!(
"function(idx) {{ throw {}; }}",
me.take_object("idx")
))
})?;
self.bind("__wbindgen_throw", &|me| {
me.expose_get_string_from_wasm();
Ok(String::from(
"
function(ptr, len) {
throw new Error(getStringFromWasm(ptr, len));
}
",
))
})?;
Ok(())
}
/// Provide implementations of remaining intrinsics after initial passes
/// have been run on the wasm module.
///
/// The intrinsics implemented here are added very late in the process or
/// otherwise may be overwritten by passes (such as the anyref pass). As a
/// result they don't go into the initial list of intrinsics but go just at
/// the end.
fn wire_up_late_intrinsics(&mut self) -> Result<(), Error> {
// After the anyref pass has executed, if this intrinsic is needed then
// we expose a function which initializes it
self.bind("__wbindgen_init_anyref_table", &|me| {
me.expose_anyref_table();
Ok(String::from(
"function() {
const table = wasm.__wbg_anyref_table;
const offset = table.grow(4);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
}",
))
})?;
// make sure that the anyref pass runs before binding this as anyref may
// remove calls to this import and then gc would remove it
self.bind("__wbindgen_object_clone_ref", &|me| {
me.expose_get_object();
me.expose_add_heap_object();
Ok(String::from(
"
function(idx) {
return addHeapObject(getObject(idx));
}
",
))
})?;
// like above, make sure anyref runs first and the anyref pass may
// remove usages of this.
self.bind("__wbindgen_object_drop_ref", &|me| {
me.expose_drop_ref();
Ok(String::from("function(i) { dropObject(i); }"))
})?;
Ok(())
}
fn ts_for_init_fn(has_memory: bool) -> String {
let (memory_doc, memory_param) = if has_memory {
(
"* @param {WebAssembly.Memory} maybe_memory\n",
", maybe_memory: WebAssembly.Memory",
)
} 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 function init \
(module_or_path: RequestInfo | BufferSource | WebAssembly.Module{}): Promise<any>;
",
memory_doc, memory_param
)
}
fn gen_init(&mut self, module_name: &str, needs_manual_start: bool) -> (String, String) {
let mem = self.module.memories.get(self.memory);
let (init_memory1, init_memory2) = if mem.import.is_some() {
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");
(
format!("memory = __exports.memory = maybe_memory;"),
format!("memory = __exports.memory = {};", memory),
)
} else {
(String::new(), String::new())
};
let init_memory_arg = if mem.import.is_some() {
", maybe_memory"
} else {
""
};
let ts = Self::ts_for_init_fn(mem.import.is_some());
let js = format!(
"\
function init(module{init_memory_arg}) {{
let result;
const imports = {{ './{module}': __exports }};
if (module instanceof URL || typeof module === 'string' || module instanceof Request) {{
{init_memory2}
const response = fetch(module);
if (typeof WebAssembly.instantiateStreaming === 'function') {{
result = WebAssembly.instantiateStreaming(response, imports)
.catch(e => {{
console.warn(\"`WebAssembly.instantiateStreaming` failed. Assuming this is \
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 response
.then(r => r.arrayBuffer())
.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,
module = module_name,
init_memory1 = init_memory1,
init_memory2 = init_memory2,
start = if needs_manual_start {
"wasm.__wbindgen_start();"
} else {
""
},
);
(js, ts)
}
fn bind(
&mut self,
name: &str,
f: &Fn(&mut Self) -> Result<String, Error>,
) -> Result<(), Error> {
if !self.wasm_import_needed(name) {
return Ok(());
}
let contents = f(self)
.with_context(|_| format!("failed to generate internal JS function `{}`", name))?;
self.export(name, &contents, None);
Ok(())
}
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);
let (mkweakref, freeref) = if self.config.weak_refs {
// When weak refs are enabled we use them to automatically free the
// contents of an exported rust class when it's gc'd. Note that a
// manual `free` function still exists for deterministic
// destruction.
//
// This is implemented by using a `WeakRefGroup` to run finalizers
// for all `WeakRef` objects that it creates. Upon construction of
// a new wasm object we use `makeRef` with "holdings" of a thunk to
// free the wasm instance. Once the `this` (the instance we're
// creating) is gc'd then the finalizer will run with the
// `WeakRef`, and we'll pull out the `holdings`, our pointer.
//
// Note, though, that if manual finalization happens we want to
// cancel the `WeakRef`-generated finalization, so we retain the
// `WeakRef` in a global map. This global map is then used to
// `drop()` the `WeakRef` (cancel finalization) whenever it is
// finalized.
self.expose_cleanup_groups();
let mk = format!("addCleanup(this, this.ptr, free{});", name);
let free = "
CLEANUPS_MAP.get(ptr).drop();
CLEANUPS_MAP.delete(ptr);
";
(mk, free)
} else {
(String::new(), "")
};
if self.config.debug && !class.has_constructor {
dst.push_str(
"
constructor() {
throw new Error('cannot invoke `new` directly');
}
",
);
}
let mut wrap_needed = class.wrap_needed;
let new_name = wasm_bindgen_shared::new_function(&name);
if self.wasm_import_needed(&new_name) {
wrap_needed = true;
self.anyref
.import_xform("__wbindgen_placeholder__", &new_name, &[], true);
let expr = format!("{}.__wrap(ptr)", name);
let expr = self.add_heap_object(&expr);
let body = format!("function(ptr) {{ return {}; }}", expr);
self.export(&new_name, &body, None);
}
if wrap_needed {
dst.push_str(&format!(
"
static __wrap(ptr) {{
const obj = Object.create({}.prototype);
obj.ptr = ptr;
{}
return obj;
}}
",
name,
mkweakref.replace("this", "obj"),
));
}
self.global(&format!(
"
function free{}(ptr) {{
{}
wasm.{}(ptr);
}}
",
name,
freeref,
wasm_bindgen_shared::free_function(&name)
));
dst.push_str(&format!(
"
free() {{
const ptr = this.ptr;
this.ptr = 0;
free{}(ptr);
}}
",
name,
));
ts_dst.push_str(" free(): void;");
dst.push_str(&class.contents);
ts_dst.push_str(&class.typescript);
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 export_table(&mut self) -> Result<(), Error> {
if !self.function_table_needed {
return Ok(());
}
let id = match self.module.tables.main_function_table()? {
Some(id) => id,
None => bail!("no function table found in module"),
};
self.module.exports.add("__wbg_function_table", id);
Ok(())
}
fn rewrite_imports(&mut self, module_name: &str) {
for (name, contents) in self._rewrite_imports(module_name) {
self.export(&name, &contents, None);
}
}
fn _rewrite_imports(&mut self, module_name: &str) -> Vec<(String, String)> {
let mut math_imports = Vec::new();
for import in self.module.imports.iter_mut() {
if import.module == "__wbindgen_placeholder__" {
import.module.truncate(0);
if let Some((module, name)) = self.direct_imports.get(import.name.as_str()) {
import.name.truncate(0);
import.module.push_str(module);
import.name.push_str(name);
} else {
import.module.push_str("./");
import.module.push_str(module_name);
}
continue;
}
if import.module != "env" {
continue;
}
// If memory is imported we'll have exported it from the shim module
// so let's import it from there.
//
// TODO: we should track this is in a more first-class fashion
// rather than just matching on strings.
if import.name == "memory" {
import.module.truncate(0);
import.module.push_str("./");
import.module.push_str(module_name);
continue;
}
let renamed_import = format!("__wbindgen_{}", import.name);
let mut bind_math = |expr: &str| {
math_imports.push((renamed_import.clone(), format!("function{}", expr)));
};
// Note that since Rust 1.32.0 this is no longer necessary. Imports
// of these functions were fixed in rust-lang/rust#54257 and we're
// just waiting until pre-1.32.0 compilers are basically no longer
// in use to remove this.
match import.name.as_str() {
"Math_acos" => bind_math("(x) { return Math.acos(x); }"),
"Math_asin" => bind_math("(x) { return Math.asin(x); }"),
"Math_atan" => bind_math("(x) { return Math.atan(x); }"),
"Math_atan2" => bind_math("(x, y) { return Math.atan2(x, y); }"),
"Math_cbrt" => bind_math("(x) { return Math.cbrt(x); }"),
"Math_cosh" => bind_math("(x) { return Math.cosh(x); }"),
"Math_expm1" => bind_math("(x) { return Math.expm1(x); }"),
"Math_hypot" => bind_math("(x, y) { return Math.hypot(x, y); }"),
"Math_log1p" => bind_math("(x) { return Math.log1p(x); }"),
"Math_sinh" => bind_math("(x) { return Math.sinh(x); }"),
"Math_tan" => bind_math("(x) { return Math.tan(x); }"),
"Math_tanh" => bind_math("(x) { return Math.tanh(x); }"),
_ => continue,
}
import.module.truncate(0);
import.module.push_str("./");
import.module.push_str(module_name);
import.name = renamed_import.clone();
}
math_imports
}
fn unexport_unused_internal_exports(&mut self) {
let mut to_remove = Vec::new();
for export in self.module.exports.iter() {
match export.name.as_str() {
// These are some internal imports set by LLD but currently
// we've got no use case for continuing to export them, so
// blacklist them.
"__heap_base" | "__data_end" | "__indirect_function_table" => {
to_remove.push(export.id());
}
// Otherwise only consider our special exports, which all start
// with the same prefix which hopefully only we're using.
n if n.starts_with("__wbindgen") => {
if !self.required_internal_exports.contains(n) {
to_remove.push(export.id());
}
}
_ => {}
}
}
for id in to_remove {
self.module.exports.delete(id);
}
}
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_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) -> Result<(), Error> {
if !self.should_write_global("pass_string_to_wasm") {
return Ok(());
}
self.require_internal_export("__wbindgen_malloc")?;
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() {
self.expose_node_buffer_memory();
self.global(&format!(
"
function passStringToWasm(arg) {{
{}
const size = Buffer.byteLength(arg);
const ptr = wasm.__wbindgen_malloc(size);
getNodeBufferMemory().write(arg, ptr, size);
WASM_VECTOR_LEN = size;
return ptr;
}}
",
debug,
));
return Ok(());
}
self.expose_text_encoder();
self.expose_uint8_memory();
// The first implementation we have for this is to use
// `TextEncoder#encode` which has been around for quite some time.
let use_encode = format!(
"
{}
const buf = cachedTextEncoder.encode(arg);
const ptr = wasm.__wbindgen_malloc(buf.length);
getUint8Memory().set(buf, ptr);
WASM_VECTOR_LEN = buf.length;
return ptr;
",
debug
);
// 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 use_encode_into = format!(
"
{}
let size = arg.length;
let ptr = wasm.__wbindgen_malloc(size);
let writeOffset = 0;
while (true) {{
const view = getUint8Memory().subarray(ptr + writeOffset, ptr + size);
const {{ read, written }} = cachedTextEncoder.encodeInto(arg, view);
if (read === arg.length) {{
break;
}}
arg = arg.substring(read);
writeOffset += written;
ptr = wasm.__wbindgen_realloc(ptr, size, size += arg.length * 3);
}}
WASM_VECTOR_LEN = writeOffset;
return ptr;
",
debug
);
// 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(self.memory).shared;
match self.config.encode_into {
EncodeInto::Always if !shared => {
self.require_internal_export("__wbindgen_realloc")?;
self.global(&format!(
"function passStringToWasm(arg) {{ {} }}",
use_encode_into,
));
}
EncodeInto::Test if !shared => {
self.require_internal_export("__wbindgen_realloc")?;
self.global(&format!(
"
let passStringToWasm;
if (typeof cachedTextEncoder.encodeInto === 'function') {{
passStringToWasm = function(arg) {{ {} }};
}} else {{
passStringToWasm = function(arg) {{ {} }};
}}
",
use_encode_into, use_encode,
));
}
_ => {
self.global(&format!(
"function passStringToWasm(arg) {{ {} }}",
use_encode,
));
}
}
Ok(())
}
fn expose_pass_array8_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint8_memory();
self.pass_array_to_wasm("passArray8ToWasm", "getUint8Memory", 1)
}
fn expose_pass_array16_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint16_memory();
self.pass_array_to_wasm("passArray16ToWasm", "getUint16Memory", 2)
}
fn expose_pass_array32_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint32_memory();
self.pass_array_to_wasm("passArray32ToWasm", "getUint32Memory", 4)
}
fn expose_pass_array64_to_wasm(&mut self) -> Result<(), Error> {
self.expose_uint64_memory();
self.pass_array_to_wasm("passArray64ToWasm", "getUint64Memory", 8)
}
fn expose_pass_array_f32_to_wasm(&mut self) -> Result<(), Error> {
self.expose_f32_memory();
self.pass_array_to_wasm("passArrayF32ToWasm", "getFloat32Memory", 4)
}
fn expose_pass_array_f64_to_wasm(&mut self) -> Result<(), Error> {
self.expose_f64_memory();
self.pass_array_to_wasm("passArrayF64ToWasm", "getFloat64Memory", 8)
}
fn expose_pass_array_jsvalue_to_wasm(&mut self) -> Result<(), Error> {
if !self.should_write_global("pass_array_jsvalue") {
return Ok(());
}
self.require_internal_export("__wbindgen_malloc")?;
self.expose_uint32_memory();
if self.config.anyref {
// TODO: using `addToAnyrefTable` goes back and forth between wasm
// and JS a lot, we should have a bulk operation for this.
self.expose_add_to_anyref_table()?;
self.global(
"
function passArrayJsValueToWasm(array) {
const ptr = wasm.__wbindgen_malloc(array.length * 4);
const mem = getUint32Memory();
for (let i = 0; i < array.length; i++) {
mem[ptr / 4 + i] = addToAnyrefTable(array[i]);
}
WASM_VECTOR_LEN = array.length;
return ptr;
}
",
);
} else {
self.expose_add_heap_object();
self.global(
"
function passArrayJsValueToWasm(array) {
const ptr = wasm.__wbindgen_malloc(array.length * 4);
const mem = getUint32Memory();
for (let i = 0; i < array.length; i++) {
mem[ptr / 4 + i] = addHeapObject(array[i]);
}
WASM_VECTOR_LEN = array.length;
return ptr;
}
",
);
}
Ok(())
}
fn pass_array_to_wasm(
&mut self,
name: &'static str,
delegate: &str,
size: usize,
) -> Result<(), Error> {
if !self.should_write_global(name) {
return Ok(());
}
self.require_internal_export("__wbindgen_malloc")?;
self.expose_wasm_vector_len();
self.global(&format!(
"
function {}(arg) {{
const ptr = wasm.__wbindgen_malloc(arg.length * {size});
{}().set(arg, ptr / {size});
WASM_VECTOR_LEN = arg.length;
return ptr;
}}
",
name,
delegate,
size = size
));
Ok(())
}
fn expose_text_encoder(&mut self) {
if !self.should_write_global("text_encoder") {
return;
}
self.expose_text_processor("TextEncoder");
}
fn expose_text_decoder(&mut self) {
if !self.should_write_global("text_decoder") {
return;
}
self.expose_text_processor("TextDecoder");
}
fn expose_text_processor(&mut self, s: &str) {
if self.config.mode.nodejs_experimental_modules() {
self.imports
.push_str(&format!("import {{ {} }} from 'util';\n", s));
self.global(&format!("let cached{0} = new {0}('utf-8');", s));
} else if self.config.mode.nodejs() {
self.global(&format!("const {0} = require('util').{0};", s));
self.global(&format!("let cached{0} = new {0}('utf-8');", s));
} 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}('utf-8');", s));
} else {
self.global(&format!("let cached{0} = new {0}('utf-8');", s));
}
}
fn expose_get_string_from_wasm(&mut self) {
if !self.should_write_global("get_string_from_wasm") {
return;
}
self.expose_text_decoder();
self.expose_uint8_memory();
// 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(self.memory).shared;
let method = if is_shared { "slice" } else { "subarray" };
self.global(&format!(
"
function getStringFromWasm(ptr, len) {{
return cachedTextDecoder.decode(getUint8Memory().{}(ptr, ptr + len));
}}
",
method
));
}
fn expose_get_array_js_value_from_wasm(&mut self) -> Result<(), Error> {
if !self.should_write_global("get_array_js_value_from_wasm") {
return Ok(());
}
self.expose_uint32_memory();
if self.config.anyref {
self.expose_anyref_table();
self.global(
"
function getArrayJsValueFromWasm(ptr, len) {
const mem = getUint32Memory();
const slice = mem.subarray(ptr / 4, ptr / 4 + len);
const result = [];
for (let i = 0; i < slice.length; i++) {
result.push(wasm.__wbg_anyref_table.get(slice[i]));
}
wasm.__wbindgen_drop_anyref_slice(ptr, len);
return result;
}
",
);
self.require_internal_export("__wbindgen_drop_anyref_slice")?;
} else {
self.expose_take_object();
self.global(
"
function getArrayJsValueFromWasm(ptr, len) {
const mem = getUint32Memory();
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;
}
",
);
}
Ok(())
}
fn expose_get_array_i8_from_wasm(&mut self) {
self.expose_int8_memory();
self.arrayget("getArrayI8FromWasm", "getInt8Memory", 1);
}
fn expose_get_array_u8_from_wasm(&mut self) {
self.expose_uint8_memory();
self.arrayget("getArrayU8FromWasm", "getUint8Memory", 1);
}
fn expose_get_clamped_array_u8_from_wasm(&mut self) {
self.expose_clamped_uint8_memory();
self.arrayget("getClampedArrayU8FromWasm", "getUint8ClampedMemory", 1);
}
fn expose_get_array_i16_from_wasm(&mut self) {
self.expose_int16_memory();
self.arrayget("getArrayI16FromWasm", "getInt16Memory", 2);
}
fn expose_get_array_u16_from_wasm(&mut self) {
self.expose_uint16_memory();
self.arrayget("getArrayU16FromWasm", "getUint16Memory", 2);
}
fn expose_get_array_i32_from_wasm(&mut self) {
self.expose_int32_memory();
self.arrayget("getArrayI32FromWasm", "getInt32Memory", 4);
}
fn expose_get_array_u32_from_wasm(&mut self) {
self.expose_uint32_memory();
self.arrayget("getArrayU32FromWasm", "getUint32Memory", 4);
}
fn expose_get_array_i64_from_wasm(&mut self) {
self.expose_int64_memory();
self.arrayget("getArrayI64FromWasm", "getInt64Memory", 8);
}
fn expose_get_array_u64_from_wasm(&mut self) {
self.expose_uint64_memory();
self.arrayget("getArrayU64FromWasm", "getUint64Memory", 8);
}
fn expose_get_array_f32_from_wasm(&mut self) {
self.expose_f32_memory();
self.arrayget("getArrayF32FromWasm", "getFloat32Memory", 4);
}
fn expose_get_array_f64_from_wasm(&mut self) {
self.expose_f64_memory();
self.arrayget("getArrayF64FromWasm", "getFloat64Memory", 8);
}
fn arrayget(&mut self, name: &'static str, mem: &'static str, size: usize) {
if !self.should_write_global(name) {
return;
}
self.global(&format!(
"
function {name}(ptr, len) {{
return {mem}().subarray(ptr / {size}, ptr / {size} + len);
}}
",
name = name,
mem = mem,
size = size,
));
}
fn expose_node_buffer_memory(&mut self) {
self.memview("getNodeBufferMemory", "Buffer.from");
}
fn expose_int8_memory(&mut self) {
self.memview("getInt8Memory", "new Int8Array");
}
fn expose_uint8_memory(&mut self) {
self.memview("getUint8Memory", "new Uint8Array");
}
fn expose_clamped_uint8_memory(&mut self) {
self.memview("getUint8ClampedMemory", "new Uint8ClampedArray");
}
fn expose_int16_memory(&mut self) {
self.memview("getInt16Memory", "new Int16Array");
}
fn expose_uint16_memory(&mut self) {
self.memview("getUint16Memory", "new Uint16Array");
}
fn expose_int32_memory(&mut self) {
self.memview("getInt32Memory", "new Int32Array");
}
fn expose_uint32_memory(&mut self) {
self.memview("getUint32Memory", "new Uint32Array");
}
fn expose_int64_memory(&mut self) {
self.memview("getInt64Memory", "new BigInt64Array");
}
fn expose_uint64_memory(&mut self) {
self.memview("getUint64Memory", "new BigUint64Array");
}
fn expose_f32_memory(&mut self) {
self.memview("getFloat32Memory", "new Float32Array");
}
fn expose_f64_memory(&mut self) {
self.memview("getFloat64Memory", "new Float64Array");
}
fn memview_function(&mut self, t: VectorKind) -> &'static str {
match t {
VectorKind::String => {
self.expose_uint8_memory();
"getUint8Memory"
}
VectorKind::I8 => {
self.expose_int8_memory();
"getInt8Memory"
}
VectorKind::U8 => {
self.expose_uint8_memory();
"getUint8Memory"
}
VectorKind::ClampedU8 => {
self.expose_clamped_uint8_memory();
"getUint8ClampedMemory"
}
VectorKind::I16 => {
self.expose_int16_memory();
"getInt16Memory"
}
VectorKind::U16 => {
self.expose_uint16_memory();
"getUint16Memory"
}
VectorKind::I32 => {
self.expose_int32_memory();
"getInt32Memory"
}
VectorKind::U32 => {
self.expose_uint32_memory();
"getUint32Memory"
}
VectorKind::I64 => {
self.expose_int64_memory();
"getInt64Memory"
}
VectorKind::U64 => {
self.expose_uint64_memory();
"getUint64Memory"
}
VectorKind::F32 => {
self.expose_f32_memory();
"getFloat32Memory"
}
VectorKind::F64 => {
self.expose_f64_memory();
"getFloat64Memory"
}
VectorKind::Anyref => {
self.expose_uint32_memory();
"getUint32Memory"
}
}
}
fn memview(&mut self, name: &'static str, js: &str) {
if !self.should_write_global(name) {
return;
}
let mem = self.memory();
self.global(&format!(
"
let cache{name} = null;
function {name}() {{
if (cache{name} === null || cache{name}.buffer !== {mem}.buffer) {{
cache{name} = {js}({mem}.buffer);
}}
return cache{name};
}}
",
name = name,
js = js,
mem = mem,
));
}
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(());
}
self.expose_uint32_memory();
if self.config.anyref {
self.expose_add_to_anyref_table()?;
self.global(
"
function handleError(exnptr, e) {
const idx = addToAnyrefTable(e);
const view = getUint32Memory();
view[exnptr / 4] = 1;
view[exnptr / 4 + 1] = idx;
}
",
);
} else {
self.expose_add_heap_object();
self.global(
"
function handleError(exnptr, e) {
const view = getUint32Memory();
view[exnptr / 4] = 1;
view[exnptr / 4 + 1] = addHeapObject(e);
}
",
);
}
Ok(())
}
fn wasm_import_needed(&self, name: &str) -> bool {
self.module
.imports
.iter()
.any(|i| i.module == "__wbindgen_placeholder__" && i.name == name)
}
fn pass_to_wasm_function(&mut self, t: VectorKind) -> Result<&'static str, Error> {
let s = match t {
VectorKind::String => {
self.expose_pass_string_to_wasm()?;
"passStringToWasm"
}
VectorKind::I8 | VectorKind::U8 | VectorKind::ClampedU8 => {
self.expose_pass_array8_to_wasm()?;
"passArray8ToWasm"
}
VectorKind::U16 | VectorKind::I16 => {
self.expose_pass_array16_to_wasm()?;
"passArray16ToWasm"
}
VectorKind::I32 | VectorKind::U32 => {
self.expose_pass_array32_to_wasm()?;
"passArray32ToWasm"
}
VectorKind::I64 | VectorKind::U64 => {
self.expose_pass_array64_to_wasm()?;
"passArray64ToWasm"
}
VectorKind::F32 => {
self.expose_pass_array_f32_to_wasm()?;
"passArrayF32ToWasm"
}
VectorKind::F64 => {
self.expose_pass_array_f64_to_wasm()?;
"passArrayF64ToWasm"
}
VectorKind::Anyref => {
self.expose_pass_array_jsvalue_to_wasm()?;
"passArrayJsValueToWasm"
}
};
Ok(s)
}
fn expose_get_vector_from_wasm(&mut self, ty: VectorKind) -> Result<&'static str, Error> {
Ok(match ty {
VectorKind::String => {
self.expose_get_string_from_wasm();
"getStringFromWasm"
}
VectorKind::I8 => {
self.expose_get_array_i8_from_wasm();
"getArrayI8FromWasm"
}
VectorKind::U8 => {
self.expose_get_array_u8_from_wasm();
"getArrayU8FromWasm"
}
VectorKind::ClampedU8 => {
self.expose_get_clamped_array_u8_from_wasm();
"getClampedArrayU8FromWasm"
}
VectorKind::I16 => {
self.expose_get_array_i16_from_wasm();
"getArrayI16FromWasm"
}
VectorKind::U16 => {
self.expose_get_array_u16_from_wasm();
"getArrayU16FromWasm"
}
VectorKind::I32 => {
self.expose_get_array_i32_from_wasm();
"getArrayI32FromWasm"
}
VectorKind::U32 => {
self.expose_get_array_u32_from_wasm();
"getArrayU32FromWasm"
}
VectorKind::I64 => {
self.expose_get_array_i64_from_wasm();
"getArrayI64FromWasm"
}
VectorKind::U64 => {
self.expose_get_array_u64_from_wasm();
"getArrayU64FromWasm"
}
VectorKind::F32 => {
self.expose_get_array_f32_from_wasm();
"getArrayF32FromWasm"
}
VectorKind::F64 => {
self.expose_get_array_f64_from_wasm();
"getArrayF64FromWasm"
}
VectorKind::Anyref => {
self.expose_get_array_js_value_from_wasm()?;
"getArrayJsValueFromWasm"
}
})
}
fn expose_global_argument_ptr(&mut self) -> Result<(), Error> {
if !self.should_write_global("global_argument_ptr") {
return Ok(());
}
self.require_internal_export("__wbindgen_global_argument_ptr")?;
self.global(
"
let cachedGlobalArgumentPtr = null;
function globalArgumentPtr() {
if (cachedGlobalArgumentPtr === null) {
cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
}
return cachedGlobalArgumentPtr;
}
",
);
Ok(())
}
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 expose_cleanup_groups(&mut self) {
if !self.should_write_global("cleanup_groups") {
return;
}
self.global(
"
const CLEANUPS = new WeakRefGroup(x => x.holdings());
const CLEANUPS_MAP = new Map();
function addCleanup(obj, ptr, free) {
const ref = CLEANUPS.makeRef(obj, () => free(ptr));
CLEANUPS_MAP.set(ptr, ref);
}
",
);
}
fn describe(&mut self, name: &str) -> Option<Descriptor> {
let name = format!("__wbindgen_describe_{}", name);
let descriptor = self.interpreter.interpret_descriptor(&name, self.module)?;
Some(Descriptor::decode(descriptor))
}
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 use_node_require(&self) -> bool {
self.config.mode.nodejs() && !self.config.mode.nodejs_experimental_modules()
}
fn memory(&mut self) -> &'static str {
if self.module.memories.get(self.memory).import.is_some() {
"memory"
} else {
"wasm.memory"
}
}
fn require_class_wrap(&mut self, class: &str) {
self.exported_classes
.as_mut()
.expect("classes already written")
.entry(class.to_string())
.or_insert_with(ExportedClass::default)
.wrap_needed = true;
}
fn import_identifier(&mut self, import: Import<'a>) -> String {
// Here's where it's a bit tricky. We need to make sure that importing
// the same identifier from two different modules works, and they're
// named uniquely below. Additionally if we've already imported the same
// identifier from the module in question then we'd like to reuse the
// one that was previously imported.
//
// Our `imported_names` map keeps track of all imported identifiers from
// modules, mapping the imported names onto names actually available for
// use in our own module. If our identifier isn't present then we
// generate a new identifier and are sure to generate the appropriate JS
// import for our new identifier.
let use_node_require = self.use_node_require();
let imported_identifiers = &mut self.imported_identifiers;
let imports = &mut self.imports;
let imports_post = &mut self.imports_post;
let identifier = self
.imported_names
.entry(import.module())
.or_insert_with(Default::default)
.entry(import.name())
.or_insert_with(|| {
let name = generate_identifier(import.name(), imported_identifiers);
match &import {
Import::Module { .. }
| Import::LocalModule { .. }
| Import::InlineJs { .. } => {
// When doing a modular import local snippets (either
// inline or not) are routed to a local `./snippets`
// directory which the rest of `wasm-bindgen` will fill
// in.
let path = match import {
Import::Module { module, .. } => module.to_string(),
Import::LocalModule { module, .. } => format!("./snippets/{}", module),
Import::InlineJs {
unique_crate_identifier,
snippet_idx_in_crate,
..
} => format!(
"./snippets/{}/inline{}.js",
unique_crate_identifier, snippet_idx_in_crate
),
_ => unreachable!(),
};
if use_node_require {
imports.push_str(&format!(
"const {} = require(String.raw`{}`).{};\n",
name,
path,
import.name()
));
} else if import.name() == name {
imports.push_str(&format!("import {{ {} }} from '{}';\n", name, path));
} else {
imports.push_str(&format!(
"import {{ {} as {} }} from '{}';\n",
import.name(),
name,
path
));
}
name
}
Import::VendorPrefixed { prefixes, .. } => {
imports_post.push_str("const l");
imports_post.push_str(&name);
imports_post.push_str(" = ");
switch(imports_post, &name, "", prefixes);
imports_post.push_str(";\n");
fn switch(dst: &mut String, name: &str, prefix: &str, left: &[&str]) {
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' ? ");
match left.len() {
1 => {
dst.push_str(&left[0]);
dst.push_str(name);
}
_ => switch(dst, name, &left[0], &left[1..]),
}
dst.push_str(" : ");
dst.push_str(prefix);
dst.push_str(name);
dst.push_str(")");
}
format!("l{}", name)
}
Import::Global { .. } => name,
}
});
// If there's a namespace we didn't actually import `item` but rather
// the namespace, so access through that.
match import.field() {
Some(field) => format!("{}.{}", identifier, field),
None => identifier.clone(),
}
}
fn generated_import_target(
&mut self,
name: Import<'a>,
import: &decode::ImportFunction<'a>,
) -> Result<ImportTarget, Error> {
let method_data = match &import.method {
Some(data) => data,
None => {
let name = self.import_identifier(name);
if import.structural || !name.contains(".") {
return Ok(ImportTarget::Function(name));
}
self.global(&format!("const {}_target = {};", import.shim, name));
let target = format!("{}_target", import.shim);
return Ok(ImportTarget::Function(target));
}
};
let class = self.import_identifier(name);
let op = match &method_data.kind {
decode::MethodKind::Constructor => {
return Ok(ImportTarget::Constructor(class.to_string()));
}
decode::MethodKind::Operation(op) => op,
};
if import.structural {
let class = if op.is_static {
Some(class.clone())
} else {
None
};
return Ok(match &op.kind {
decode::OperationKind::Regular => {
let name = import.function.name.to_string();
match class {
Some(c) => ImportTarget::Function(format!("{}.{}", c, name)),
None => ImportTarget::StructuralMethod(name),
}
}
decode::OperationKind::Getter(g) => {
ImportTarget::StructuralGetter(class, g.to_string())
}
decode::OperationKind::Setter(s) => {
ImportTarget::StructuralSetter(class, s.to_string())
}
decode::OperationKind::IndexingGetter => {
ImportTarget::StructuralIndexingGetter(class)
}
decode::OperationKind::IndexingSetter => {
ImportTarget::StructuralIndexingSetter(class)
}
decode::OperationKind::IndexingDeleter => {
ImportTarget::StructuralIndexingDeleter(class)
}
});
}
let target = format!(
"typeof {0} === 'undefined' ? null : {}{}",
class,
if op.is_static { "" } else { ".prototype" }
);
let (mut target, name) = match &op.kind {
decode::OperationKind::Regular => (
format!("{}.{}", target, import.function.name),
&import.function.name,
),
decode::OperationKind::Getter(g) => {
self.expose_get_inherited_descriptor();
(
format!(
"GetOwnOrInheritedPropertyDescriptor({}, '{}').get",
target, g,
),
g,
)
}
decode::OperationKind::Setter(s) => {
self.expose_get_inherited_descriptor();
(
format!(
"GetOwnOrInheritedPropertyDescriptor({}, '{}').set",
target, s,
),
s,
)
}
decode::OperationKind::IndexingGetter => panic!("indexing getter should be structural"),
decode::OperationKind::IndexingSetter => panic!("indexing setter should be structural"),
decode::OperationKind::IndexingDeleter => {
panic!("indexing deleter should be structural")
}
};
target.push_str(&format!(
" || function() {{
throw new Error(`wasm-bindgen: {}.{} does not exist`);
}}",
class, name
));
if op.is_static {
target.insert(0, '(');
target.push_str(").bind(");
target.push_str(&class);
target.push_str(")");
}
self.global(&format!("const {}_target = {};", import.shim, target));
Ok(if op.is_static {
ImportTarget::Function(format!("{}_target", import.shim))
} else {
ImportTarget::Method(format!("{}_target", import.shim))
})
}
/// Update the wasm file's `producers` section to include information about
/// the wasm-bindgen tool.
///
/// Specified at:
/// https://github.com/WebAssembly/tool-conventions/blob/master/ProducersSection.md
fn update_producers_section(&mut self) {
self.module
.producers
.add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
}
fn add_start_function(&mut self) -> Result<(), Error> {
let start = match &self.start {
Some(name) => name.clone(),
None => return Ok(()),
};
let export = match self.module.exports.iter().find(|e| e.name == start) {
Some(export) => export,
None => bail!("export `{}` not found", start),
};
let id = match export.item {
walrus::ExportItem::Function(i) => i,
_ => bail!("export `{}` wasn't a function", start),
};
let prev_start = match self.module.start {
Some(f) => f,
None => {
self.module.start = Some(id);
return Ok(());
}
};
// Note that we call the previous start function, if any, first. This is
// because the start function currently only shows up when it's injected
// through thread/anyref transforms. These injected start functions need
// to happen before user code, so we always schedule them first.
let mut builder = walrus::FunctionBuilder::new();
let call1 = builder.call(prev_start, Box::new([]));
let call2 = builder.call(id, Box::new([]));
let ty = self.module.funcs.get(id).ty();
let new_start = builder.finish(ty, Vec::new(), vec![call1, call2], self.module);
self.module.start = Some(new_start);
Ok(())
}
/// 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_anyref_table(&mut self) {
assert!(self.config.anyref);
if !self.should_write_global("anyref_table") {
return;
}
self.module
.exports
.add("__wbg_anyref_table", self.anyref.anyref_table_id());
}
fn expose_add_to_anyref_table(&mut self) -> Result<(), Error> {
assert!(self.config.anyref);
if !self.should_write_global("add_to_anyref_table") {
return Ok(());
}
self.expose_anyref_table();
self.require_internal_export("__wbindgen_anyref_table_alloc")?;
self.global(
"
function addToAnyrefTable(obj) {
const idx = wasm.__wbindgen_anyref_table_alloc();
wasm.__wbg_anyref_table.set(idx, obj);
return idx;
}
",
);
Ok(())
}
fn add_heap_object(&mut self, expr: &str) -> String {
if self.config.anyref {
expr.to_string()
} else {
self.expose_add_heap_object();
format!("addHeapObject({})", expr)
}
}
fn take_object(&mut self, expr: &str) -> String {
if self.config.anyref {
expr.to_string()
} else {
self.expose_take_object();
format!("takeObject({})", expr)
}
}
fn get_object(&mut self, expr: &str) -> String {
if self.config.anyref {
expr.to_string()
} else {
self.expose_get_object();
format!("getObject({})", expr)
}
}
}
impl<'a, 'b> SubContext<'a, 'b> {
pub fn generate(&mut self) -> Result<(), Error> {
for m in self.program.local_modules.iter() {
// All local modules we find should be unique, but the same module
// may have showed up in a few different blocks. If that's the case
// all the same identifiers should have the same contents.
if let Some(prev) = self.cx.local_modules.insert(m.identifier, m.contents) {
assert_eq!(prev, m.contents);
}
}
for f in self.program.exports.iter() {
self.generate_export(f).with_context(|_| {
format!(
"failed to generate bindings for Rust export `{}`",
f.function.name
)
})?;
}
for f in self.program.imports.iter() {
if let decode::ImportKind::Type(ty) = &f.kind {
self.register_vendor_prefix(ty);
}
}
for f in self.program.imports.iter() {
self.generate_import(f)?;
}
for e in self.program.enums.iter() {
self.generate_enum(e);
}
for s in self.program.structs.iter() {
self.generate_struct(s).with_context(|_| {
format!("failed to generate bindings for Rust struct `{}`", s.name,)
})?;
}
for s in self.program.typescript_custom_sections.iter() {
self.cx.typescript.push_str(s);
self.cx.typescript.push_str("\n\n");
}
if let Some(path) = self.program.package_json {
self.add_package_json(path)?;
}
Ok(())
}
fn generate_export(&mut self, export: &decode::Export<'b>) -> Result<(), Error> {
if let Some(ref class) = export.class {
assert!(!export.start);
return self.generate_export_for_class(class, export);
}
let descriptor = match self.cx.describe(&export.function.name) {
None => return Ok(()),
Some(d) => d,
};
if export.start {
self.set_start_function(export.function.name)?;
}
let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx)
.process(descriptor.unwrap_function(), &export.function.arg_names)?
.finish(
"function",
&format!("wasm.{}", export.function.name),
ExportedShim::Named(&export.function.name),
);
self.cx.export(
&export.function.name,
&js,
Some(format_doc_comments(&export.comments, Some(js_doc))),
);
self.cx.globals.push_str("\n");
self.cx.typescript.push_str("export ");
self.cx.typescript.push_str(&ts);
self.cx.typescript.push_str("\n");
Ok(())
}
fn set_start_function(&mut self, start: &str) -> Result<(), Error> {
if let Some(prev) = &self.cx.start {
bail!(
"cannot flag `{}` as start function as `{}` is \
already the start function",
start,
prev
);
}
self.cx.start = Some(start.to_string());
Ok(())
}
fn generate_export_for_class(
&mut self,
class_name: &'b str,
export: &decode::Export,
) -> Result<(), Error> {
let wasm_name =
wasm_bindgen_shared::struct_function_export_name(class_name, &export.function.name);
let descriptor = match self.cx.describe(&wasm_name) {
None => return Ok(()),
Some(d) => d,
};
let function_name = if export.is_constructor {
"constructor"
} else {
&export.function.name
};
let (js, ts, js_doc) = Js2Rust::new(function_name, self.cx)
.method(export.method, export.consumed)
.constructor(if export.is_constructor {
Some(class_name)
} else {
None
})
.process(descriptor.unwrap_function(), &export.function.arg_names)?
.finish(
"",
&format!("wasm.{}", wasm_name),
ExportedShim::Named(&wasm_name),
);
let class = self
.cx
.exported_classes
.as_mut()
.expect("classes already written")
.entry(class_name.to_string())
.or_insert(ExportedClass::default());
class
.contents
.push_str(&format_doc_comments(&export.comments, Some(js_doc)));
class.typescript.push_str(" "); // Indentation
if export.is_constructor {
if class.has_constructor {
bail!("found duplicate constructor `{}`", export.function.name);
}
class.has_constructor = true;
} else if !export.method {
class.contents.push_str("static ");
class.typescript.push_str("static ");
}
class.contents.push_str(function_name);
class.contents.push_str(&js);
class.contents.push_str("\n");
class.typescript.push_str(&ts);
class.typescript.push_str("\n");
Ok(())
}
fn generate_import(&mut self, import: &decode::Import<'b>) -> Result<(), Error> {
match import.kind {
decode::ImportKind::Function(ref f) => {
self.generate_import_function(import, f).with_context(|_| {
format!(
"failed to generate bindings for JS import `{}`",
f.function.name
)
})?;
}
decode::ImportKind::Static(ref s) => {
self.generate_import_static(import, s).with_context(|_| {
format!("failed to generate bindings for JS import `{}`", s.name)
})?;
}
decode::ImportKind::Type(ref ty) => {
self.generate_import_type(import, ty).with_context(|_| {
format!("failed to generate bindings for JS import `{}`", ty.name,)
})?;
}
decode::ImportKind::Enum(_) => {}
}
Ok(())
}
fn generate_import_static(
&mut self,
info: &decode::Import<'b>,
import: &decode::ImportStatic<'b>,
) -> Result<(), Error> {
// The same static can be imported in multiple locations, so only
// generate bindings once for it.
if !self.cx.imported_statics.insert(import.shim) {
return Ok(());
}
// TODO: should support more types to import here
let obj = self.import_name(info, &import.name)?;
self.cx
.anyref
.import_xform("__wbindgen_placeholder__", &import.shim, &[], true);
let body = format!("function() {{ return {}; }}", self.cx.add_heap_object(&obj));
self.cx.export(&import.shim, &body, None);
Ok(())
}
fn generate_import_function(
&mut self,
info: &decode::Import<'b>,
import: &decode::ImportFunction<'b>,
) -> Result<(), Error> {
if !self.cx.wasm_import_needed(&import.shim) {
return Ok(());
}
// It's possible for the same function to be imported in two locations,
// but we only want to generate one.
if !self.cx.imported_functions.insert(import.shim) {
return Ok(());
}
let descriptor = match self.cx.describe(&import.shim) {
None => return Ok(()),
Some(d) => d,
};
// Figure out the name that we're importing to dangle further references
// off of. This is the function name if there's no method all here, or
// the class if there's a method call.
let name = match &import.method {
Some(data) => self.determine_import(info, &data.class)?,
None => self.determine_import(info, &import.function.name)?,
};
// Build up our shim's state, and we'll use that to guide whether we
// actually emit an import here or not.
let mut shim = Rust2Js::new(self.cx);
if shim.cx.config.debug {
shim.catch_and_rethrow(true);
}
shim.catch(import.catch)
.variadic(import.variadic)
.process(descriptor.unwrap_function())?;
// If this is a bare function import and the shim doesn't actually do
// anything (all argument/return conversions are noops) then we can wire
// up the wasm import directly to the destination. We don't actually
// wire up anything here, but we record it to get wired up later.
if import.method.is_none() && shim.is_noop() {
if let Import::Module {
module,
name,
field: None,
} = name
{
shim.cx.direct_imports.insert(import.shim, (module, name));
if shim.ret_anyref || shim.anyref_args.len() > 0 {
shim.cx.anyref.import_xform(
"__wbindgen_placeholder__",
&import.shim,
&shim.anyref_args,
shim.ret_anyref,
);
}
return Ok(());
}
}
// If the above optimization fails then we actually generate the import
// here (possibly emitting some glue in our JS module) and then emit the
// shim as the wasm will be importing the shim.
let target = shim.cx.generated_import_target(name, import)?;
let js = shim.finish(&target, &import.shim)?;
shim.cx.export(&import.shim, &js, None);
Ok(())
}
fn generate_import_type(
&mut self,
info: &decode::Import<'b>,
import: &decode::ImportType<'b>,
) -> Result<(), Error> {
if !self.cx.wasm_import_needed(&import.instanceof_shim) {
return Ok(());
}
let name = self.import_name(info, &import.name)?;
self.cx.anyref.import_xform(
"__wbindgen_placeholder__",
&import.instanceof_shim,
&[(0, false)],
false,
);
let body = format!(
"function(idx) {{ return {} instanceof {} ? 1 : 0; }}",
self.cx.get_object("idx"),
name
);
self.cx.export(&import.instanceof_shim, &body, None);
Ok(())
}
fn generate_enum(&mut self, enum_: &decode::Enum) {
let mut variants = String::new();
for variant in enum_.variants.iter() {
variants.push_str(&format!("{}:{},", variant.name, variant.value));
}
self.cx.export(
&enum_.name,
&format!("Object.freeze({{ {} }})", variants),
Some(format_doc_comments(&enum_.comments, None)),
);
self.cx
.typescript
.push_str(&format!("export enum {} {{", enum_.name));
for variant in enum_.variants.iter() {
self.cx
.typescript
.push_str(&format!("\n {},", variant.name));
}
self.cx.typescript.push_str("\n}\n");
}
fn generate_struct(&mut self, struct_: &decode::Struct) -> Result<(), Error> {
let mut dst = String::new();
let mut ts_dst = String::new();
for field in struct_.fields.iter() {
let wasm_getter = wasm_bindgen_shared::struct_field_get(&struct_.name, &field.name);
let wasm_setter = wasm_bindgen_shared::struct_field_set(&struct_.name, &field.name);
let descriptor = match self.cx.describe(&wasm_getter) {
None => continue,
Some(d) => d,
};
let set = {
let setter = ExportedShim::Named(&wasm_setter);
let mut cx = Js2Rust::new(&field.name, self.cx);
cx.method(true, false)
.argument(&descriptor, None)?
.ret(&Descriptor::Unit)?;
ts_dst.push_str(&format!(
"\n {}{}: {};",
if field.readonly { "readonly " } else { "" },
field.name,
&cx.js_arguments[0].1
));
cx.finish("", &format!("wasm.{}", wasm_setter), setter).0
};
let getter = ExportedShim::Named(&wasm_getter);
let (get, _ts, js_doc) = Js2Rust::new(&field.name, self.cx)
.method(true, false)
.ret(&descriptor)?
.finish("", &format!("wasm.{}", wasm_getter), getter);
if !dst.ends_with("\n") {
dst.push_str("\n");
}
dst.push_str(&format_doc_comments(&field.comments, Some(js_doc)));
dst.push_str("get ");
dst.push_str(&field.name);
dst.push_str(&get);
dst.push_str("\n");
if !field.readonly {
dst.push_str("set ");
dst.push_str(&field.name);
dst.push_str(&set);
}
}
let class = self
.cx
.exported_classes
.as_mut()
.expect("classes already written")
.entry(struct_.name.to_string())
.or_insert_with(Default::default);
class.comments = format_doc_comments(&struct_.comments, None);
class.contents.push_str(&dst);
class.contents.push_str("\n");
class.typescript.push_str(&ts_dst);
class.typescript.push_str("\n");
Ok(())
}
fn register_vendor_prefix(&mut self, info: &decode::ImportType<'b>) {
if info.vendor_prefixes.len() == 0 {
return;
}
self.vendor_prefixes
.entry(info.name)
.or_insert(Vec::new())
.extend(info.vendor_prefixes.iter().cloned());
}
fn determine_import(
&self,
import: &decode::Import<'b>,
item: &'b str,
) -> Result<Import<'b>, Error> {
// First up, imports don't work at all in `--target no-modules` mode as
// we're not sure how to import them.
let is_local_snippet = match import.module {
decode::ImportModule::Named(s) => self.cx.local_modules.contains_key(s),
decode::ImportModule::RawNamed(_) => false,
decode::ImportModule::Inline(_) => true,
decode::ImportModule::None => false,
};
if self.cx.config.mode.no_modules() {
if is_local_snippet {
bail!(
"local JS snippets are not supported with `--target no-modules`; \
use `--target web` or no flag instead",
);
}
if let decode::ImportModule::Named(module) = &import.module {
bail!(
"import from `{}` module not allowed with `--target no-modules`; \
use `nodejs`, `web`, or `bundler` target instead",
module
);
}
}
// FIXME: currently we require that local JS snippets are written in ES
// module syntax for imports/exports, but nodejs uses CommonJS to handle
// this meaning that local JS snippets are basically guaranteed to be
// incompatible. We need to implement a pass that translates the ES
// module syntax in the snippet to a CommonJS module, which is in theory
// not that hard but is a chunk of work to do.
if is_local_snippet && self.cx.config.mode.nodejs() {
// have a small unergonomic escape hatch for our webidl-tests tests
if env::var("WBINDGEN_I_PROMISE_JS_SYNTAX_WORKS_IN_NODE").is_err() {
bail!(
"local JS snippets are not supported with `--target nodejs`; \
see rustwasm/rfcs#6 for more details, but this restriction \
will be lifted in the future"
);
}
}
// Similar to `--target no-modules`, only allow vendor prefixes
// basically for web apis, shouldn't be necessary for things like npm
// packages or other imported items.
let vendor_prefixes = self.vendor_prefixes.get(item);
if let Some(vendor_prefixes) = vendor_prefixes {
assert!(vendor_prefixes.len() > 0);
if is_local_snippet {
bail!(
"local JS snippets do not support vendor prefixes for \
the import of `{}` with a polyfill of `{}`",
item,
&vendor_prefixes[0]
);
}
if let decode::ImportModule::Named(module) = &import.module {
bail!(
"import of `{}` from `{}` has a polyfill of `{}` listed, but
vendor prefixes aren't supported when importing from modules",
item,
module,
&vendor_prefixes[0],
);
}
if let Some(ns) = &import.js_namespace {
bail!(
"import of `{}` through js namespace `{}` isn't supported \
right now when it lists a polyfill",
item,
ns
);
}
return Ok(Import::VendorPrefixed {
name: item,
prefixes: vendor_prefixes.clone(),
});
}
let (name, field) = match import.js_namespace {
Some(ns) => (ns, Some(item)),
None => (item, None),
};
Ok(match import.module {
decode::ImportModule::Named(module) if is_local_snippet => Import::LocalModule {
module,
name,
field,
},
decode::ImportModule::Named(module) | decode::ImportModule::RawNamed(module) => {
Import::Module {
module,
name,
field,
}
}
decode::ImportModule::Inline(idx) => {
let offset = *self
.cx
.snippet_offsets
.get(self.program.unique_crate_identifier)
.unwrap_or(&0);
Import::InlineJs {
unique_crate_identifier: self.program.unique_crate_identifier,
snippet_idx_in_crate: idx as usize + offset,
name,
field,
}
}
decode::ImportModule::None => Import::Global { name, field },
})
}
fn import_name(&mut self, import: &decode::Import<'b>, item: &'b str) -> Result<String, Error> {
let import = self.determine_import(import, item)?;
Ok(self.cx.import_identifier(import))
}
fn add_package_json(&mut self, path: &'b str) -> Result<(), Error> {
if !self.cx.package_json_read.insert(path) {
return Ok(());
}
if !self.cx.config.mode.nodejs() && !self.cx.config.mode.bundler() {
bail!(
"NPM dependencies have been specified in `{}` but \
this is only compatible with the `bundler` and `nodejs` targets"
);
}
let contents = fs::read_to_string(path).context(format!("failed to read `{}`", path))?;
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
),
};
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
);
}
let value = match value.as_object() {
Some(s) => s,
None => bail!("expected `dependencies` to be a JSON object in `{}`", path),
};
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
),
};
if let Some((prev, _prev_version)) = self.cx.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,
prev
)
}
self.cx
.npm_dependencies
.insert(name.to_string(), (path, value.to_string()));
}
Ok(())
}
}
#[derive(Hash, Eq, PartialEq)]
pub enum ImportModule<'a> {
Named(&'a str),
Inline(&'a str, usize),
None,
}
impl<'a> Import<'a> {
fn module(&self) -> ImportModule<'a> {
match self {
Import::Module { module, .. } | Import::LocalModule { module, .. } => {
ImportModule::Named(module)
}
Import::InlineJs {
unique_crate_identifier,
snippet_idx_in_crate,
..
} => ImportModule::Inline(unique_crate_identifier, *snippet_idx_in_crate),
Import::Global { .. } | Import::VendorPrefixed { .. } => ImportModule::None,
}
}
fn field(&self) -> Option<&'a str> {
match self {
Import::Module { field, .. }
| Import::LocalModule { field, .. }
| Import::InlineJs { field, .. }
| Import::Global { field, .. } => *field,
Import::VendorPrefixed { .. } => None,
}
}
fn name(&self) -> &'a str {
match self {
Import::Module { name, .. }
| Import::LocalModule { name, .. }
| Import::InlineJs { name, .. }
| Import::Global { name, .. }
| Import::VendorPrefixed { name, .. } => *name,
}
}
}
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
.iter()
.map(|c| format!("*{}\n", c.trim_matches('"')))
.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)
}
#[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()
);
}