1
0
mirror of https://github.com/fluencelabs/wasm-bindgen synced 2025-04-02 10:21:07 +00:00
Alex Crichton f80a7067a0 Use a struct instead of a bool variant
Helps it be a bit more readable!
2018-04-17 11:29:03 -07:00

311 lines
10 KiB
Rust

use super::Context;
use descriptor::{Descriptor, Function};
use super::{indent, Js2Rust};
/// Helper struct for manfuacturing 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,
}
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,
}
}
pub fn catch(&mut self, catch: bool) -> &mut Self {
if catch {
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
}
self.catch = catch;
self
}
/// Generates all bindings necessary for the signature in `Function`,
/// creating necessary argument conversions and return value processing.
pub fn process(&mut self, function: &Function) -> &mut Self {
for arg in function.arguments.iter() {
self.argument(arg);
}
self.ret(&function.ret);
self
}
fn argument(&mut self, arg: &Descriptor) {
let i = self.arg_idx;
self.arg_idx += 1;
self.shim_arguments.push(format!("arg{}", i));
if let Some(ty) = arg.vector_kind() {
let f = self.cx.expose_get_vector_from_wasm(ty);
self.cx.expose_get_global_argument();
let next_global = self.global_idx();
self.prelude(&format!("\
let len{0} = getGlobalArgument({next_global});\n\
let v{0} = {func}(arg{0}, len{0});\n\
", i, func = f, next_global = next_global));
if !arg.is_by_ref() {
self.prelude(&format!("\
wasm.__wbindgen_free(arg{0}, len{0} * {size});\
", i, size = ty.size()));
self.cx.required_internal_exports.insert(
"__wbindgen_free"
);
}
self.js_arguments.push(format!("v{}", i));
return
}
if let Some(class) = arg.rust_struct() {
if arg.is_by_ref() {
panic!("cannot invoke JS functions with custom ref types yet")
}
let assign = format!("let c{0} = {1}.__construct(arg{0});", i, class);
self.prelude(&assign);
self.js_arguments.push(format!("c{}", i));
return
}
if let Some((f, mutable)) = arg.stack_closure() {
let (js, _ts) = {
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)
.finish("function", "this.f")
};
self.cx.expose_get_global_argument();
self.cx.function_table_needed = true;
let next_global = self.global_idx();
self.global_idx();
self.prelude(&format!("\
let cb{0} = {js};\n\
cb{0}.f = wasm.__wbg_function_table.get(arg{0});\n\
cb{0}.a = getGlobalArgument({next_global});\n\
cb{0}.b = getGlobalArgument({next_global} + 1);\n\
", i, js = js, next_global = next_global));
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", i));
self.js_arguments.push(format!("cb{0}.bind(cb{0})", i));
return
}
if let Some(closure) = arg.ref_closure() {
let (js, _ts) = {
let mut builder = Js2Rust::new("", self.cx);
if closure.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(&closure.function)
.finish("function", "this.f")
};
self.cx.expose_get_global_argument();
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
self.cx.function_table_needed = true;
let reset_idx = format!("\
let cb{0} = {js};\n\
cb{0}.a = getGlobalArgument({a});\n\
cb{0}.b = getGlobalArgument({b});\n\
cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({c}));\n\
let real = cb{0}.bind(cb{0});\n\
real.original = cb{0};\n\
idx{0} = getUint32Memory()[arg{0} / 4] = addHeapObject(real);\n\
",
i,
js = js,
a = self.global_idx(),
b = self.global_idx(),
c = self.global_idx(),
);
self.prelude(&format!("\
let idx{0} = getUint32Memory()[arg{0} / 4];\n\
if (idx{0} === 0xffffffff) {{\n\
{1}\
}}\n\
", i, indent(&reset_idx)));
self.cx.expose_get_object();
self.js_arguments.push(format!("getObject(idx{})", i));
return
}
let invoc_arg = match *arg {
ref d if d.is_number() => format!("arg{}", i),
Descriptor::Boolean => format!("arg{} !== 0", i),
Descriptor::Anyref => {
self.cx.expose_take_object();
format!("takeObject(arg{})", i)
}
ref d if d.is_ref_anyref() => {
self.cx.expose_get_object();
format!("getObject(arg{})", i)
}
_ => panic!("unimplemented argument type in imported function: {:?}", arg),
};
self.js_arguments.push(invoc_arg);
}
fn ret(&mut self, ret: &Option<Descriptor>) {
let ty = match *ret {
Some(ref t) => t,
None => {
self.ret_expr = "JS;".to_string();
return
}
};
if ty.is_by_ref() {
panic!("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.cx.expose_set_global_argument();
self.ret_expr = format!("\
const [retptr, retlen] = {}(JS);\n\
setGlobalArgument(retlen, 0);\n\
return retptr;\n\
", f);
return
}
if ty.is_number() {
self.ret_expr = "return JS;".to_string();
return
}
self.ret_expr = match *ty {
Descriptor::Boolean => "return JS ? 1 : 0;".to_string(),
Descriptor::Anyref => {
self.cx.expose_add_heap_object();
"return addHeapObject(JS);".to_string()
}
_ => panic!("unimplemented return from JS to Rust: {:?}", ty),
}
}
pub fn finish(&self, invoc: &str) -> String {
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(&indent(&self.prelude));
let mut invoc = self.ret_expr.replace(
"JS",
&format!("{}({})", invoc, self.js_arguments.join(", ")),
);
if self.catch {
let catch = "\
const view = getUint32Memory();\n\
view[exnptr / 4] = 1;\n\
view[exnptr / 4 + 1] = addHeapObject(e);\n\
";
invoc = format!("\
try {{\n\
{}\
}} catch (e) {{\n\
{}\
}}\
", indent(&invoc), indent(catch));
};
if self.finally.len() > 0 {
invoc = format!("\
try {{\n\
{}\
}} finally {{\n\
{}\
}}\
", indent(&invoc), indent(&self.finally));
}
ret.push_str(&indent(&invoc));
ret.push_str("}\n");
return 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
}
}