mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-26 03:24:13 +03:00
Get imports working in a basic capacity
This commit is contained in:
parent
1ffcb90d2d
commit
eda9beae25
26
README.md
26
README.md
@ -16,6 +16,7 @@ Notable features of this project includes:
|
||||
* Exposing Rust structs to JS as classes
|
||||
* Exposing Rust functions to JS
|
||||
* Managing arguments between JS/Rust (strings, numbers, classes, etc)
|
||||
* Importing JS functions with richer types (strings)
|
||||
|
||||
Planned features include:
|
||||
|
||||
@ -241,13 +242,18 @@ wasm_bindgen! {
|
||||
contents: u32,
|
||||
}
|
||||
|
||||
extern "JS" {
|
||||
fn bar_on_reset(to: &str);
|
||||
}
|
||||
|
||||
impl Bar {
|
||||
pub fn from_str(s: &str) -> Foo {
|
||||
pub fn from_str(s: &str) -> Bar {
|
||||
Bar { contents: s.parse().unwrap_or(0) }
|
||||
}
|
||||
|
||||
pub fn reset(&mut self, s: &str) {
|
||||
if let Ok(n) = s.parse() {
|
||||
bar_on_reset(s);
|
||||
self.contents = n;
|
||||
}
|
||||
}
|
||||
@ -273,7 +279,15 @@ and this can be worked with similarly to above with:
|
||||
|
||||
fetch("hello.wasm")
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(instantiate)
|
||||
.then(bytes => {
|
||||
return instantiate(bytes, {
|
||||
env: {
|
||||
bar_on_reset(s) {
|
||||
console.log(`an instance of bar was reset to ${s}`);
|
||||
},
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(mod => {
|
||||
assertEq(mod.concat('a', 'b'), 'ab');
|
||||
|
||||
@ -310,9 +324,11 @@ and this can be worked with similarly to above with:
|
||||
Here this section will attempt to be a reference for the various features
|
||||
implemented in this project.
|
||||
|
||||
In the `wasm_bindgen!` macro you can have three items: functions, structs, and
|
||||
impls. Impls can only contain functions. No lifetime parameters or type
|
||||
parameters are allowed on any of these types.
|
||||
In the `wasm_bindgen!` macro you can have four items: functions, structs,
|
||||
impls, and foreign mdoules. Impls can only contain functions. No lifetime
|
||||
parameters or type parameters are allowed on any of these types. Foreign
|
||||
modules must have the `"JS"` abi and currently only allow integer/string
|
||||
arguments and integer return values.
|
||||
|
||||
All structs referenced through arguments to functions should be defined in the
|
||||
macro itself. Arguments allowed are:
|
||||
|
@ -2,15 +2,16 @@ use shared;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Js {
|
||||
pub expose_global_memory: bool,
|
||||
pub expose_global_exports: bool,
|
||||
pub expose_get_string_from_wasm: bool,
|
||||
pub expose_pass_string_to_wasm: bool,
|
||||
pub expose_assert_num: bool,
|
||||
pub expose_assert_class: bool,
|
||||
pub expose_token: bool,
|
||||
pub exports: Vec<(String, String)>,
|
||||
pub classes: Vec<String>,
|
||||
expose_global_memory: bool,
|
||||
expose_global_exports: bool,
|
||||
expose_get_string_from_wasm: bool,
|
||||
expose_pass_string_to_wasm: bool,
|
||||
expose_assert_num: bool,
|
||||
expose_assert_class: bool,
|
||||
expose_token: bool,
|
||||
exports: Vec<(String, String)>,
|
||||
classes: Vec<String>,
|
||||
imports: Vec<String>,
|
||||
pub nodejs: bool,
|
||||
}
|
||||
|
||||
@ -23,6 +24,9 @@ impl Js {
|
||||
for s in program.structs.iter() {
|
||||
self.generate_struct(s);
|
||||
}
|
||||
for s in program.imports.iter() {
|
||||
self.generate_import(s);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_free_function(&mut self, func: &shared::Function) {
|
||||
@ -160,7 +164,14 @@ impl Js {
|
||||
}
|
||||
Some(&shared::Type::String) => {
|
||||
self.expose_get_string_from_wasm = true;
|
||||
format!("return getStringFromWasm(ret);")
|
||||
self.expose_global_exports = true;
|
||||
format!("
|
||||
const ptr = exports.__wbindgen_boxed_str_ptr(ret);
|
||||
const len = exports.__wbindgen_boxed_str_len(ret);
|
||||
const realRet = getStringFromWasm(ptr, len);
|
||||
exports.__wbindgen_boxed_str_free(ret);
|
||||
return realRet;
|
||||
")
|
||||
}
|
||||
};
|
||||
dst.push_str(") {\n ");
|
||||
@ -186,8 +197,47 @@ impl Js {
|
||||
return dst
|
||||
}
|
||||
|
||||
pub fn generate_import(&mut self, import: &shared::Function) {
|
||||
let mut dst = String::new();
|
||||
|
||||
dst.push_str(&format!("const {0} = imports.env.{0};\n", import.name));
|
||||
dst.push_str(&format!("imports.env.{0} = function {0}_shim(", import.name));
|
||||
|
||||
let mut invocation = String::new();
|
||||
for (i, arg) in import.arguments.iter().enumerate() {
|
||||
if invocation.len() > 0 {
|
||||
invocation.push_str(", ");
|
||||
}
|
||||
if i > 0 {
|
||||
dst.push_str(", ");
|
||||
}
|
||||
match *arg {
|
||||
shared::Type::Number => {
|
||||
invocation.push_str(&format!("arg{}", i));
|
||||
dst.push_str(&format!("arg{}", i));
|
||||
}
|
||||
shared::Type::BorrowedStr => {
|
||||
self.expose_get_string_from_wasm = true;
|
||||
invocation.push_str(&format!("getStringFromWasm(ptr{0}, len{0})", i));
|
||||
dst.push_str(&format!("ptr{0}, len{0}", i));
|
||||
}
|
||||
shared::Type::String |
|
||||
shared::Type::ByRef(_) |
|
||||
shared::Type::ByMutRef(_) |
|
||||
shared::Type::ByValue(_) => {
|
||||
panic!("unsupported type in import");
|
||||
}
|
||||
}
|
||||
}
|
||||
dst.push_str(") {\n");
|
||||
dst.push_str(&format!("return {}({});\n}}", import.name, invocation));
|
||||
|
||||
self.imports.push(dst);
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
let mut globals = String::new();
|
||||
let mut real_globals = String::new();
|
||||
if self.expose_global_memory ||
|
||||
self.expose_pass_string_to_wasm ||
|
||||
self.expose_get_string_from_wasm
|
||||
@ -247,27 +297,22 @@ impl Js {
|
||||
}
|
||||
}
|
||||
if self.expose_get_string_from_wasm {
|
||||
real_globals.push_str("let getStringFromWasm = null;\n");
|
||||
if self.nodejs {
|
||||
globals.push_str("
|
||||
function getStringFromWasm(ptr) {
|
||||
getStringFromWasm = function getStringFromWasm(ptr, len) {
|
||||
const mem = new Uint8Array(memory.buffer);
|
||||
const data = exports.__wbindgen_boxed_str_ptr(ptr);
|
||||
const len = exports.__wbindgen_boxed_str_len(ptr);
|
||||
const buf = Buffer.from(mem.slice(data, data + len));
|
||||
const buf = Buffer.from(mem.slice(ptr, ptr + len));
|
||||
const ret = buf.toString();
|
||||
exports.__wbindgen_boxed_str_free(ptr);
|
||||
return ret;
|
||||
}
|
||||
");
|
||||
} else {
|
||||
globals.push_str("
|
||||
function getStringFromWasm(ptr) {
|
||||
getStringFromWasm = function getStringFromWasm(ptr, len) {
|
||||
const mem = new Uint8Array(memory.buffer);
|
||||
const data = exports.__wbindgen_boxed_str_ptr(ptr);
|
||||
const len = exports.__wbindgen_boxed_str_len(ptr);
|
||||
const slice = mem.slice(data, data + len);
|
||||
const slice = mem.slice(ptr, ptr + len);
|
||||
const ret = new TextDecoder('utf-8').decode(slice);
|
||||
exports.__wbindgen_boxed_str_free(ptr);
|
||||
return ret;
|
||||
}
|
||||
");
|
||||
@ -295,15 +340,22 @@ impl Js {
|
||||
exports.push_str(body);
|
||||
exports.push_str(";\n");
|
||||
}
|
||||
let mut imports = String::new();
|
||||
for import in self.imports.iter() {
|
||||
imports.push_str(import);
|
||||
imports.push_str("\n");
|
||||
}
|
||||
format!("
|
||||
{}
|
||||
function xform(obj) {{
|
||||
{}
|
||||
{}
|
||||
return obj;
|
||||
}}
|
||||
export function instantiate(bytes, imports) {{
|
||||
{}
|
||||
return WebAssembly.instantiate(bytes, imports).then(xform);
|
||||
}}
|
||||
", globals, exports)
|
||||
", real_globals, globals, exports, imports)
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ fn extract_program(module: &mut Module) -> shared::Program {
|
||||
let mut ret = shared::Program {
|
||||
structs: Vec::new(),
|
||||
free_functions: Vec::new(),
|
||||
imports: Vec::new(),
|
||||
};
|
||||
let data = match data {
|
||||
Some(data) => data,
|
||||
@ -125,9 +126,10 @@ fn extract_program(module: &mut Module) -> shared::Program {
|
||||
Ok(f) => f,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let shared::Program { structs, free_functions } = p;
|
||||
let shared::Program { structs, free_functions, imports } = p;
|
||||
ret.structs.extend(structs);
|
||||
ret.free_functions.extend(free_functions);
|
||||
ret.imports.extend(imports);
|
||||
}
|
||||
data.entries_mut().remove(i);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use wasm_bindgen_shared as shared;
|
||||
pub struct Program {
|
||||
pub structs: Vec<Struct>,
|
||||
pub free_functions: Vec<Function>,
|
||||
pub imports: Vec<Import>,
|
||||
}
|
||||
|
||||
pub struct Function {
|
||||
@ -13,6 +14,14 @@ pub struct Function {
|
||||
pub ret: Option<Type>,
|
||||
}
|
||||
|
||||
pub struct Import {
|
||||
pub function: Function,
|
||||
pub decl: Box<syn::FnDecl>,
|
||||
pub ident: syn::Ident,
|
||||
pub vis: syn::Visibility,
|
||||
pub attrs: Vec<syn::Attribute>,
|
||||
}
|
||||
|
||||
pub enum Type {
|
||||
Integer(syn::Ident),
|
||||
BorrowedStr,
|
||||
@ -62,10 +71,36 @@ impl Program {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_foreign_mod(&mut self, f: &syn::ItemForeignMod) {
|
||||
match f.abi.kind {
|
||||
syn::AbiKind::Named(ref l) if l.to_string() == "\"JS\"" => {}
|
||||
_ => panic!("only foreign mods with the `JS` ABI are allowed"),
|
||||
}
|
||||
for item in f.items.iter() {
|
||||
self.push_foreign_item(item);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_foreign_item(&mut self, f: &syn::ForeignItem) {
|
||||
let f = match *f {
|
||||
syn::ForeignItem::Fn(ref f) => f,
|
||||
_ => panic!("only foreign functions allowed for now, not statics"),
|
||||
};
|
||||
|
||||
self.imports.push(Import {
|
||||
attrs: f.attrs.clone(),
|
||||
vis: f.vis.clone(),
|
||||
decl: f.decl.clone(),
|
||||
ident: f.ident.clone(),
|
||||
function: Function::from_decl(f.ident, &f.decl),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn shared(&self) -> shared::Program {
|
||||
shared::Program {
|
||||
structs: self.structs.iter().map(|s| s.shared()).collect(),
|
||||
free_functions: self.free_functions.iter().map(|s| s.shared()).collect(),
|
||||
imports: self.imports.iter().map(|i| i.function.shared()).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,14 +126,18 @@ impl Function {
|
||||
panic!("can only bindgen Rust ABI functions")
|
||||
}
|
||||
|
||||
if input.decl.variadic {
|
||||
Function::from_decl(input.ident, &input.decl)
|
||||
}
|
||||
|
||||
pub fn from_decl(name: syn::Ident, decl: &syn::FnDecl) -> Function {
|
||||
if decl.variadic {
|
||||
panic!("can't bindgen variadic functions")
|
||||
}
|
||||
if input.decl.generics.params.len() > 0 {
|
||||
if decl.generics.params.len() > 0 {
|
||||
panic!("can't bindgen functions with lifetime or type parameters")
|
||||
}
|
||||
|
||||
let arguments = input.decl.inputs.iter()
|
||||
let arguments = decl.inputs.iter()
|
||||
.map(|i| i.into_item())
|
||||
.map(|arg| {
|
||||
match *arg {
|
||||
@ -109,12 +148,12 @@ impl Function {
|
||||
.map(|arg| Type::from(&arg.ty))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ret = match input.decl.output {
|
||||
let ret = match decl.output {
|
||||
syn::ReturnType::Default => None,
|
||||
syn::ReturnType::Type(ref t, _) => Some(Type::from(t)),
|
||||
};
|
||||
|
||||
Function { name: input.ident, arguments, ret }
|
||||
Function { name, arguments, ret }
|
||||
}
|
||||
|
||||
pub fn free_function_export_name(&self) -> syn::Lit {
|
||||
@ -153,21 +192,22 @@ impl Function {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_path_ident(path: &syn::Path) -> syn::Ident {
|
||||
if path.leading_colon.is_some() {
|
||||
panic!("unsupported leading colon in path")
|
||||
}
|
||||
if path.segments.len() != 1 {
|
||||
panic!("unsupported path that needs name resolution")
|
||||
}
|
||||
match path.segments.get(0).item().arguments {
|
||||
syn::PathArguments::None => {}
|
||||
_ => panic!("unsupported path that has path arguments")
|
||||
}
|
||||
path.segments.get(0).item().ident
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn from(ty: &syn::Type) -> Type {
|
||||
let extract_path_ident = |path: &syn::Path| {
|
||||
if path.leading_colon.is_some() {
|
||||
panic!("unsupported leading colon in path")
|
||||
}
|
||||
if path.segments.len() != 1 {
|
||||
panic!("unsupported path that needs name resolution")
|
||||
}
|
||||
match path.segments.get(0).item().arguments {
|
||||
syn::PathArguments::None => {}
|
||||
_ => panic!("unsupported path that has path arguments")
|
||||
}
|
||||
path.segments.get(0).item().ident
|
||||
};
|
||||
match *ty {
|
||||
syn::Type::Reference(ref r) => {
|
||||
if r.lifetime.is_some() {
|
||||
|
@ -35,6 +35,7 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
||||
let mut program = ast::Program {
|
||||
structs: Vec::new(),
|
||||
free_functions: Vec::new(),
|
||||
imports: Vec::new(),
|
||||
};
|
||||
|
||||
// Translate all input items into our own internal representation (the `ast`
|
||||
@ -54,9 +55,12 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
program.structs.push(s);
|
||||
}
|
||||
syn::Item::Impl(ref s) => {
|
||||
syn::Item::Impl(ref i) => {
|
||||
item.to_tokens(&mut ret);
|
||||
program.push_impl(s);
|
||||
program.push_impl(i);
|
||||
}
|
||||
syn::Item::ForeignMod(ref f) => {
|
||||
program.push_foreign_mod(f);
|
||||
}
|
||||
_ => panic!("unexpected item in bindgen macro"),
|
||||
}
|
||||
@ -70,6 +74,9 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
||||
for s in program.structs.iter() {
|
||||
bindgen_struct(s, &mut ret);
|
||||
}
|
||||
for i in program.imports.iter() {
|
||||
bindgen_import(i, &mut ret);
|
||||
}
|
||||
|
||||
// Finally generate a static which will eventually be what lives in a custom
|
||||
// section of the wasm executable. For now it's just a plain old static, but
|
||||
@ -94,7 +101,7 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
|
||||
*#generated_static_value;
|
||||
}).to_tokens(&mut ret);
|
||||
|
||||
// println!("{}", ret);
|
||||
println!("{}", ret);
|
||||
|
||||
ret.into()
|
||||
}
|
||||
@ -345,3 +352,97 @@ impl ToTokens for Receiver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
|
||||
let vis = &import.vis;
|
||||
let ret = &import.decl.output;
|
||||
let name = &import.ident;
|
||||
let fn_token = &import.decl.fn_token;
|
||||
let arguments = &import.decl.inputs;
|
||||
|
||||
let mut abi_argument_names = Vec::new();
|
||||
let mut abi_arguments = Vec::new();
|
||||
let mut arg_conversions = Vec::new();
|
||||
let ret_ident = syn::Ident::from("_ret");
|
||||
|
||||
let names = import.decl.inputs
|
||||
.iter()
|
||||
.map(|i| i.into_item())
|
||||
.map(|arg| {
|
||||
match *arg {
|
||||
syn::FnArg::Captured(ref c) => c,
|
||||
_ => panic!("arguments cannot be `self` or ignored"),
|
||||
}
|
||||
})
|
||||
.map(|arg| {
|
||||
match arg.pat {
|
||||
syn::Pat::Ident(syn::PatIdent {
|
||||
mode: syn::BindingMode::ByValue(_),
|
||||
ident,
|
||||
subpat: None,
|
||||
..
|
||||
}) => {
|
||||
ident
|
||||
}
|
||||
_ => panic!("unsupported pattern in foreign function"),
|
||||
}
|
||||
});
|
||||
|
||||
for (ty, name) in import.function.arguments.iter().zip(names) {
|
||||
match *ty {
|
||||
ast::Type::Integer(i) => {
|
||||
abi_argument_names.push(name);
|
||||
abi_arguments.push(my_quote! { #name: #i });
|
||||
arg_conversions.push(my_quote! {});
|
||||
}
|
||||
ast::Type::BorrowedStr => {
|
||||
let ptr = syn::Ident::from(format!("{}_ptr", name));
|
||||
let len = syn::Ident::from(format!("{}_len", name));
|
||||
abi_argument_names.push(ptr);
|
||||
abi_argument_names.push(len);
|
||||
abi_arguments.push(my_quote! { #ptr: *const u8 });
|
||||
abi_arguments.push(my_quote! { #len: usize });
|
||||
arg_conversions.push(my_quote! {
|
||||
let #ptr = #name.as_ptr();
|
||||
let #len = #name.len();
|
||||
});
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
let abi_ret;
|
||||
let convert_ret;
|
||||
match import.function.ret {
|
||||
Some(ast::Type::Integer(i)) => {
|
||||
abi_ret = my_quote! { #i };
|
||||
convert_ret = my_quote! { #ret_ident };
|
||||
}
|
||||
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"),
|
||||
Some(ast::Type::String) => panic!("can't return a string in foreign functions"),
|
||||
Some(ast::Type::ByValue(_)) => panic!("can't return a struct in a foreign function"),
|
||||
None => {
|
||||
abi_ret = my_quote! { () };
|
||||
convert_ret = my_quote! {};
|
||||
}
|
||||
}
|
||||
|
||||
(quote! {
|
||||
#vis #fn_token #name(#arguments) #ret {
|
||||
extern {
|
||||
fn #name(#(#abi_arguments),*) -> #abi_ret;
|
||||
}
|
||||
unsafe {
|
||||
#(#arg_conversions)*
|
||||
let #ret_ident = #name(#(#abi_argument_names),*);
|
||||
#convert_ret
|
||||
}
|
||||
}
|
||||
}).to_tokens(tokens);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ extern crate serde_derive;
|
||||
pub struct Program {
|
||||
pub structs: Vec<Struct>,
|
||||
pub free_functions: Vec<Function>,
|
||||
pub imports: Vec<Function>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
59
tests/imports.rs
Normal file
59
tests/imports.rs
Normal file
@ -0,0 +1,59 @@
|
||||
extern crate test_support;
|
||||
|
||||
#[test]
|
||||
fn simple() {
|
||||
test_support::project()
|
||||
.file("src/lib.rs", r#"
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate wasm_bindgen;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
wasm_bindgen! {
|
||||
extern "JS" {
|
||||
fn foo(s: &str);
|
||||
fn another(a: u32) -> i32;
|
||||
}
|
||||
pub fn bar(s: &str) {
|
||||
foo(s);
|
||||
}
|
||||
pub fn another_thunk(a: u32) -> i32 {
|
||||
another(a)
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
import * as assert from "assert";
|
||||
|
||||
let ARG = null;
|
||||
let ANOTHER_ARG = null;
|
||||
|
||||
export const imports = {
|
||||
env: {
|
||||
foo(s) {
|
||||
assert.strictEqual(ARG, null);
|
||||
assert.strictEqual(s, "foo");
|
||||
ARG = s;
|
||||
},
|
||||
another(s) {
|
||||
assert.strictEqual(ANOTHER_ARG, null);
|
||||
assert.strictEqual(s, 21);
|
||||
ANOTHER_ARG = s;
|
||||
return 35;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function test(wasm) {
|
||||
assert.strictEqual(ARG, null);
|
||||
wasm.bar("foo");
|
||||
assert.strictEqual(ARG, "foo");
|
||||
|
||||
assert.strictEqual(ANOTHER_ARG, null);
|
||||
assert.strictEqual(wasm.another_thunk(21), 35);
|
||||
assert.strictEqual(ANOTHER_ARG, 21);
|
||||
}
|
||||
"#)
|
||||
.test();
|
||||
}
|
Loading…
Reference in New Issue
Block a user