Initial support for closures

This commit starts wasm-bindgen down the path of supporting closures. We
discussed this at the recent Rust All-Hands but I ended up needing to pretty
significantly scale back the ambitions of what closures are supported. This
commit is just the initial support and provides only a small amount of support
but will hopefully provide a good basis for future implementations.

Specifically this commit adds support for passing `&Fn(...)` to an *imported
function*, but nothing elese. The `&Fn` type can have any lifetime and the JS
object is invalidated as soon as the import returns. The arguments and return
value of `Fn` must currently implement the `WasmAbi` trait, aka they can't
require any conversions like strings/types/etc.

I'd like to soon expand this to `&mut FnMut` as well as `'static` closures that
can be passed around for a long time in JS, but for now I'm putting that off
until later. I'm not currently sure how to implement richer argument types, but
hopefully that can be figured out at some point!
This commit is contained in:
Alex Crichton
2018-04-04 08:24:19 -07:00
parent 9b46c8831d
commit c0cad447c1
8 changed files with 347 additions and 64 deletions

View File

@ -19,6 +19,7 @@ pub struct Context<'a> {
pub custom_type_names: HashMap<u32, String>,
pub imported_names: HashSet<String>,
pub exported_classes: HashMap<String, ExportedClass>,
pub function_table_needed: bool,
}
#[derive(Default)]
@ -246,6 +247,7 @@ impl<'a> Context<'a> {
);
self.unexport_unused_internal_exports();
self.export_table();
(js, self.typescript.clone())
}
@ -313,6 +315,22 @@ impl<'a> Context<'a> {
}
}
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);
@ -1404,6 +1422,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
let mut abi_args = Vec::new();
let mut extra = String::new();
let mut finally = String::new();
let mut next_global = 0;
for (i, arg) in import.function.arguments.iter().enumerate() {
@ -1419,6 +1438,30 @@ impl<'a, 'b> SubContext<'a, 'b> {
self.cx.expose_get_object();
format!("getObject(arg{})", i)
}
shared::TYPE_STACK_FUNC0 |
shared::TYPE_STACK_FUNC1 => {
let nargs = *arg - shared::TYPE_STACK_FUNC0;
let args = (0..nargs)
.map(|i| format!("arg{}", i))
.collect::<Vec<_>>()
.join(", ");
self.cx.expose_get_global_argument();
self.cx.function_table_needed = true;
let sep = if nargs == 0 {""} else {","};
extra.push_str(&format!("
let cb{0} = function({args}) {{
return this.f(this.a, this.b {sep} {args});
}};
cb{0}.f = wasm.__wbg_function_table.get(arg{0});
cb{0}.a = getGlobalArgument({next_global});
cb{0}.b = getGlobalArgument({next_global} + 1);
", i, next_global = next_global, args = args, sep = sep));
next_global += 2;
finally.push_str(&format!("
cb{0}.a = cb{0}.b = 0;
", i));
format!("cb{0}.bind(cb{0})", i)
}
other => {
match VectorType::from(other) {
Some(ty) => {
@ -1585,6 +1628,17 @@ impl<'a, 'b> SubContext<'a, 'b> {
} else {
invoc
};
let invoc = if finally.len() > 0 {
format!("
try {{
{}
}} finally {{
{}
}}
", invoc, finally)
} else {
invoc
};
dst.push_str(&abi_args.join(", "));
dst.push_str(") {\n");

View File

@ -94,6 +94,7 @@ impl Bindgen {
exported_classes: Default::default(),
config: &self,
module: &mut module,
function_table_needed: false,
};
for program in programs.iter() {
cx.add_custom_type_names(program);