mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-03 02:41:06 +00:00
This commit adds experimental support for `WeakRef` to be used to automatically free wasm objects instead of having to always call the `free` function manually. Note that when enabled the `free` function for all exported objects is still generated, it's just optionally invoked by the application. Support isn't exposed through a CLI flag right now due to the early stages of the `WeakRef` proposal, but the env var `WASM_BINDGEN_WEAKREF` can be used to enable this generation. Upon doing so the output can then be edited slightly as well to work in the SpiderMonkey shell and it looks like this is working! Closes #704
2148 lines
70 KiB
Rust
2148 lines
70 KiB
Rust
use std::collections::{HashMap, HashSet};
|
|
use std::fmt::Write;
|
|
use std::mem;
|
|
|
|
use failure::{Error, ResultExt};
|
|
use parity_wasm;
|
|
use parity_wasm::elements::*;
|
|
use shared;
|
|
use wasm_gc;
|
|
|
|
use super::Bindgen;
|
|
use descriptor::{Descriptor, VectorKind};
|
|
|
|
mod js2rust;
|
|
use self::js2rust::Js2Rust;
|
|
mod rust2js;
|
|
use self::rust2js::Rust2Js;
|
|
|
|
pub struct Context<'a> {
|
|
pub globals: String,
|
|
pub imports: String,
|
|
pub footer: String,
|
|
pub typescript: String,
|
|
pub exposed_globals: HashSet<&'static str>,
|
|
pub required_internal_exports: HashSet<&'static str>,
|
|
pub config: &'a Bindgen,
|
|
pub module: &'a mut Module,
|
|
|
|
/// 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<Option<String>, HashMap<String, 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>,
|
|
|
|
pub exported_classes: HashMap<String, ExportedClass>,
|
|
pub function_table_needed: bool,
|
|
pub run_descriptor: &'a Fn(&str) -> Option<Vec<u32>>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct ExportedClass {
|
|
comments: String,
|
|
contents: String,
|
|
typescript: String,
|
|
constructor: Option<String>,
|
|
fields: Vec<ClassField>,
|
|
}
|
|
|
|
struct ClassField {
|
|
comments: Vec<String>,
|
|
name: String,
|
|
readonly: bool,
|
|
}
|
|
|
|
pub struct SubContext<'a, 'b: 'a> {
|
|
pub program: &'a shared::Program,
|
|
pub cx: &'a mut Context<'b>,
|
|
}
|
|
|
|
const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"];
|
|
|
|
impl<'a> Context<'a> {
|
|
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);
|
|
}
|
|
let global = if self.use_node_require() {
|
|
if contents.starts_with("class") {
|
|
format!("{1}\nmodule.exports.{0} = {0};\n", name, contents)
|
|
} else {
|
|
format!("module.exports.{} = {};\n", name, contents)
|
|
}
|
|
} else if self.config.no_modules {
|
|
if contents.starts_with("class") {
|
|
format!("{1}\n__exports.{0} = {0};\n", name, contents)
|
|
} else {
|
|
format!("__exports.{} = {};\n", name, contents)
|
|
}
|
|
} else {
|
|
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)
|
|
}
|
|
};
|
|
self.global(&global);
|
|
}
|
|
|
|
fn require_internal_export(&mut self, name: &'static str) -> Result<(), Error> {
|
|
if !self.required_internal_exports.insert(name) {
|
|
return Ok(());
|
|
}
|
|
if let Some(s) = self.module.export_section() {
|
|
if s.entries().iter().any(|e| e.field() == 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> {
|
|
self.write_classes()?;
|
|
|
|
self.bind("__wbindgen_object_clone_ref", &|me| {
|
|
me.expose_add_heap_object();
|
|
me.expose_get_object();
|
|
let bump_cnt = if me.config.debug {
|
|
String::from(
|
|
"
|
|
if (typeof(val) === 'number') throw new Error('corrupt slab');
|
|
val.cnt += 1;
|
|
",
|
|
)
|
|
} else {
|
|
String::from("val.cnt += 1;")
|
|
};
|
|
Ok(format!(
|
|
"
|
|
function(idx) {{
|
|
// If this object is on the stack promote it to the heap.
|
|
if ((idx & 1) === 1) return addHeapObject(getObject(idx));
|
|
|
|
// Otherwise if the object is on the heap just bump the
|
|
// refcount and move on
|
|
const val = slab[idx >> 1];
|
|
{}
|
|
return idx;
|
|
}}
|
|
",
|
|
bump_cnt
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_object_drop_ref", &|me| {
|
|
me.expose_drop_ref();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
dropRef(i);
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_string_new", &|me| {
|
|
me.expose_add_heap_object();
|
|
me.expose_get_string_from_wasm();
|
|
Ok(String::from(
|
|
"
|
|
function(p, l) {
|
|
return addHeapObject(getStringFromWasm(p, l));
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_number_new", &|me| {
|
|
me.expose_add_heap_object();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
return addHeapObject(i);
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_number_get", &|me| {
|
|
me.expose_get_object();
|
|
me.expose_uint8_memory();
|
|
Ok(String::from(
|
|
"
|
|
function(n, invalid) {
|
|
let obj = getObject(n);
|
|
if (typeof(obj) === 'number') return obj;
|
|
getUint8Memory()[invalid] = 1;
|
|
return 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_is_null", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(idx) {
|
|
return getObject(idx) === null ? 1 : 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_is_undefined", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(idx) {
|
|
return getObject(idx) === undefined ? 1 : 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_boolean_get", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
let v = getObject(i);
|
|
if (typeof(v) === 'boolean') {
|
|
return v ? 1 : 0;
|
|
} else {
|
|
return 2;
|
|
}
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_symbol_new", &|me| {
|
|
me.expose_get_string_from_wasm();
|
|
me.expose_add_heap_object();
|
|
Ok(String::from(
|
|
"
|
|
function(ptr, len) {
|
|
let a;
|
|
if (ptr === 0) {
|
|
a = Symbol();
|
|
} else {
|
|
a = Symbol(getStringFromWasm(ptr, len));
|
|
}
|
|
return addHeapObject(a);
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_is_symbol", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
return typeof(getObject(i)) === 'symbol' ? 1 : 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_is_object", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
const val = getObject(i);
|
|
return typeof(val) === 'object' && val !== null ? 1 : 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_is_function", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
return typeof(getObject(i)) === 'function' ? 1 : 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_is_string", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
return typeof(getObject(i)) === 'string' ? 1 : 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_string_get", &|me| {
|
|
me.expose_pass_string_to_wasm()?;
|
|
me.expose_get_object();
|
|
me.expose_uint32_memory();
|
|
Ok(String::from(
|
|
"
|
|
function(i, len_ptr) {
|
|
let obj = getObject(i);
|
|
if (typeof(obj) !== 'string') return 0;
|
|
const [ptr, len] = passStringToWasm(obj);
|
|
getUint32Memory()[len_ptr / 4] = len;
|
|
return ptr;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_cb_drop", &|me| {
|
|
me.expose_drop_ref();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
let obj = getObject(i).original;
|
|
obj.a = obj.b = 0;
|
|
dropRef(i);
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_cb_forget", &|me| {
|
|
me.expose_drop_ref();
|
|
Ok(String::from(
|
|
"
|
|
function(i) {
|
|
dropRef(i);
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_json_parse", &|me| {
|
|
me.expose_add_heap_object();
|
|
me.expose_get_string_from_wasm();
|
|
Ok(String::from(
|
|
"
|
|
function(ptr, len) {
|
|
return addHeapObject(JSON.parse(getStringFromWasm(ptr, len)));
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_json_serialize", &|me| {
|
|
me.expose_get_object();
|
|
me.expose_pass_string_to_wasm()?;
|
|
me.expose_uint32_memory();
|
|
Ok(String::from(
|
|
"
|
|
function(idx, ptrptr) {
|
|
const [ptr, len] = passStringToWasm(JSON.stringify(getObject(idx)));
|
|
getUint32Memory()[ptrptr / 4] = ptr;
|
|
return len;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.bind("__wbindgen_jsval_eq", &|me| {
|
|
me.expose_get_object();
|
|
Ok(String::from(
|
|
"
|
|
function(a, b) {
|
|
return getObject(a) === getObject(b) ? 1 : 0;
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.unexport_unused_internal_exports();
|
|
self.gc()?;
|
|
|
|
// Note that it's important `throw` comes last *after* we gc. The
|
|
// `__wbindgen_malloc` function may call this but we only want to
|
|
// generate code for this if it's actually live (and __wbindgen_malloc
|
|
// isn't gc'd).
|
|
self.bind("__wbindgen_throw", &|me| {
|
|
me.expose_get_string_from_wasm();
|
|
Ok(String::from(
|
|
"
|
|
function(ptr, len) {
|
|
throw new Error(getStringFromWasm(ptr, len));
|
|
}
|
|
",
|
|
))
|
|
})?;
|
|
|
|
self.rewrite_imports(module_name);
|
|
|
|
let mut js = if self.config.no_modules {
|
|
format!(
|
|
"
|
|
(function() {{
|
|
var wasm;
|
|
const __exports = {{}};
|
|
{globals}
|
|
function init(wasm_path) {{
|
|
const fetchPromise = fetch(wasm_path);
|
|
let resultPromise;
|
|
if (typeof WebAssembly.instantiateStreaming === 'function') {{
|
|
resultPromise = WebAssembly.instantiateStreaming(fetchPromise, {{ './{module}': __exports }});
|
|
}} else {{
|
|
resultPromise = fetchPromise
|
|
.then(response => response.arrayBuffer())
|
|
.then(buffer => WebAssembly.instantiate(buffer, {{ './{module}': __exports }}));
|
|
}}
|
|
return resultPromise.then(({{instance}}) => {{
|
|
wasm = init.wasm = instance.exports;
|
|
return;
|
|
}});
|
|
}};
|
|
self.{global_name} = Object.assign(init, __exports);
|
|
}})();
|
|
",
|
|
globals = self.globals,
|
|
module = module_name,
|
|
global_name = self.config.no_modules_global
|
|
.as_ref()
|
|
.map(|s| &**s)
|
|
.unwrap_or("wasm_bindgen"),
|
|
)
|
|
} else {
|
|
let import_wasm = if self.globals.len() == 0 {
|
|
String::new()
|
|
} else if self.use_node_require() {
|
|
self.footer
|
|
.push_str(&format!("wasm = require('./{}_bg');", module_name));
|
|
format!("var wasm;")
|
|
} else {
|
|
format!("import * as wasm from './{}_bg';", module_name)
|
|
};
|
|
|
|
format!(
|
|
"\
|
|
/* tslint:disable */\n\
|
|
{import_wasm}\n\
|
|
{imports}\n\
|
|
|
|
{globals}\n\
|
|
{footer}",
|
|
import_wasm = import_wasm,
|
|
globals = self.globals,
|
|
imports = self.imports,
|
|
footer = self.footer,
|
|
)
|
|
};
|
|
|
|
self.export_table();
|
|
self.gc()?;
|
|
|
|
while js.contains("\n\n\n") {
|
|
js = js.replace("\n\n\n", "\n\n");
|
|
}
|
|
|
|
Ok((js, self.typescript.clone()))
|
|
}
|
|
|
|
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> {
|
|
let classes = mem::replace(&mut self.exported_classes, Default::default());
|
|
for (class, exports) in classes {
|
|
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!("
|
|
const cleanup_ptr = this.ptr;
|
|
const ref = CLEANUPS.makeRef(this, () => free{}(cleanup_ptr));
|
|
CLEANUPS_MAP.set(this.ptr, ref);
|
|
", name);
|
|
let free = "
|
|
CLEANUPS_MAP.get(ptr).drop();
|
|
CLEANUPS_MAP.delete(ptr);
|
|
";
|
|
(mk, free)
|
|
} else {
|
|
(String::new(), "")
|
|
};
|
|
|
|
if self.config.debug || class.constructor.is_some() {
|
|
self.expose_constructor_token();
|
|
|
|
dst.push_str(&format!(
|
|
"
|
|
static __construct(ptr) {{
|
|
return new {}(new ConstructorToken(ptr));
|
|
}}
|
|
|
|
constructor(...args) {{
|
|
if (args.length === 1 && args[0] instanceof ConstructorToken) {{
|
|
this.ptr = args[0].ptr;
|
|
return;
|
|
}}
|
|
",
|
|
name
|
|
));
|
|
|
|
if let Some(ref constructor) = class.constructor {
|
|
ts_dst.push_str(&format!("constructor(...args: any[]);\n"));
|
|
|
|
dst.push_str(&format!(
|
|
"
|
|
// This invocation of new will call this constructor with a ConstructorToken
|
|
let instance = {class}.{constructor}(...args);
|
|
this.ptr = instance.ptr;
|
|
{mkweakref}
|
|
",
|
|
class = name,
|
|
constructor = constructor,
|
|
mkweakref = mkweakref,
|
|
));
|
|
} else {
|
|
dst.push_str(
|
|
"throw new Error('you cannot invoke `new` directly without having a \
|
|
method annotated a constructor');\n",
|
|
);
|
|
}
|
|
|
|
dst.push_str("}");
|
|
} else {
|
|
dst.push_str(&format!(
|
|
"
|
|
static __construct(ptr) {{
|
|
return new {}(ptr);
|
|
}}
|
|
|
|
constructor(ptr) {{
|
|
this.ptr = ptr;
|
|
{}
|
|
}}
|
|
",
|
|
name,
|
|
mkweakref,
|
|
));
|
|
}
|
|
|
|
let new_name = shared::new_function(&name);
|
|
if self.wasm_import_needed(&new_name) {
|
|
self.expose_add_heap_object();
|
|
|
|
self.export(
|
|
&new_name,
|
|
&format!(
|
|
"
|
|
function(ptr) {{
|
|
return addHeapObject({}.__construct(ptr));
|
|
}}
|
|
",
|
|
name
|
|
),
|
|
None,
|
|
);
|
|
}
|
|
|
|
for field in class.fields.iter() {
|
|
let wasm_getter = shared::struct_field_get(name, &field.name);
|
|
let wasm_setter = shared::struct_field_set(name, &field.name);
|
|
let descriptor = match self.describe(&wasm_getter) {
|
|
None => continue,
|
|
Some(d) => d,
|
|
};
|
|
|
|
let set = {
|
|
let mut cx = Js2Rust::new(&field.name, self);
|
|
cx.method(true, false).argument(&descriptor)?.ret(&None)?;
|
|
ts_dst.push_str(&format!(
|
|
"{}{}: {}\n",
|
|
if field.readonly { "readonly " } else { "" },
|
|
field.name,
|
|
&cx.js_arguments[0].1
|
|
));
|
|
cx.finish("", &format!("wasm.{}", wasm_setter)).0
|
|
};
|
|
let (get, _ts, js_doc) = Js2Rust::new(&field.name, self)
|
|
.method(true, false)
|
|
.ret(&Some(descriptor))?
|
|
.finish("", &format!("wasm.{}", wasm_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);
|
|
}
|
|
}
|
|
|
|
self.global(&format!(
|
|
"
|
|
function free{}(ptr) {{
|
|
{}
|
|
wasm.{}(ptr);
|
|
}}
|
|
",
|
|
name,
|
|
freeref,
|
|
shared::free_function(&name)
|
|
));
|
|
dst.push_str(&format!(
|
|
"
|
|
free() {{
|
|
if (this.ptr === 0)
|
|
return;
|
|
const ptr = this.ptr;
|
|
this.ptr = 0;
|
|
free{}(this.ptr);
|
|
}}
|
|
",
|
|
name,
|
|
));
|
|
ts_dst.push_str("free(): void;\n");
|
|
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) {
|
|
if !self.function_table_needed {
|
|
return;
|
|
}
|
|
for section in self.module.sections_mut() {
|
|
let exports = match *section {
|
|
Section::Export(ref mut s) => s,
|
|
_ => continue,
|
|
};
|
|
let entry = ExportEntry::new("__wbg_function_table".to_string(), Internal::Table(0));
|
|
exports.entries_mut().push(entry);
|
|
break;
|
|
}
|
|
}
|
|
|
|
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();
|
|
let imports = self
|
|
.module
|
|
.sections_mut()
|
|
.iter_mut()
|
|
.filter_map(|s| match *s {
|
|
Section::Import(ref mut s) => Some(s),
|
|
_ => None,
|
|
})
|
|
.flat_map(|s| s.entries_mut());
|
|
|
|
for import in imports {
|
|
if import.module() == "__wbindgen_placeholder__" {
|
|
import.module_mut().truncate(0);
|
|
import.module_mut().push_str("./");
|
|
import.module_mut().push_str(module_name);
|
|
continue;
|
|
}
|
|
|
|
if import.module() != "env" {
|
|
continue;
|
|
}
|
|
|
|
let renamed_import = format!("__wbindgen_{}", import.field());
|
|
let mut bind_math = |expr: &str| {
|
|
math_imports.push((renamed_import.clone(), format!("function{}", expr)));
|
|
};
|
|
|
|
// FIXME(#32): try to not use function shims
|
|
match import.field() {
|
|
"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); }"),
|
|
"cos" => bind_math("(x) { return Math.cos(x); }"),
|
|
"cosf" => bind_math("(x) { return Math.cos(x); }"),
|
|
"exp" => bind_math("(x) { return Math.exp(x); }"),
|
|
"expf" => bind_math("(x) { return Math.exp(x); }"),
|
|
"log2" => bind_math("(x) { return Math.log2(x); }"),
|
|
"log2f" => bind_math("(x) { return Math.log2(x); }"),
|
|
"log10" => bind_math("(x) { return Math.log10(x); }"),
|
|
"log10f" => bind_math("(x) { return Math.log10(x); }"),
|
|
"log" => bind_math("(x) { return Math.log(x); }"),
|
|
"logf" => bind_math("(x) { return Math.log(x); }"),
|
|
"round" => bind_math("(x) { return Math.round(x); }"),
|
|
"roundf" => bind_math("(x) { return Math.round(x); }"),
|
|
"sin" => bind_math("(x) { return Math.sin(x); }"),
|
|
"sinf" => bind_math("(x) { return Math.sin(x); }"),
|
|
"pow" => bind_math("(x, y) { return Math.pow(x, y); }"),
|
|
"powf" => bind_math("(x, y) { return Math.pow(x, y); }"),
|
|
"exp2" => bind_math("(a) { return Math.pow(2, a); }"),
|
|
"exp2f" => bind_math("(a) { return Math.pow(2, a); }"),
|
|
"fmod" => bind_math("(a, b) { return a % b; }"),
|
|
"fmodf" => bind_math("(a, b) { return a % b; }"),
|
|
"fma" => bind_math("(a, b, c) { return (a * b) + c; }"),
|
|
"fmaf" => bind_math("(a, b, c) { return (a * b) + c; }"),
|
|
_ => continue,
|
|
}
|
|
|
|
import.module_mut().truncate(0);
|
|
import.module_mut().push_str("./");
|
|
import.module_mut().push_str(module_name);
|
|
*import.field_mut() = renamed_import.clone();
|
|
}
|
|
|
|
math_imports
|
|
}
|
|
|
|
fn unexport_unused_internal_exports(&mut self) {
|
|
let required = &self.required_internal_exports;
|
|
for section in self.module.sections_mut() {
|
|
let exports = match *section {
|
|
Section::Export(ref mut s) => s,
|
|
_ => continue,
|
|
};
|
|
exports.entries_mut().retain(|export| {
|
|
!export.field().starts_with("__wbindgen") || required.contains(export.field())
|
|
});
|
|
}
|
|
}
|
|
|
|
fn expose_drop_ref(&mut self) {
|
|
if !self.exposed_globals.insert("drop_ref") {
|
|
return;
|
|
}
|
|
self.expose_global_slab();
|
|
self.expose_global_slab_next();
|
|
let validate_owned = if self.config.debug {
|
|
String::from(
|
|
"
|
|
if ((idx & 1) === 1) throw new Error('cannot drop ref of stack objects');
|
|
",
|
|
)
|
|
} else {
|
|
String::new()
|
|
};
|
|
let dec_ref = if self.config.debug {
|
|
String::from(
|
|
"
|
|
if (typeof(obj) === 'number') throw new Error('corrupt slab');
|
|
obj.cnt -= 1;
|
|
if (obj.cnt > 0) return;
|
|
",
|
|
)
|
|
} else {
|
|
String::from(
|
|
"
|
|
obj.cnt -= 1;
|
|
if (obj.cnt > 0) return;
|
|
",
|
|
)
|
|
};
|
|
self.global(&format!(
|
|
"
|
|
function dropRef(idx) {{
|
|
{}
|
|
idx = idx >> 1;
|
|
if (idx < {}) return;
|
|
let obj = slab[idx];
|
|
{}
|
|
// If we hit 0 then free up our space in the slab
|
|
slab[idx] = slab_next;
|
|
slab_next = idx;
|
|
}}
|
|
",
|
|
validate_owned, INITIAL_SLAB_VALUES.len(), dec_ref
|
|
));
|
|
}
|
|
|
|
fn expose_global_stack(&mut self) {
|
|
if !self.exposed_globals.insert("stack") {
|
|
return;
|
|
}
|
|
self.global(&format!(
|
|
"
|
|
const stack = [];
|
|
"
|
|
));
|
|
if self.config.debug {
|
|
self.export(
|
|
"assertStackEmpty",
|
|
"
|
|
function() {
|
|
if (stack.length === 0) return;
|
|
throw new Error('stack is not currently empty');
|
|
}
|
|
",
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn expose_global_slab(&mut self) {
|
|
if !self.exposed_globals.insert("slab") {
|
|
return;
|
|
}
|
|
let initial_values = INITIAL_SLAB_VALUES.iter()
|
|
.map(|s| format!("{{ obj: {} }}", s))
|
|
.collect::<Vec<_>>();
|
|
self.global(&format!("const slab = [{}];", initial_values.join(", ")));
|
|
if self.config.debug {
|
|
self.export(
|
|
"assertSlabEmpty",
|
|
&format!(
|
|
"
|
|
function() {{
|
|
for (let i = {}; i < slab.length; i++) {{
|
|
if (typeof(slab[i]) === 'number') continue;
|
|
throw new Error('slab is not currently empty');
|
|
}}
|
|
}}
|
|
",
|
|
initial_values.len()
|
|
),
|
|
None,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn expose_global_slab_next(&mut self) {
|
|
if !self.exposed_globals.insert("slab_next") {
|
|
return;
|
|
}
|
|
self.expose_global_slab();
|
|
self.global(
|
|
"
|
|
let slab_next = slab.length;
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_get_object(&mut self) {
|
|
if !self.exposed_globals.insert("get_object") {
|
|
return;
|
|
}
|
|
self.expose_global_stack();
|
|
self.expose_global_slab();
|
|
|
|
let get_obj = if self.config.debug {
|
|
String::from(
|
|
"
|
|
if (typeof(val) === 'number') throw new Error('corrupt slab');
|
|
return val.obj;
|
|
",
|
|
)
|
|
} else {
|
|
String::from(
|
|
"
|
|
return val.obj;
|
|
",
|
|
)
|
|
};
|
|
self.global(&format!(
|
|
"
|
|
function getObject(idx) {{
|
|
if ((idx & 1) === 1) {{
|
|
return stack[idx >> 1];
|
|
}} else {{
|
|
const val = slab[idx >> 1];
|
|
{}
|
|
}}
|
|
}}
|
|
",
|
|
get_obj
|
|
));
|
|
}
|
|
|
|
fn expose_assert_num(&mut self) {
|
|
if !self.exposed_globals.insert("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.exposed_globals.insert("assert_bool") {
|
|
return;
|
|
}
|
|
self.global(&format!(
|
|
"
|
|
function _assertBoolean(n) {{
|
|
if (typeof(n) !== 'boolean') {{
|
|
throw new Error('expected a boolean argument');
|
|
}}
|
|
}}
|
|
"
|
|
));
|
|
}
|
|
|
|
fn expose_pass_string_to_wasm(&mut self) -> Result<(), Error> {
|
|
if !self.exposed_globals.insert("pass_string_to_wasm") {
|
|
return Ok(());
|
|
}
|
|
self.require_internal_export("__wbindgen_malloc")?;
|
|
self.expose_text_encoder();
|
|
self.expose_uint8_memory();
|
|
let debug = if self.config.debug {
|
|
"
|
|
if (typeof(arg) !== 'string') throw new Error('expected a string argument');
|
|
"
|
|
} else {
|
|
""
|
|
};
|
|
self.global(&format!(
|
|
"
|
|
function passStringToWasm(arg) {{
|
|
{}
|
|
const buf = cachedEncoder.encode(arg);
|
|
const ptr = wasm.__wbindgen_malloc(buf.length);
|
|
getUint8Memory().set(buf, ptr);
|
|
return [ptr, buf.length];
|
|
}}
|
|
",
|
|
debug
|
|
));
|
|
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.exposed_globals.insert("pass_array_jsvalue") {
|
|
return Ok(());
|
|
}
|
|
self.require_internal_export("__wbindgen_malloc")?;
|
|
self.expose_uint32_memory();
|
|
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]);
|
|
}
|
|
return [ptr, array.length];
|
|
}
|
|
|
|
");
|
|
Ok(())
|
|
}
|
|
|
|
fn pass_array_to_wasm(
|
|
&mut self,
|
|
name: &'static str,
|
|
delegate: &str,
|
|
size: usize,
|
|
) -> Result<(), Error> {
|
|
if !self.exposed_globals.insert(name) {
|
|
return Ok(());
|
|
}
|
|
self.require_internal_export("__wbindgen_malloc")?;
|
|
self.global(&format!(
|
|
"
|
|
function {}(arg) {{
|
|
const ptr = wasm.__wbindgen_malloc(arg.length * {size});
|
|
{}().set(arg, ptr / {size});
|
|
return [ptr, arg.length];
|
|
}}
|
|
",
|
|
name,
|
|
delegate,
|
|
size = size
|
|
));
|
|
Ok(())
|
|
}
|
|
|
|
fn expose_text_encoder(&mut self) {
|
|
if !self.exposed_globals.insert("text_encoder") {
|
|
return;
|
|
}
|
|
if self.config.nodejs_experimental_modules {
|
|
self.imports
|
|
.push_str("import { TextEncoder } from 'util';\n");
|
|
} else if self.config.nodejs {
|
|
self.global(
|
|
"
|
|
const TextEncoder = require('util').TextEncoder;
|
|
",
|
|
);
|
|
} else if !(self.config.browser || self.config.no_modules) {
|
|
self.global(
|
|
"
|
|
const TextEncoder = typeof self === 'object' && self.TextEncoder
|
|
? self.TextEncoder
|
|
: require('util').TextEncoder;
|
|
",
|
|
);
|
|
}
|
|
self.global(
|
|
"
|
|
let cachedEncoder = new TextEncoder('utf-8');
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_text_decoder(&mut self) {
|
|
if !self.exposed_globals.insert("text_decoder") {
|
|
return;
|
|
}
|
|
if self.config.nodejs_experimental_modules {
|
|
self.imports
|
|
.push_str("import { TextDecoder } from 'util';\n");
|
|
} else if self.config.nodejs {
|
|
self.global(
|
|
"
|
|
const TextDecoder = require('util').TextDecoder;
|
|
",
|
|
);
|
|
} else if !(self.config.browser || self.config.no_modules) {
|
|
self.global(
|
|
"
|
|
const TextDecoder = typeof self === 'object' && self.TextDecoder
|
|
? self.TextDecoder
|
|
: require('util').TextDecoder;
|
|
",
|
|
);
|
|
}
|
|
self.global(
|
|
"
|
|
let cachedDecoder = new TextDecoder('utf-8');
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_constructor_token(&mut self) {
|
|
if !self.exposed_globals.insert("ConstructorToken") {
|
|
return;
|
|
}
|
|
|
|
self.global(
|
|
"
|
|
class ConstructorToken {
|
|
constructor(ptr) {
|
|
this.ptr = ptr;
|
|
}
|
|
}
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_get_string_from_wasm(&mut self) {
|
|
if !self.exposed_globals.insert("get_string_from_wasm") {
|
|
return;
|
|
}
|
|
self.expose_text_decoder();
|
|
self.expose_uint8_memory();
|
|
self.global(
|
|
"
|
|
function getStringFromWasm(ptr, len) {
|
|
return cachedDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
|
|
}
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_get_array_js_value_from_wasm(&mut self) {
|
|
if !self.exposed_globals.insert("get_array_js_value_from_wasm") {
|
|
return;
|
|
}
|
|
self.expose_uint32_memory();
|
|
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;
|
|
}
|
|
",
|
|
);
|
|
}
|
|
|
|
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_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.exposed_globals.insert(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_int8_memory(&mut self) {
|
|
self.memview("getInt8Memory", "Int8Array");
|
|
}
|
|
|
|
fn expose_uint8_memory(&mut self) {
|
|
self.memview("getUint8Memory", "Uint8Array");
|
|
}
|
|
|
|
fn expose_int16_memory(&mut self) {
|
|
self.memview("getInt16Memory", "Int16Array");
|
|
}
|
|
|
|
fn expose_uint16_memory(&mut self) {
|
|
self.memview("getUint16Memory", "Uint16Array");
|
|
}
|
|
|
|
fn expose_int32_memory(&mut self) {
|
|
self.memview("getInt32Memory", "Int32Array");
|
|
}
|
|
|
|
fn expose_uint32_memory(&mut self) {
|
|
self.memview("getUint32Memory", "Uint32Array");
|
|
}
|
|
|
|
fn expose_int64_memory(&mut self) {
|
|
self.memview("getInt64Memory", "BigInt64Array");
|
|
}
|
|
|
|
fn expose_uint64_memory(&mut self) {
|
|
self.memview("getUint64Memory", "BigUint64Array");
|
|
}
|
|
|
|
fn expose_f32_memory(&mut self) {
|
|
self.memview("getFloat32Memory", "Float32Array");
|
|
}
|
|
|
|
fn expose_f64_memory(&mut self) {
|
|
self.memview("getFloat64Memory", "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::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.exposed_globals.insert(name) {
|
|
return;
|
|
}
|
|
self.global(&format!(
|
|
"
|
|
let cache{name} = null;
|
|
function {name}() {{
|
|
if (cache{name} === null || cache{name}.buffer !== wasm.memory.buffer) {{
|
|
cache{name} = new {js}(wasm.memory.buffer);
|
|
}}
|
|
return cache{name};
|
|
}}
|
|
",
|
|
name = name,
|
|
js = js,
|
|
));
|
|
}
|
|
|
|
fn expose_assert_class(&mut self) {
|
|
if !self.exposed_globals.insert("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_borrowed_objects(&mut self) {
|
|
if !self.exposed_globals.insert("borrowed_objects") {
|
|
return;
|
|
}
|
|
self.expose_global_stack();
|
|
self.global(
|
|
"
|
|
function addBorrowedObject(obj) {
|
|
stack.push(obj);
|
|
return ((stack.length - 1) << 1) | 1;
|
|
}
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_take_object(&mut self) {
|
|
if !self.exposed_globals.insert("take_object") {
|
|
return;
|
|
}
|
|
self.expose_get_object();
|
|
self.expose_drop_ref();
|
|
self.global(
|
|
"
|
|
function takeObject(idx) {
|
|
const ret = getObject(idx);
|
|
dropRef(idx);
|
|
return ret;
|
|
}
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_add_heap_object(&mut self) {
|
|
if !self.exposed_globals.insert("add_heap_object") {
|
|
return;
|
|
}
|
|
self.expose_global_slab();
|
|
self.expose_global_slab_next();
|
|
let set_slab_next = if self.config.debug {
|
|
String::from(
|
|
"
|
|
if (typeof(next) !== 'number') throw new Error('corrupt slab');
|
|
slab_next = next;
|
|
",
|
|
)
|
|
} else {
|
|
String::from(
|
|
"
|
|
slab_next = next;
|
|
",
|
|
)
|
|
};
|
|
self.global(&format!(
|
|
"
|
|
function addHeapObject(obj) {{
|
|
if (slab_next === slab.length) slab.push(slab.length + 1);
|
|
const idx = slab_next;
|
|
const next = slab[idx];
|
|
{}
|
|
slab[idx] = {{ obj, cnt: 1 }};
|
|
return idx << 1;
|
|
}}
|
|
",
|
|
set_slab_next
|
|
));
|
|
}
|
|
|
|
fn wasm_import_needed(&self, name: &str) -> bool {
|
|
let imports = match self.module.import_section() {
|
|
Some(s) => s,
|
|
None => return false,
|
|
};
|
|
|
|
imports
|
|
.entries()
|
|
.iter()
|
|
.any(|i| i.module() == "__wbindgen_placeholder__" && i.field() == 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 => {
|
|
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) -> &'static str {
|
|
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::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_get_global_argument(&mut self) -> Result<(), Error> {
|
|
if !self.exposed_globals.insert("get_global_argument") {
|
|
return Ok(());
|
|
}
|
|
self.expose_uint32_memory();
|
|
self.expose_global_argument_ptr()?;
|
|
self.global(
|
|
"
|
|
function getGlobalArgument(arg) {
|
|
const idx = globalArgumentPtr() / 4 + arg;
|
|
return getUint32Memory()[idx];
|
|
}
|
|
",
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn expose_global_argument_ptr(&mut self) -> Result<(), Error> {
|
|
if !self.exposed_globals.insert("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.exposed_globals.insert("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);
|
|
}
|
|
throw new Error(`descriptor for id='${id}' not found`);
|
|
}
|
|
",
|
|
);
|
|
}
|
|
|
|
fn expose_u32_cvt_shim(&mut self) -> &'static str {
|
|
let name = "u32CvtShim";
|
|
if !self.exposed_globals.insert(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.exposed_globals.insert(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.exposed_globals.insert(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.exposed_globals.insert("is_like_none") {
|
|
return
|
|
}
|
|
self.global("
|
|
function isLikeNone(x) {
|
|
return x === undefined || x === null;
|
|
}
|
|
");
|
|
}
|
|
|
|
fn expose_cleanup_groups(&mut self) {
|
|
if !self.exposed_globals.insert("cleanup_groups") {
|
|
return
|
|
}
|
|
self.global(
|
|
"
|
|
const CLEANUPS = new WeakRefGroup(x => x.holdings());
|
|
const CLEANUPS_MAP = new Map();
|
|
"
|
|
);
|
|
}
|
|
|
|
fn gc(&mut self) -> Result<(), Error> {
|
|
let module = mem::replace(self.module, Module::default());
|
|
let module = module.parse_names().unwrap_or_else(|p| p.1);
|
|
let result = wasm_gc::Config::new()
|
|
.demangle(self.config.demangle)
|
|
.keep_debug(self.config.keep_debug || self.config.debug)
|
|
.run(module, |m| parity_wasm::serialize(m).unwrap())?;
|
|
*self.module = match result.into_module() {
|
|
Ok(m) => m,
|
|
Err(result) => deserialize_buffer(&result.into_bytes()?)?,
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn describe(&self, name: &str) -> Option<Descriptor> {
|
|
let name = format!("__wbindgen_describe_{}", name);
|
|
(self.run_descriptor)(&name).map(|d| Descriptor::decode(&d))
|
|
}
|
|
|
|
fn global(&mut self, s: &str) {
|
|
let s = s;
|
|
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.nodejs && !self.config.nodejs_experimental_modules
|
|
}
|
|
}
|
|
|
|
impl<'a, 'b> SubContext<'a, 'b> {
|
|
pub fn generate(&mut self) -> Result<(), Error> {
|
|
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() {
|
|
self.generate_import(f)?;
|
|
}
|
|
for e in self.program.enums.iter() {
|
|
self.generate_enum(e);
|
|
}
|
|
for s in self.program.structs.iter() {
|
|
let mut class = self
|
|
.cx
|
|
.exported_classes
|
|
.entry(s.name.clone())
|
|
.or_insert_with(Default::default);
|
|
class.comments = format_doc_comments(&s.comments, None);
|
|
class.fields.extend(s.fields.iter().map(|f| ClassField {
|
|
name: f.name.clone(),
|
|
readonly: f.readonly,
|
|
comments: f.comments.clone(),
|
|
}));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_export(&mut self, export: &shared::Export) -> Result<(), Error> {
|
|
if let Some(ref class) = export.class {
|
|
return self.generate_export_for_class(class, export);
|
|
}
|
|
|
|
let descriptor = match self.cx.describe(&export.function.name) {
|
|
None => return Ok(()),
|
|
Some(d) => d,
|
|
};
|
|
|
|
let (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx)
|
|
.process(descriptor.unwrap_function())?
|
|
.finish("function", &format!("wasm.{}", 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 generate_export_for_class(
|
|
&mut self,
|
|
class_name: &str,
|
|
export: &shared::Export,
|
|
) -> Result<(), Error> {
|
|
let wasm_name = 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 (js, ts, js_doc) = Js2Rust::new(&export.function.name, self.cx)
|
|
.method(export.method, export.consumed)
|
|
.process(descriptor.unwrap_function())?
|
|
.finish("", &format!("wasm.{}", wasm_name));
|
|
|
|
let class = self
|
|
.cx
|
|
.exported_classes
|
|
.entry(class_name.to_string())
|
|
.or_insert(ExportedClass::default());
|
|
class
|
|
.contents
|
|
.push_str(&format_doc_comments(&export.comments, Some(js_doc)));
|
|
if !export.method {
|
|
class.contents.push_str("static ");
|
|
class.typescript.push_str("static ");
|
|
}
|
|
|
|
let constructors: Vec<String> = self
|
|
.program
|
|
.exports
|
|
.iter()
|
|
.filter(|x| x.class == Some(class_name.to_string()))
|
|
.filter_map(|x| x.constructor.clone())
|
|
.collect();
|
|
|
|
class.constructor = match constructors.len() {
|
|
0 => None,
|
|
1 => Some(constructors[0].clone()),
|
|
x @ _ => bail!("there must be only one constructor, not {}", x),
|
|
};
|
|
class.contents.push_str(&export.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: &shared::Import) -> Result<(), Error> {
|
|
match import.kind {
|
|
shared::ImportKind::Function(ref f) => {
|
|
self.generate_import_function(import, f).with_context(|_| {
|
|
format!(
|
|
"failed to generate bindings for JS import `{}`",
|
|
f.function.name
|
|
)
|
|
})?;
|
|
}
|
|
shared::ImportKind::Static(ref s) => {
|
|
self.generate_import_static(import, s).with_context(|_| {
|
|
format!("failed to generate bindings for JS import `{}`", s.name)
|
|
})?;
|
|
}
|
|
shared::ImportKind::Type(ref ty) => {
|
|
self.generate_import_type(import, ty).with_context(|_| {
|
|
format!(
|
|
"failed to generate bindings for JS import `{}`",
|
|
ty.name,
|
|
)
|
|
})?;
|
|
}
|
|
shared::ImportKind::Enum(_) => {}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_import_static(
|
|
&mut self,
|
|
info: &shared::Import,
|
|
import: &shared::ImportStatic,
|
|
) -> Result<(), Error> {
|
|
// TODO: should support more types to import here
|
|
let obj = self.import_name(info, &import.name)?;
|
|
self.cx.expose_add_heap_object();
|
|
self.cx.export(
|
|
&import.shim,
|
|
&format!(
|
|
"
|
|
function() {{
|
|
return addHeapObject({});
|
|
}}
|
|
",
|
|
obj
|
|
),
|
|
None,
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_import_function(
|
|
&mut self,
|
|
info: &shared::Import,
|
|
import: &shared::ImportFunction,
|
|
) -> Result<(), Error> {
|
|
if !self.cx.wasm_import_needed(&import.shim) {
|
|
return Ok(());
|
|
}
|
|
|
|
let descriptor = match self.cx.describe(&import.shim) {
|
|
None => return Ok(()),
|
|
Some(d) => d,
|
|
};
|
|
|
|
let target = match &import.method {
|
|
Some(shared::MethodData { class, kind }) => {
|
|
let class = self.import_name(info, class)?;
|
|
match kind {
|
|
shared::MethodKind::Constructor => format!("new {}", class),
|
|
shared::MethodKind::Operation(shared::Operation { is_static, kind }) => {
|
|
let target = if import.structural {
|
|
let location = if *is_static { &class } else { "this" };
|
|
|
|
match kind {
|
|
shared::OperationKind::Regular => {
|
|
let nargs = descriptor.unwrap_function().arguments.len();
|
|
let mut s = format!("function(");
|
|
for i in 0..nargs - 1 {
|
|
if i > 0 {
|
|
drop(write!(s, ", "));
|
|
}
|
|
drop(write!(s, "x{}", i));
|
|
}
|
|
s.push_str(") { \nreturn this.");
|
|
s.push_str(&import.function.name);
|
|
s.push_str("(");
|
|
for i in 0..nargs - 1 {
|
|
if i > 0 {
|
|
drop(write!(s, ", "));
|
|
}
|
|
drop(write!(s, "x{}", i));
|
|
}
|
|
s.push_str(");\n}");
|
|
s
|
|
}
|
|
shared::OperationKind::Getter(g) => format!(
|
|
"function() {{
|
|
return {}.{};
|
|
}}",
|
|
location, g
|
|
),
|
|
shared::OperationKind::Setter(s) => format!(
|
|
"function(y) {{
|
|
{}.{} = y;
|
|
}}",
|
|
location, s
|
|
),
|
|
shared::OperationKind::IndexingGetter => format!(
|
|
"function(y) {{
|
|
return {}[y];
|
|
}}",
|
|
location
|
|
),
|
|
shared::OperationKind::IndexingSetter => format!(
|
|
"function(y, z) {{
|
|
{}[y] = z;
|
|
}}",
|
|
location
|
|
),
|
|
shared::OperationKind::IndexingDeleter => format!(
|
|
"function(y) {{
|
|
delete {}[y];
|
|
}}",
|
|
location
|
|
),
|
|
}
|
|
} else {
|
|
let (location, binding) = if *is_static {
|
|
("", format!(".bind({})", class))
|
|
} else {
|
|
(".prototype", "".into())
|
|
};
|
|
|
|
match kind {
|
|
shared::OperationKind::Regular => {
|
|
format!("{}{}.{}{}", class, location, import.function.name, binding)
|
|
}
|
|
shared::OperationKind::Getter(g) => {
|
|
self.cx.expose_get_inherited_descriptor();
|
|
format!(
|
|
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').get{}",
|
|
class, location, g, binding,
|
|
)
|
|
}
|
|
shared::OperationKind::Setter(s) => {
|
|
self.cx.expose_get_inherited_descriptor();
|
|
format!(
|
|
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').set{}",
|
|
class, location, s, binding,
|
|
)
|
|
}
|
|
shared::OperationKind::IndexingGetter => panic!("indexing getter should be structural"),
|
|
shared::OperationKind::IndexingSetter => panic!("indexing setter should be structural"),
|
|
shared::OperationKind::IndexingDeleter => panic!("indexing deleter should be structural"),
|
|
}
|
|
};
|
|
|
|
let fallback = if import.structural {
|
|
"".to_string()
|
|
} else {
|
|
format!(
|
|
" || function() {{
|
|
throw new Error(`wasm-bindgen: {} does not exist`);
|
|
}}",
|
|
target
|
|
)
|
|
};
|
|
|
|
self.cx.global(&format!(
|
|
"
|
|
const {}_target = {} {} ;
|
|
",
|
|
import.shim, target, fallback
|
|
));
|
|
format!(
|
|
"{}_target{}",
|
|
import.shim,
|
|
if *is_static { "" } else { ".call" }
|
|
)
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
let name = self.import_name(info, &import.function.name)?;
|
|
if name.contains(".") {
|
|
self.cx.global(&format!(
|
|
"
|
|
const {}_target = {};
|
|
",
|
|
import.shim, name
|
|
));
|
|
format!("{}_target", import.shim)
|
|
} else {
|
|
name
|
|
}
|
|
}
|
|
};
|
|
|
|
let js = Rust2Js::new(self.cx)
|
|
.catch(import.catch)
|
|
.process(descriptor.unwrap_function())?
|
|
.finish(&target);
|
|
self.cx.export(&import.shim, &js, None);
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_import_type(
|
|
&mut self,
|
|
info: &shared::Import,
|
|
import: &shared::ImportType,
|
|
) -> Result<(), Error> {
|
|
if !self.cx.wasm_import_needed(&import.instanceof_shim) {
|
|
return Ok(());
|
|
}
|
|
let name = self.import_name(info, &import.name)?;
|
|
self.cx.expose_get_object();
|
|
let body = format!("
|
|
function(idx) {{
|
|
return getObject(idx) instanceof {} ? 1 : 0;
|
|
}}
|
|
",
|
|
name,
|
|
);
|
|
self.cx.export(&import.instanceof_shim, &body, None);
|
|
Ok(())
|
|
}
|
|
|
|
fn generate_enum(&mut self, enum_: &shared::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));
|
|
|
|
variants.clear();
|
|
for variant in enum_.variants.iter() {
|
|
variants.push_str(&format!("{},", variant.name));
|
|
}
|
|
self.cx.typescript.push_str(&variants);
|
|
self.cx.typescript.push_str("}\n");
|
|
}
|
|
|
|
fn import_name(&mut self, import: &shared::Import, item: &str) -> Result<String, Error> {
|
|
// First up, imports don't work at all in `--no-modules` mode as we're
|
|
// not sure how to import them.
|
|
if self.cx.config.no_modules {
|
|
if let Some(module) = &import.module {
|
|
bail!(
|
|
"import from `{}` module not allowed with `--no-modules`; \
|
|
use `--nodejs` or `--browser` instead",
|
|
module
|
|
);
|
|
}
|
|
}
|
|
|
|
// Figure out what identifier we're importing from the module. If we've
|
|
// got a namespace we use that, otherwise it's the name specified above.
|
|
let name_to_import = import.js_namespace
|
|
.as_ref()
|
|
.map(|s| &**s)
|
|
.unwrap_or(item);
|
|
|
|
// 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.cx.use_node_require();
|
|
let imported_identifiers = &mut self.cx.imported_identifiers;
|
|
let imports = &mut self.cx.imports;
|
|
let identifier = self.cx.imported_names.entry(import.module.clone())
|
|
.or_insert_with(Default::default)
|
|
.entry(name_to_import.to_string())
|
|
.or_insert_with(|| {
|
|
let name = generate_identifier(name_to_import, imported_identifiers);
|
|
if let Some(module) = &import.module {
|
|
if use_node_require {
|
|
imports.push_str(&format!(
|
|
"const {} = require(String.raw`{}`).{};\n",
|
|
name, module, name_to_import
|
|
));
|
|
} else if name_to_import == name {
|
|
imports.push_str(&format!(
|
|
"import {{ {} }} from '{}';\n",
|
|
name, module
|
|
));
|
|
} else {
|
|
imports.push_str(&format!(
|
|
"import {{ {} as {} }} from '{}';\n",
|
|
name_to_import, name, module
|
|
));
|
|
}
|
|
}
|
|
name
|
|
});
|
|
|
|
// If there's a namespace we didn't actually import `item` but rather
|
|
// the namespace, so access through that.
|
|
if import.js_namespace.is_some() {
|
|
Ok(format!("{}.{}", identifier, item))
|
|
} else {
|
|
Ok(identifier.to_string())
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
if *cnt == 1 {
|
|
name.to_string()
|
|
} else {
|
|
format!("{}{}", name, cnt)
|
|
}
|
|
}
|
|
|
|
fn format_doc_comments(comments: &Vec<String>, 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)
|
|
}
|