mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-22 12:42:14 +00:00
1222 lines
45 KiB
Rust
1222 lines
45 KiB
Rust
use crate::descriptor::Descriptor;
|
|
use crate::intrinsic::Intrinsic;
|
|
use crate::js::{Context, Js2Rust};
|
|
use crate::webidl::{AuxImport, AuxValue, ImportBinding};
|
|
use failure::{bail, Error};
|
|
|
|
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
|
|
/// JS, then invoking an imported JS function.
|
|
pub struct Rust2Js<'a, 'b: 'a> {
|
|
cx: &'a mut Context<'b>,
|
|
|
|
/// Arguments of the JS shim that we're generating, aka the variables passed
|
|
/// from Rust which are only numbers.
|
|
shim_arguments: Vec<String>,
|
|
|
|
/// Arguments which are forwarded to the imported JS function
|
|
js_arguments: Vec<String>,
|
|
|
|
/// Conversions that happen before we invoke the wasm function, such as
|
|
/// converting a string to a ptr/length pair.
|
|
prelude: String,
|
|
|
|
/// "Destructors" or cleanup that must happen after the wasm function
|
|
/// finishes. This is scheduled in a `finally` block.
|
|
finally: String,
|
|
|
|
/// Next global index to write to when passing arguments via the single
|
|
/// global stack.
|
|
global_idx: usize,
|
|
|
|
/// Index of the next argument for unique name generation purposes.
|
|
arg_idx: usize,
|
|
|
|
/// Expression used to generate the return value. The string "JS" in this
|
|
/// expression is replaced with the actual JS invocation eventually.
|
|
ret_expr: String,
|
|
|
|
/// Whether or not we're catching JS exceptions
|
|
catch: bool,
|
|
catch_and_rethrow: bool,
|
|
|
|
/// Whether or not the last argument is a slice representing variadic arguments.
|
|
variadic: bool,
|
|
|
|
/// What sort of style this invocation will be like, see the variants of
|
|
/// this enum for more information.
|
|
style: Style,
|
|
|
|
/// list of arguments that are anyref, and whether they're an owned anyref
|
|
/// or not.
|
|
anyref_args: Vec<(usize, bool)>,
|
|
ret_anyref: bool,
|
|
}
|
|
|
|
#[derive(PartialEq)]
|
|
enum Style {
|
|
/// The imported function is expected to be invoked with `new` to create a
|
|
/// JS object.
|
|
Constructor,
|
|
/// The imported function is expected to be invoked where the first
|
|
/// parameter is the `this` and the rest of the arguments are the
|
|
/// function's arguments.
|
|
Method,
|
|
/// Just a normal function call.
|
|
Function,
|
|
}
|
|
|
|
impl<'a, 'b> Rust2Js<'a, 'b> {
|
|
pub fn new(cx: &'a mut Context<'b>) -> Rust2Js<'a, 'b> {
|
|
Rust2Js {
|
|
cx,
|
|
shim_arguments: Vec::new(),
|
|
js_arguments: Vec::new(),
|
|
prelude: String::new(),
|
|
finally: String::new(),
|
|
global_idx: 0,
|
|
arg_idx: 0,
|
|
ret_expr: String::new(),
|
|
catch: false,
|
|
catch_and_rethrow: false,
|
|
variadic: false,
|
|
anyref_args: Vec::new(),
|
|
ret_anyref: false,
|
|
style: Style::Function,
|
|
}
|
|
}
|
|
|
|
pub fn catch(&mut self, catch: bool) -> &mut Self {
|
|
self.catch = catch;
|
|
self
|
|
}
|
|
|
|
pub fn catch_and_rethrow(&mut self, catch_and_rethrow: bool) -> &mut Self {
|
|
self.catch_and_rethrow = catch_and_rethrow;
|
|
self
|
|
}
|
|
|
|
pub fn variadic(&mut self, variadic: bool) -> &mut Self {
|
|
self.variadic = variadic;
|
|
self
|
|
}
|
|
|
|
/// Generates all bindings necessary for the signature in `Function`,
|
|
/// creating necessary argument conversions and return value processing.
|
|
pub fn process(&mut self, binding: &ImportBinding) -> Result<&mut Self, Error> {
|
|
let function = match binding {
|
|
ImportBinding::Constructor(f) => {
|
|
self.style = Style::Constructor;
|
|
f
|
|
}
|
|
ImportBinding::Method(f) => {
|
|
self.style = Style::Method;
|
|
f
|
|
}
|
|
ImportBinding::Function(f) => {
|
|
self.style = Style::Function;
|
|
f
|
|
}
|
|
};
|
|
for arg in function.arguments.iter() {
|
|
// Process the function argument and assert that the metadata about
|
|
// the number of arguments on the Rust side required is correct.
|
|
let before = self.shim_arguments.len();
|
|
self.argument(arg)?;
|
|
arg.assert_abi_arg_correct(before, self.shim_arguments.len());
|
|
}
|
|
// Process the return argument, and assert that the metadata returned
|
|
// about the descriptor is indeed correct.
|
|
let before = self.shim_arguments.len();
|
|
self.ret(&function.ret)?;
|
|
function
|
|
.ret
|
|
.assert_abi_return_correct(before, self.shim_arguments.len());
|
|
Ok(self)
|
|
}
|
|
|
|
/// Get a generated name for an argument.
|
|
fn shim_argument(&mut self) -> String {
|
|
let s = format!("arg{}", self.arg_idx);
|
|
self.arg_idx += 1;
|
|
self.shim_arguments.push(s.clone());
|
|
s
|
|
}
|
|
|
|
fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
|
let abi = self.shim_argument();
|
|
|
|
let (arg, optional) = match arg {
|
|
Descriptor::Option(t) => (&**t, true),
|
|
_ => (arg, false),
|
|
};
|
|
|
|
if let Some(ty) = arg.vector_kind() {
|
|
let abi2 = self.shim_argument();
|
|
let f = self.cx.expose_get_vector_from_wasm(ty)?;
|
|
self.prelude(&format!(
|
|
"let v{0} = {prefix}{func}({0}, {1});",
|
|
abi,
|
|
abi2,
|
|
func = f,
|
|
prefix = if optional {
|
|
format!("{} == 0 ? undefined : ", abi)
|
|
} else {
|
|
String::new()
|
|
},
|
|
));
|
|
|
|
if !arg.is_by_ref() && !arg.is_clamped_by_ref() {
|
|
self.prelude(&format!(
|
|
"\
|
|
{start}
|
|
v{0} = v{0}.slice();
|
|
wasm.__wbindgen_free({0}, {1} * {size});
|
|
{end}\
|
|
",
|
|
abi,
|
|
abi2,
|
|
size = ty.size(),
|
|
start = if optional {
|
|
format!("if ({} !== 0) {{", abi)
|
|
} else {
|
|
String::new()
|
|
},
|
|
end = if optional { "}" } else { "" },
|
|
));
|
|
self.cx.require_internal_export("__wbindgen_free")?;
|
|
}
|
|
self.js_arguments.push(format!("v{}", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
// No need to special case `optional` here because `takeObject` will
|
|
// naturally work.
|
|
if arg.is_anyref() {
|
|
let arg = self.cx.take_object(&abi);
|
|
self.js_arguments.push(arg);
|
|
self.anyref_args.push((self.arg_idx - 1, true));
|
|
return Ok(());
|
|
} else if arg.is_ref_anyref() {
|
|
let arg = self.cx.get_object(&abi);
|
|
self.js_arguments.push(arg);
|
|
self.anyref_args.push((self.arg_idx - 1, false));
|
|
return Ok(());
|
|
}
|
|
|
|
if optional {
|
|
if arg.is_wasm_native() {
|
|
let value = self.shim_argument();
|
|
self.js_arguments.push(format!(
|
|
"{present} === 0 ? undefined : {value}",
|
|
value = value,
|
|
present = abi,
|
|
));
|
|
return Ok(());
|
|
}
|
|
|
|
if arg.is_abi_as_u32() {
|
|
self.js_arguments
|
|
.push(format!("{0} === 0xFFFFFF ? undefined : {0}", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(signed) = arg.get_64() {
|
|
let f = if signed {
|
|
self.cx.expose_int64_cvt_shim()
|
|
} else {
|
|
self.cx.expose_uint64_cvt_shim()
|
|
};
|
|
self.shim_argument();
|
|
let low = self.shim_argument();
|
|
let high = self.shim_argument();
|
|
let name = format!("n{}", abi);
|
|
self.prelude(&format!(
|
|
"
|
|
u32CvtShim[0] = {present} === 0 ? 0 : {low};
|
|
u32CvtShim[1] = {present} === 0 ? 0 : {high};
|
|
const {name} = {present} === 0 ? undefined : {f}[0];
|
|
",
|
|
present = abi,
|
|
low = low,
|
|
high = high,
|
|
f = f,
|
|
name = name,
|
|
));
|
|
self.js_arguments.push(name);
|
|
return Ok(());
|
|
}
|
|
|
|
match *arg {
|
|
Descriptor::Boolean => {
|
|
self.js_arguments
|
|
.push(format!("{0} === 0xFFFFFF ? undefined : {0} !== 0", abi));
|
|
return Ok(());
|
|
}
|
|
Descriptor::Enum { hole } => {
|
|
self.js_arguments
|
|
.push(format!("{0} === {1} ? undefined : {0}", abi, hole));
|
|
return Ok(());
|
|
}
|
|
Descriptor::Char => {
|
|
self.js_arguments.push(format!(
|
|
"{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})",
|
|
abi
|
|
));
|
|
return Ok(());
|
|
}
|
|
Descriptor::RustStruct(ref class) => {
|
|
self.cx.require_class_wrap(class);
|
|
let assign = format!(
|
|
"let c{0} = {0} === 0 ? undefined : {1}.__wrap({0});",
|
|
abi, class
|
|
);
|
|
self.prelude(&assign);
|
|
self.js_arguments.push(format!("c{}", abi));
|
|
return Ok(());
|
|
}
|
|
_ => bail!(
|
|
"unsupported optional argument type for calling JS function from Rust: {:?}",
|
|
arg
|
|
),
|
|
};
|
|
}
|
|
|
|
if let Some(signed) = arg.get_64() {
|
|
let f = if signed {
|
|
self.cx.expose_int64_cvt_shim()
|
|
} else {
|
|
self.cx.expose_uint64_cvt_shim()
|
|
};
|
|
let high = self.shim_argument();
|
|
let name = format!("n{}", abi);
|
|
self.prelude(&format!(
|
|
"\
|
|
u32CvtShim[0] = {low};
|
|
u32CvtShim[1] = {high};
|
|
const {name} = {f}[0];
|
|
",
|
|
low = abi,
|
|
high = high,
|
|
f = f,
|
|
name = name,
|
|
));
|
|
self.js_arguments.push(name);
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(class) = arg.rust_struct() {
|
|
if arg.is_by_ref() {
|
|
bail!("cannot invoke JS functions with custom ref types yet")
|
|
}
|
|
self.cx.require_class_wrap(class);
|
|
let assign = format!("let c{0} = {1}.__wrap({0});", abi, class);
|
|
self.prelude(&assign);
|
|
self.js_arguments.push(format!("c{}", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some((f, mutable)) = arg.stack_closure() {
|
|
let arg2 = self.shim_argument();
|
|
let (js, _ts, _js_doc) = {
|
|
let mut builder = Js2Rust::new("", self.cx);
|
|
if mutable {
|
|
builder
|
|
.prelude("let a = this.a;\n")
|
|
.prelude("this.a = 0;\n")
|
|
.rust_argument("a")
|
|
.finally("this.a = a;\n");
|
|
} else {
|
|
builder.rust_argument("this.a");
|
|
}
|
|
builder
|
|
.rust_argument("this.b")
|
|
.process(f, &None)?
|
|
.finish("function", "this.f")
|
|
};
|
|
self.cx.export_function_table()?;
|
|
self.global_idx();
|
|
self.prelude(&format!(
|
|
"\
|
|
let cb{0} = {js};\n\
|
|
cb{0}.f = wasm.__wbg_function_table.get({idx});\n\
|
|
cb{0}.a = {0};\n\
|
|
cb{0}.b = {1};\n\
|
|
",
|
|
abi,
|
|
arg2,
|
|
js = js,
|
|
idx = f.shim_idx,
|
|
));
|
|
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi));
|
|
self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi));
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(num) = arg.number() {
|
|
if num.is_u32() {
|
|
self.js_arguments.push(format!("{} >>> 0", abi));
|
|
} else {
|
|
self.js_arguments.push(abi);
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
let invoc_arg = match *arg {
|
|
Descriptor::Boolean => format!("{} !== 0", abi),
|
|
Descriptor::Char => format!("String.fromCodePoint({})", abi),
|
|
_ => bail!(
|
|
"unsupported argument type for calling JS function from Rust: {:?}",
|
|
arg
|
|
),
|
|
};
|
|
self.js_arguments.push(invoc_arg);
|
|
Ok(())
|
|
}
|
|
|
|
fn ret(&mut self, ty: &Descriptor) -> Result<(), Error> {
|
|
if let Descriptor::Unit = ty {
|
|
self.ret_expr = "JS;".to_string();
|
|
return Ok(());
|
|
}
|
|
let (ty, optional) = match ty {
|
|
Descriptor::Option(t) => (&**t, true),
|
|
_ => (ty, false),
|
|
};
|
|
if ty.is_by_ref() {
|
|
bail!("cannot return a reference from JS to Rust")
|
|
}
|
|
if let Some(ty) = ty.vector_kind() {
|
|
let f = self.cx.pass_to_wasm_function(ty)?;
|
|
self.cx.expose_uint32_memory();
|
|
self.shim_arguments.insert(0, "ret".to_string());
|
|
let mut prelude = String::new();
|
|
let expr = if optional {
|
|
prelude.push_str("const val = JS;");
|
|
self.cx.expose_is_like_none();
|
|
format!("isLikeNone(val) ? [0, 0] : {}(val)", f)
|
|
} else {
|
|
format!("{}(JS)", f)
|
|
};
|
|
self.ret_expr = format!(
|
|
"\
|
|
{}
|
|
const retptr = {};
|
|
const retlen = WASM_VECTOR_LEN;
|
|
const mem = getUint32Memory();
|
|
mem[ret / 4] = retptr;
|
|
mem[ret / 4 + 1] = retlen;
|
|
",
|
|
prelude, expr
|
|
);
|
|
return Ok(());
|
|
}
|
|
if ty.is_anyref() {
|
|
if self.cx.config.anyref {
|
|
if optional {
|
|
self.cx.expose_add_to_anyref_table()?;
|
|
self.cx.expose_is_like_none();
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0 : addToAnyrefTable(val);
|
|
"
|
|
.to_string();
|
|
} else {
|
|
self.ret_anyref = true;
|
|
self.ret_expr = "return JS;".to_string()
|
|
}
|
|
} else {
|
|
self.cx.expose_add_heap_object();
|
|
if optional {
|
|
self.cx.expose_is_like_none();
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0 : addHeapObject(val);
|
|
"
|
|
.to_string();
|
|
} else {
|
|
self.ret_expr = "return addHeapObject(JS);".to_string()
|
|
}
|
|
}
|
|
return Ok(());
|
|
}
|
|
if optional {
|
|
self.cx.expose_is_like_none();
|
|
|
|
if ty.is_wasm_native() {
|
|
self.cx.expose_uint32_memory();
|
|
match ty {
|
|
Descriptor::I32 => self.cx.expose_int32_memory(),
|
|
Descriptor::U32 => (),
|
|
Descriptor::F32 => self.cx.expose_f32_memory(),
|
|
Descriptor::F64 => self.cx.expose_f64_memory(),
|
|
_ => (),
|
|
};
|
|
self.shim_arguments.insert(0, "ret".to_string());
|
|
self.ret_expr = format!(
|
|
"
|
|
const val = JS;
|
|
getUint32Memory()[ret / 4] = !isLikeNone(val);
|
|
{mem}[ret / {size} + 1] = isLikeNone(val) ? 0 : val;
|
|
",
|
|
size = match ty {
|
|
Descriptor::I32 => 4,
|
|
Descriptor::U32 => 4,
|
|
Descriptor::F32 => 4,
|
|
Descriptor::F64 => 8,
|
|
_ => unreachable!(),
|
|
},
|
|
mem = match ty {
|
|
Descriptor::I32 => "getInt32Memory()",
|
|
Descriptor::U32 => "getUint32Memory()",
|
|
Descriptor::F32 => "getFloat32Memory()",
|
|
Descriptor::F64 => "getFloat64Memory()",
|
|
_ => unreachable!(),
|
|
}
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
if ty.is_abi_as_u32() {
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0xFFFFFF : val;
|
|
"
|
|
.to_string();
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(signed) = ty.get_64() {
|
|
self.cx.expose_uint32_memory();
|
|
let f = if signed {
|
|
self.cx.expose_int64_memory();
|
|
"getInt64Memory"
|
|
} else {
|
|
self.cx.expose_uint64_memory();
|
|
"getUint64Memory"
|
|
};
|
|
self.shim_arguments.insert(0, "ret".to_string());
|
|
self.ret_expr = format!(
|
|
"
|
|
const val = JS;
|
|
getUint32Memory()[ret / 4] = !isLikeNone(val);
|
|
{}()[ret / 8 + 1] = isLikeNone(val) ? BigInt(0) : val;
|
|
",
|
|
f
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
match *ty {
|
|
Descriptor::Boolean => {
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0xFFFFFF : val ? 1 : 0;
|
|
"
|
|
.to_string();
|
|
}
|
|
Descriptor::Char => {
|
|
self.ret_expr = "
|
|
const val = JS;
|
|
return isLikeNone(val) ? 0xFFFFFF : val.codePointAt(0);
|
|
"
|
|
.to_string();
|
|
}
|
|
Descriptor::Enum { hole } => {
|
|
self.ret_expr = format!(
|
|
"
|
|
const val = JS;
|
|
return isLikeNone(val) ? {} : val;
|
|
",
|
|
hole
|
|
);
|
|
}
|
|
Descriptor::RustStruct(ref class) => {
|
|
// Like below, assert the type
|
|
self.ret_expr = format!(
|
|
"\
|
|
const val = JS;
|
|
if (isLikeNone(val))
|
|
return 0;
|
|
if (!(val instanceof {0})) {{
|
|
throw new Error('expected value of type {0}');
|
|
}}
|
|
const ret = val.ptr;
|
|
val.ptr = 0;
|
|
return ret;\
|
|
",
|
|
class
|
|
);
|
|
}
|
|
_ => bail!(
|
|
"unsupported optional return type for calling JS function from Rust: {:?}",
|
|
ty
|
|
),
|
|
};
|
|
|
|
return Ok(());
|
|
}
|
|
if ty.number().is_some() {
|
|
self.ret_expr = "return JS;".to_string();
|
|
return Ok(());
|
|
}
|
|
if let Some(signed) = ty.get_64() {
|
|
let f = if signed {
|
|
self.cx.expose_int64_memory();
|
|
"getInt64Memory"
|
|
} else {
|
|
self.cx.expose_uint64_memory();
|
|
"getUint64Memory"
|
|
};
|
|
self.shim_arguments.insert(0, "ret".to_string());
|
|
self.ret_expr = format!(
|
|
"\
|
|
const val = JS;\n\
|
|
{}()[ret / 8] = val;\n\
|
|
",
|
|
f
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
if let Some(class) = ty.rust_struct() {
|
|
if ty.is_by_ref() {
|
|
bail!("cannot invoke JS functions returning custom ref types yet")
|
|
}
|
|
// Insert an assertion to the type of the returned value as
|
|
// otherwise this will cause memory unsafety on the Rust side of
|
|
// things.
|
|
self.ret_expr = format!(
|
|
"\
|
|
const val = JS;
|
|
if (!(val instanceof {0})) {{
|
|
throw new Error('expected value of type {0}');
|
|
}}
|
|
const ret = val.ptr;
|
|
val.ptr = 0;
|
|
return ret;\
|
|
",
|
|
class
|
|
);
|
|
return Ok(());
|
|
}
|
|
|
|
self.ret_expr = match *ty {
|
|
Descriptor::Boolean => "return JS;".to_string(),
|
|
Descriptor::Char => "return JS.codePointAt(0);".to_string(),
|
|
_ => bail!(
|
|
"unsupported return type for calling JS function from Rust: {:?}",
|
|
ty
|
|
),
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns whether this shim won't actually do anything when called other
|
|
/// than forward the invocation somewhere else.
|
|
///
|
|
/// This is used as an optimization to wire up imports directly where
|
|
/// possible and avoid a shim in some circumstances.
|
|
fn is_noop(&self) -> bool {
|
|
let Rust2Js {
|
|
// fields which may affect whether we do nontrivial work
|
|
catch,
|
|
catch_and_rethrow,
|
|
finally,
|
|
js_arguments,
|
|
prelude,
|
|
ret_expr,
|
|
variadic,
|
|
shim_arguments,
|
|
|
|
// all other fields, listed explicitly here so if one is added we'll
|
|
// trigger a nonexhaustive error.
|
|
arg_idx: _,
|
|
cx: _,
|
|
global_idx: _,
|
|
anyref_args: _,
|
|
ret_anyref: _,
|
|
style,
|
|
} = self;
|
|
|
|
!catch &&
|
|
!catch_and_rethrow &&
|
|
!variadic &&
|
|
prelude.is_empty() &&
|
|
finally.is_empty() &&
|
|
// make sure our faux return expression is "simple" by not
|
|
// performing any sort of transformation on the return value
|
|
(ret_expr == "JS;" || ret_expr == "return JS;") &&
|
|
// similarly we want to make sure that all the arguments are simply
|
|
// forwarded from the shim we would generate to the import,
|
|
// requiring no transformations
|
|
js_arguments == shim_arguments &&
|
|
// method/constructor invocations require some JS shimming right
|
|
// now, so only normal function-style invocations may get wired up
|
|
*style == Style::Function
|
|
}
|
|
|
|
pub fn finish(&mut self, target: &AuxImport) -> Result<String, Error> {
|
|
let variadic = self.variadic;
|
|
let variadic_args = |js_arguments: &[String]| {
|
|
Ok(if !variadic {
|
|
format!("{}", js_arguments.join(", "))
|
|
} else {
|
|
let (last_arg, args) = match js_arguments.split_last() {
|
|
Some(pair) => pair,
|
|
None => bail!("a function with no arguments cannot be variadic"),
|
|
};
|
|
if args.len() > 0 {
|
|
format!("{}, ...{}", args.join(", "), last_arg)
|
|
} else {
|
|
format!("...{}", last_arg)
|
|
}
|
|
})
|
|
};
|
|
|
|
let invoc = match target {
|
|
AuxImport::Value(val) => match self.style {
|
|
Style::Constructor => {
|
|
let js = match val {
|
|
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
|
_ => bail!("invalid import set for constructor"),
|
|
};
|
|
format!("new {}({})", js, variadic_args(&self.js_arguments)?)
|
|
}
|
|
Style::Method => {
|
|
let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| {
|
|
format!(
|
|
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}",
|
|
anchor, extra, field, which
|
|
)
|
|
};
|
|
let js = match val {
|
|
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
|
AuxValue::Getter(class, field) => {
|
|
self.cx.expose_get_inherited_descriptor();
|
|
let class = self.cx.import_name(class)?;
|
|
descriptor(&class, ".prototype", field, "get")
|
|
}
|
|
AuxValue::ClassGetter(class, field) => {
|
|
self.cx.expose_get_inherited_descriptor();
|
|
let class = self.cx.import_name(class)?;
|
|
descriptor(&class, "", field, "get")
|
|
}
|
|
AuxValue::Setter(class, field) => {
|
|
self.cx.expose_get_inherited_descriptor();
|
|
let class = self.cx.import_name(class)?;
|
|
descriptor(&class, ".prototype", field, "set")
|
|
}
|
|
AuxValue::ClassSetter(class, field) => {
|
|
self.cx.expose_get_inherited_descriptor();
|
|
let class = self.cx.import_name(class)?;
|
|
descriptor(&class, "", field, "set")
|
|
}
|
|
};
|
|
format!("{}.call({})", js, variadic_args(&self.js_arguments)?)
|
|
}
|
|
Style::Function => {
|
|
let js = match val {
|
|
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
|
_ => bail!("invalid import set for constructor"),
|
|
};
|
|
if self.is_noop() {
|
|
self.cx.expose_does_not_exist();
|
|
// TODO: comment this
|
|
let js = format!("typeof {} === 'undefined' ? doesNotExist : {0}", js);
|
|
return Ok(js);
|
|
}
|
|
format!("{}({})", js, variadic_args(&self.js_arguments)?)
|
|
}
|
|
},
|
|
|
|
AuxImport::Instanceof(js) => {
|
|
let js = self.cx.import_name(js)?;
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("{} instanceof {}", self.js_arguments[0], js)
|
|
}
|
|
|
|
AuxImport::Static(js) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 0);
|
|
self.cx.import_name(js)?
|
|
}
|
|
|
|
AuxImport::Closure(closure) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 3);
|
|
let (js, _ts, _js_doc) = {
|
|
let mut builder = Js2Rust::new("", self.cx);
|
|
|
|
// First up with a closure we increment the internal reference
|
|
// count. This ensures that the Rust closure environment won't
|
|
// be deallocated while we're invoking it.
|
|
builder.prelude("this.cnt++;");
|
|
|
|
if closure.mutable {
|
|
// For mutable closures they can't be invoked recursively.
|
|
// To handle that we swap out the `this.a` pointer with zero
|
|
// while we invoke it. If we finish and the closure wasn't
|
|
// destroyed, then we put back the pointer so a future
|
|
// invocation can succeed.
|
|
builder
|
|
.prelude("let a = this.a;")
|
|
.prelude("this.a = 0;")
|
|
.rust_argument("a")
|
|
.rust_argument("b")
|
|
.finally("if (--this.cnt === 0) d(a, b);")
|
|
.finally("else this.a = a;");
|
|
} else {
|
|
// For shared closures they can be invoked recursively so we
|
|
// just immediately pass through `this.a`. If we end up
|
|
// executing the destructor, however, we clear out the
|
|
// `this.a` pointer to prevent it being used again the
|
|
// future.
|
|
builder
|
|
.rust_argument("this.a")
|
|
.rust_argument("b")
|
|
.finally("if (--this.cnt === 0) {")
|
|
.finally("d(this.a, b);")
|
|
.finally("this.a = 0;")
|
|
.finally("}");
|
|
}
|
|
builder
|
|
.process(&closure.function, &None)?
|
|
.finish("function", "f")
|
|
};
|
|
self.cx.export_function_table()?;
|
|
let body = format!(
|
|
"
|
|
const f = wasm.__wbg_function_table.get({});
|
|
const d = wasm.__wbg_function_table.get({});
|
|
const b = {};
|
|
const cb = {};
|
|
cb.a = {};
|
|
cb.cnt = 1;
|
|
let real = cb.bind(cb);
|
|
real.original = cb;
|
|
",
|
|
closure.shim_idx,
|
|
closure.dtor_idx,
|
|
&self.js_arguments[1],
|
|
js,
|
|
&self.js_arguments[0],
|
|
);
|
|
self.prelude(&body);
|
|
"real".to_string()
|
|
}
|
|
|
|
AuxImport::StructuralMethod(name) => {
|
|
assert!(self.style == Style::Function);
|
|
let (receiver, args) = match self.js_arguments.split_first() {
|
|
Some(pair) => pair,
|
|
None => bail!("structural method calls must have at least one argument"),
|
|
};
|
|
format!("{}.{}({})", receiver, name, variadic_args(args)?)
|
|
}
|
|
|
|
AuxImport::StructuralGetter(field) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("{}.{}", self.js_arguments[0], field)
|
|
}
|
|
|
|
AuxImport::StructuralClassGetter(class, field) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 0);
|
|
let class = self.cx.import_name(class)?;
|
|
format!("{}.{}", class, field)
|
|
}
|
|
|
|
AuxImport::StructuralSetter(field) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 2);
|
|
format!(
|
|
"{}.{} = {}",
|
|
self.js_arguments[0], field, self.js_arguments[1]
|
|
)
|
|
}
|
|
|
|
AuxImport::StructuralClassSetter(class, field) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
let class = self.cx.import_name(class)?;
|
|
format!("{}.{} = {}", class, field, self.js_arguments[0])
|
|
}
|
|
|
|
AuxImport::IndexingGetterOfClass(class) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
let class = self.cx.import_name(class)?;
|
|
format!("{}[{}]", class, self.js_arguments[0])
|
|
}
|
|
|
|
AuxImport::IndexingGetterOfObject => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 2);
|
|
format!("{}[{}]", self.js_arguments[0], self.js_arguments[1])
|
|
}
|
|
|
|
AuxImport::IndexingSetterOfClass(class) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 2);
|
|
let class = self.cx.import_name(class)?;
|
|
format!(
|
|
"{}[{}] = {}",
|
|
class, self.js_arguments[0], self.js_arguments[1]
|
|
)
|
|
}
|
|
|
|
AuxImport::IndexingSetterOfObject => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 3);
|
|
format!(
|
|
"{}[{}] = {}",
|
|
self.js_arguments[0], self.js_arguments[1], self.js_arguments[2]
|
|
)
|
|
}
|
|
|
|
AuxImport::IndexingDeleterOfClass(class) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
let class = self.cx.import_name(class)?;
|
|
format!("delete {}[{}]", class, self.js_arguments[0])
|
|
}
|
|
|
|
AuxImport::IndexingDeleterOfObject => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 2);
|
|
format!("delete {}[{}]", self.js_arguments[0], self.js_arguments[1])
|
|
}
|
|
|
|
AuxImport::WrapInExportedClass(class) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.cx.require_class_wrap(class);
|
|
if self.is_noop() {
|
|
return Ok(format!("{}.__wrap", class));
|
|
}
|
|
format!("{}.__wrap({})", class, self.js_arguments[0])
|
|
}
|
|
|
|
AuxImport::Intrinsic(intrinsic) => {
|
|
assert!(self.style == Style::Function);
|
|
assert!(!variadic);
|
|
self.intrinsic_expr(intrinsic)?
|
|
}
|
|
};
|
|
let mut invoc = self.ret_expr.replace("JS", &invoc);
|
|
|
|
if self.catch {
|
|
self.cx.expose_handle_error()?;
|
|
invoc = format!(
|
|
"\
|
|
try {{\n\
|
|
{}
|
|
}} catch (e) {{\n\
|
|
handleError(exnptr, e);\n\
|
|
}}\
|
|
",
|
|
&invoc
|
|
);
|
|
} else if self.catch_and_rethrow {
|
|
invoc = format!(
|
|
"\
|
|
try {{\n\
|
|
{}
|
|
}} catch (e) {{\n\
|
|
let error = (function () {{
|
|
try {{
|
|
return e instanceof Error \
|
|
? `${{e.message}}\\n\\nStack:\\n${{e.stack}}` \
|
|
: e.toString();
|
|
}} catch(_) {{
|
|
return \"<failed to stringify thrown value>\";
|
|
}}
|
|
}}());
|
|
console.error(\"wasm-bindgen: imported JS function that \
|
|
was not marked as `catch` threw an error:\", \
|
|
error);
|
|
throw e;
|
|
}}\
|
|
",
|
|
&invoc,
|
|
);
|
|
}
|
|
|
|
if self.finally.len() > 0 {
|
|
invoc = format!(
|
|
"\
|
|
try {{\n\
|
|
{}
|
|
}} finally {{\n\
|
|
{}
|
|
}}\
|
|
",
|
|
&invoc, &self.finally
|
|
);
|
|
}
|
|
let mut ret = String::new();
|
|
ret.push_str("function(");
|
|
ret.push_str(&self.shim_arguments.join(", "));
|
|
if self.catch {
|
|
if self.shim_arguments.len() > 0 {
|
|
ret.push_str(", ")
|
|
}
|
|
ret.push_str("exnptr");
|
|
}
|
|
ret.push_str(") {\n");
|
|
ret.push_str(&self.prelude);
|
|
|
|
ret.push_str(&invoc);
|
|
ret.push_str("\n}\n");
|
|
|
|
Ok(ret)
|
|
}
|
|
|
|
fn global_idx(&mut self) -> usize {
|
|
let ret = self.global_idx;
|
|
self.global_idx += 1;
|
|
ret
|
|
}
|
|
|
|
fn prelude(&mut self, s: &str) -> &mut Self {
|
|
for line in s.lines() {
|
|
self.prelude.push_str(line);
|
|
self.prelude.push_str("\n");
|
|
}
|
|
self
|
|
}
|
|
|
|
fn finally(&mut self, s: &str) -> &mut Self {
|
|
for line in s.lines() {
|
|
self.finally.push_str(line);
|
|
self.finally.push_str("\n");
|
|
}
|
|
self
|
|
}
|
|
|
|
fn intrinsic_expr(&mut self, intrinsic: &Intrinsic) -> Result<String, Error> {
|
|
let expr = match intrinsic {
|
|
Intrinsic::JsvalEq => {
|
|
assert_eq!(self.js_arguments.len(), 2);
|
|
format!("{} === {}", self.js_arguments[0], self.js_arguments[1])
|
|
}
|
|
|
|
Intrinsic::IsFunction => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("typeof({}) === 'function'", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::IsUndefined => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("{} === undefined", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::IsNull => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("{} === null", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::IsObject => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.prelude(&format!("const val = {};", self.js_arguments[0]));
|
|
format!("typeof(val) === 'object' && val !== null ? 1 : 0")
|
|
}
|
|
|
|
Intrinsic::IsSymbol => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("typeof({}) === 'symbol'", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::IsString => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("typeof({}) === 'string'", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::ObjectCloneRef => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.js_arguments[0].clone()
|
|
}
|
|
|
|
Intrinsic::ObjectDropRef => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.js_arguments[0].clone()
|
|
}
|
|
|
|
Intrinsic::CallbackDrop => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.prelude(&format!("const obj = {}.original;", self.js_arguments[0]));
|
|
self.prelude("if (obj.cnt-- == 1) {");
|
|
self.prelude("obj.a = 0;");
|
|
self.prelude("return true;");
|
|
self.prelude("}");
|
|
"false".to_string()
|
|
}
|
|
|
|
Intrinsic::CallbackForget => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.js_arguments[0].clone()
|
|
}
|
|
|
|
Intrinsic::NumberNew => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.js_arguments[0].clone()
|
|
}
|
|
|
|
Intrinsic::StringNew => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.js_arguments[0].clone()
|
|
}
|
|
|
|
Intrinsic::SymbolNamedNew => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("Symbol({})", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::SymbolAnonymousNew => {
|
|
assert_eq!(self.js_arguments.len(), 0);
|
|
"Symbol()".to_string()
|
|
}
|
|
|
|
Intrinsic::NumberGet => {
|
|
assert_eq!(self.js_arguments.len(), 2);
|
|
self.cx.expose_uint8_memory();
|
|
self.prelude(&format!("const obj = {};", self.js_arguments[0]));
|
|
self.prelude("if (typeof(obj) === 'number') return obj;");
|
|
self.prelude(&format!("getUint8Memory()[{}] = 1;", self.js_arguments[1]));
|
|
"0".to_string()
|
|
}
|
|
|
|
Intrinsic::StringGet => {
|
|
self.cx.expose_pass_string_to_wasm()?;
|
|
self.cx.expose_uint32_memory();
|
|
assert_eq!(self.js_arguments.len(), 2);
|
|
self.prelude(&format!("const obj = {};", self.js_arguments[0]));
|
|
self.prelude("if (typeof(obj) !== 'string') return 0;");
|
|
self.prelude("const ptr = passStringToWasm(obj);");
|
|
self.prelude(&format!(
|
|
"getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;",
|
|
self.js_arguments[1],
|
|
));
|
|
"ptr".to_string()
|
|
}
|
|
|
|
Intrinsic::BooleanGet => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.prelude(&format!("const v = {};", self.js_arguments[0]));
|
|
format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
|
|
}
|
|
Intrinsic::Throw => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("throw new Error({})", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::Rethrow => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("throw {}", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::Module => {
|
|
assert_eq!(self.js_arguments.len(), 0);
|
|
if !self.cx.config.mode.no_modules() && !self.cx.config.mode.web() {
|
|
bail!(
|
|
"`wasm_bindgen::module` is currently only supported with \
|
|
`--target no-modules` and `--target web`"
|
|
);
|
|
}
|
|
format!("init.__wbindgen_wasm_module")
|
|
}
|
|
|
|
Intrinsic::Memory => {
|
|
assert_eq!(self.js_arguments.len(), 0);
|
|
self.cx.memory().to_string()
|
|
}
|
|
|
|
Intrinsic::FunctionTable => {
|
|
assert_eq!(self.js_arguments.len(), 0);
|
|
self.cx.export_function_table()?;
|
|
format!("wasm.__wbg_function_table")
|
|
}
|
|
|
|
Intrinsic::DebugString => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
self.cx.expose_debug_string();
|
|
format!("debugString({})", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::JsonParse => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("JSON.parse({})", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::JsonSerialize => {
|
|
assert_eq!(self.js_arguments.len(), 1);
|
|
format!("JSON.stringify({})", self.js_arguments[0])
|
|
}
|
|
|
|
Intrinsic::AnyrefHeapLiveCount => {
|
|
assert_eq!(self.js_arguments.len(), 0);
|
|
if self.cx.config.anyref {
|
|
// Eventually we should add support to the anyref-xform to
|
|
// re-write calls to the imported
|
|
// `__wbindgen_anyref_heap_live_count` function into calls to
|
|
// the exported `__wbindgen_anyref_heap_live_count_impl`
|
|
// function, and to un-export that function.
|
|
//
|
|
// But for now, we just bounce wasm -> js -> wasm because it is
|
|
// easy.
|
|
self.cx.require_internal_export("__wbindgen_anyref_heap_live_count_impl")?;
|
|
"wasm.__wbindgen_anyref_heap_live_count_impl()".into()
|
|
} else {
|
|
self.cx.expose_global_heap();
|
|
self.prelude(
|
|
"
|
|
let free_count = 0;
|
|
let next = heap_next;
|
|
while (next < heap.length) {
|
|
free_count += 1;
|
|
next = heap[next];
|
|
}
|
|
",
|
|
);
|
|
format!(
|
|
"heap.length - free_count - {} - {}",
|
|
super::INITIAL_HEAP_OFFSET,
|
|
super::INITIAL_HEAP_VALUES.len(),
|
|
)
|
|
}
|
|
}
|
|
|
|
Intrinsic::InitAnyrefTable => {
|
|
self.cx.expose_anyref_table();
|
|
String::from(
|
|
"
|
|
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);
|
|
",
|
|
)
|
|
}
|
|
};
|
|
Ok(expr)
|
|
}
|
|
}
|