Support passing JS objects through Rust

This commit is contained in:
Alex Crichton
2017-12-19 09:25:41 -08:00
parent eda9beae25
commit 946e5317fe
9 changed files with 436 additions and 13 deletions

View File

@@ -55,6 +55,8 @@ pub fn project() -> Project {
out.instantiate(wasm, test.imports).then(m => {
test.test(m);
if (m.assertHeapAndStackEmpty)
m.assertHeapAndStackEmpty();
}).catch(function(error) {
console.error(error);
process.exit(1);
@@ -146,6 +148,7 @@ impl Project {
let obj = cli::Bindgen::new()
.input_path(&out)
.nodejs(true)
.debug(true)
.generate()
.expect("failed to run bindgen");
obj.write_js_to(root.join("out.js")).expect("failed to write js");

View File

@@ -9,10 +9,12 @@ pub struct Js {
expose_assert_num: bool,
expose_assert_class: bool,
expose_token: bool,
expose_objects: bool,
exports: Vec<(String, String)>,
classes: Vec<String>,
imports: Vec<String>,
pub nodejs: bool,
pub debug: bool,
}
impl Js {
@@ -149,11 +151,31 @@ impl Js {
", i = i, arg = name, struct_ = s));
pass(&format!("ptr{}", i));
}
shared::Type::JsObject => {
self.expose_objects = true;
arg_conversions.push_str(&format!("\
const idx{i} = addHeapObject({arg});
", i = i, arg = name));
pass(&format!("idx{}", i));
}
shared::Type::JsObjectRef => {
self.expose_objects = true;
arg_conversions.push_str(&format!("\
const idx{i} = addBorrowedObject({arg});
", i = i, arg = name));
destructors.push_str("popBorrowedObject();\n");
pass(&format!("idx{}", i));
}
}
}
let convert_ret = match ret {
None |
Some(&shared::Type::Number) => format!("return ret;"),
Some(&shared::Type::JsObject) => {
self.expose_objects = true;
format!("return takeObject(ret);")
}
Some(&shared::Type::JsObjectRef) |
Some(&shared::Type::BorrowedStr) |
Some(&shared::Type::ByMutRef(_)) |
Some(&shared::Type::ByRef(_)) => panic!(),
@@ -221,6 +243,16 @@ impl Js {
invocation.push_str(&format!("getStringFromWasm(ptr{0}, len{0})", i));
dst.push_str(&format!("ptr{0}, len{0}", i));
}
shared::Type::JsObject => {
self.expose_objects = true;
invocation.push_str(&format!("takeObject(arg{})", i));
dst.push_str(&format!("arg{}", i));
}
shared::Type::JsObjectRef => {
self.expose_objects = true;
invocation.push_str(&format!("getObject(arg{})", i));
dst.push_str(&format!("arg{}", i));
}
shared::Type::String |
shared::Type::ByRef(_) |
shared::Type::ByMutRef(_) |
@@ -235,7 +267,7 @@ impl Js {
self.imports.push(dst);
}
pub fn to_string(&self) -> String {
pub fn to_string(&mut self) -> String {
let mut globals = String::new();
let mut real_globals = String::new();
if self.expose_global_memory ||
@@ -328,6 +360,92 @@ impl Js {
");
}
if self.expose_objects {
real_globals.push_str("
let stack = [];
let slab = [];
let slab_next = 0;
function addHeapObject(obj) {
if (slab_next == slab.length) {
slab.push(slab.length + 1);
}
const idx = slab_next;
slab_next = slab[idx];
slab[idx] = { obj, cnt: 1 };
return idx << 1;
}
function addBorrowedObject(obj) {
stack.push(obj);
return ((stack.length - 1) << 1) | 1;
}
function popBorrowedObject() {
stack.pop();
}
function getObject(idx) {
if (idx & 1 == 1) {
return stack[idx >> 1];
} else {
return slab[idx >> 1].obj;
}
}
function takeObject(idx) {
const ret = getObject(idx);
dropRef(idx);
return ret;
}
function cloneRef(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
slab[idx >> 1].cnt += 1;
return idx;
}
function dropRef(idx) {
if (idx & 1 == 1)
throw new Error('cannot drop ref of stack objects');
// Decrement our refcount, but if it's still larger than one
// keep going
let obj = slab[idx >> 1];
obj.cnt -= 1;
if (obj.cnt > 0)
return;
// If we hit 0 then free up our space in the slab
slab[idx >> 1] = slab_next;
slab_next = idx >> 1;
}
");
if self.debug {
self.exports.push(
(
"assertHeapAndStackEmpty".to_string(),
"function() {
if (stack.length > 0)
throw new Error('stack is not empty');
for (let i = 0; i < slab.length; i++) {
if (typeof(slab[i]) !== 'number')
throw new Error('slab is not empty');
}
}".to_string(),
)
);
}
}
let mut exports = String::new();
for class in self.classes.iter() {
exports.push_str(class);
@@ -345,6 +463,13 @@ impl Js {
imports.push_str(import);
imports.push_str("\n");
}
if self.expose_objects {
imports.push_str("
imports.env.__wasm_bindgen_object_clone_ref = cloneRef;
imports.env.__wasm_bindgen_object_drop_ref = dropRef;
");
}
format!("
{}
function xform(obj) {{

View File

@@ -16,12 +16,14 @@ mod js;
pub struct Bindgen {
path: Option<PathBuf>,
nodejs: bool,
debug: bool,
}
pub struct Object {
module: Module,
program: shared::Program,
nodejs: bool,
debug: bool,
}
impl Bindgen {
@@ -29,6 +31,7 @@ impl Bindgen {
Bindgen {
path: None,
nodejs: false,
debug: false,
}
}
@@ -42,6 +45,11 @@ impl Bindgen {
self
}
pub fn debug(&mut self, debug: bool) -> &mut Bindgen {
self.debug = debug;
self
}
pub fn generate(&mut self) -> Result<Object, Error> {
let input = match self.path {
Some(ref path) => path,
@@ -55,6 +63,7 @@ impl Bindgen {
module,
program,
nodejs: self.nodejs,
debug: self.debug,
})
}
}
@@ -89,6 +98,7 @@ impl Object {
pub fn generate_js(&self) -> String {
let mut js = js::Js::default();
js.nodejs = self.nodejs;
js.debug = self.debug;
js.generate_program(&self.program);
js.to_string()
}

View File

@@ -29,6 +29,8 @@ pub enum Type {
ByValue(syn::Ident),
ByRef(syn::Ident),
ByMutRef(syn::Ident),
JsObject,
JsObjectRef,
}
pub struct Struct {
@@ -227,6 +229,10 @@ impl Type {
}
Type::BorrowedStr
}
"JsObject" if !mutable => Type::JsObjectRef,
"JsObject" if mutable => {
panic!("can't have mutable js object refs")
}
_ if mutable => Type::ByMutRef(ident),
_ => Type::ByRef(ident),
}
@@ -250,6 +256,7 @@ impl Type {
Type::Integer(ident)
}
"String" => Type::String,
"JsObject" => Type::JsObject,
_ => Type::ByValue(ident),
}
}
@@ -265,6 +272,8 @@ impl Type {
Type::ByValue(n) => shared::Type::ByValue(n.to_string()),
Type::ByRef(n) => shared::Type::ByRef(n.to_string()),
Type::ByMutRef(n) => shared::Type::ByMutRef(n.to_string()),
Type::JsObject => shared::Type::JsObject,
Type::JsObjectRef => shared::Type::JsObjectRef,
}
}
}

View File

@@ -101,7 +101,7 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
*#generated_static_value;
}).to_tokens(&mut ret);
println!("{}", ret);
// println!("{}", ret);
ret.into()
}
@@ -241,6 +241,21 @@ fn bindgen(export_name: &syn::Lit,
let #ident = &mut *#ident;
});
}
ast::Type::JsObject => {
args.push(my_quote! { #ident: u32 });
arg_conversions.push(my_quote! {
let #ident = ::wasm_bindgen::JsObject::__from_idx(#ident);
});
}
ast::Type::JsObjectRef => {
args.push(my_quote! { #ident: u32 });
arg_conversions.push(my_quote! {
let #ident = ::std::mem::ManuallyDrop::new(
::wasm_bindgen::JsObject::__from_idx(#ident)
);
let #ident = &*#ident;
});
}
}
converted_arguments.push(my_quote! { #ident });
}
@@ -265,6 +280,15 @@ fn bindgen(export_name: &syn::Lit,
Box::into_raw(Box::new(::std::cell::RefCell::new(#ret)))
};
}
Some(&ast::Type::JsObject) => {
ret_ty = my_quote! { -> u32 };
convert_ret = my_quote! {
::wasm_bindgen::JsObject::__into_idx(#ret)
};
}
Some(&ast::Type::JsObjectRef) => {
panic!("can't return a borrowed ref");
}
None => {
ret_ty = my_quote! {};
convert_ret = my_quote! {};
@@ -407,11 +431,25 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
let #len = #name.len();
});
}
ast::Type::JsObject => {
abi_argument_names.push(name);
abi_arguments.push(my_quote! { #name: u32 });
arg_conversions.push(my_quote! {
let #name = ::wasm_bindgen::JsObject::__into_idx(#name);
});
}
ast::Type::JsObjectRef => {
abi_argument_names.push(name);
abi_arguments.push(my_quote! { #name: u32 });
arg_conversions.push(my_quote! {
let #name = ::wasm_bindgen::JsObject::__get_idx(#name);
});
}
ast::Type::String => panic!("can't use `String` in foreign functions"),
ast::Type::ByValue(_name) |
ast::Type::ByRef(_name) |
ast::Type::ByMutRef(_name) => {
panic!("can't use strct types in foreign functions yet");
panic!("can't use struct types in foreign functions yet");
}
}
}
@@ -422,6 +460,13 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
abi_ret = my_quote! { #i };
convert_ret = my_quote! { #ret_ident };
}
Some(ast::Type::JsObject) => {
abi_ret = my_quote! { u32 };
convert_ret = my_quote! {
::wasm_bindgen::JsObject::__from_idx(#ret_ident)
};
}
Some(ast::Type::JsObjectRef) => panic!("can't return a borrowed ref"),
Some(ast::Type::BorrowedStr) => panic!("can't return a borrowed string"),
Some(ast::Type::ByRef(_)) => panic!("can't return a borrowed ref"),
Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"),

View File

@@ -63,6 +63,8 @@ pub enum Type {
ByValue(String),
ByRef(String),
ByMutRef(String),
JsObject,
JsObjectRef,
}
impl Type {