576 lines
20 KiB
Rust
Raw Normal View History

#![recursion_limit = "128"]
2017-12-14 19:31:01 -08:00
#![feature(proc_macro)]
2018-02-05 14:24:25 -08:00
#[macro_use]
2017-12-14 19:31:01 -08:00
extern crate syn;
2017-12-14 21:55:21 -08:00
#[macro_use]
2017-12-14 19:31:01 -08:00
extern crate quote;
extern crate proc_macro;
extern crate proc_macro2;
extern crate serde_json;
extern crate wasm_bindgen_shared as shared;
2017-12-14 19:31:01 -08:00
2017-12-14 21:55:21 -08:00
use std::sync::atomic::*;
2017-12-14 19:31:01 -08:00
use proc_macro::TokenStream;
2018-01-08 10:42:01 -08:00
use proc_macro2::{Span, TokenNode, Delimiter, TokenTree};
2017-12-14 19:31:01 -08:00
use quote::{Tokens, ToTokens};
macro_rules! my_quote {
($($t:tt)*) => (quote_spanned!(Span::call_site() => $($t)*))
}
2017-12-14 19:31:01 -08:00
mod ast;
#[proc_macro]
pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
// Parse the input as a list of Rust items, reusing the `syn::File` parser.
2018-02-05 14:24:25 -08:00
let file = syn::parse::<ast::File>(input)
2017-12-14 19:31:01 -08:00
.expect("expected a set of valid Rust items");
let mut ret = Tokens::new();
2017-12-18 12:39:14 -08:00
let mut program = ast::Program {
structs: Vec::new(),
free_functions: Vec::new(),
imports: Vec::new(),
2018-02-05 14:24:25 -08:00
imported_structs: Vec::new(),
2017-12-18 12:39:14 -08:00
};
// Translate all input items into our own internal representation (the `ast`
// module). We'll be panicking here on anything that we can't process
2017-12-14 19:31:01 -08:00
for item in file.items.iter() {
2018-02-05 14:24:25 -08:00
let item = match *item {
ast::MyItem::ExternClass(ref c) => {
program.push_extern_class(c);
continue
}
ast::MyItem::Normal(ref item) => item,
};
2017-12-14 19:31:01 -08:00
match *item {
2017-12-18 12:39:14 -08:00
syn::Item::Fn(ref f) => {
item.to_tokens(&mut ret);
2017-12-18 12:39:14 -08:00
program.free_functions.push(ast::Function::from(f));
}
syn::Item::Struct(ref s) => {
item.to_tokens(&mut ret);
2017-12-18 12:39:14 -08:00
let s = ast::Struct::from(s);
if program.structs.iter().any(|a| a.name == s.name) {
panic!("redefinition of struct: {}", s.name);
}
program.structs.push(s);
}
syn::Item::Impl(ref i) => {
item.to_tokens(&mut ret);
program.push_impl(i);
}
syn::Item::ForeignMod(ref f) => {
program.push_foreign_mod(f);
}
2017-12-14 19:31:01 -08:00
_ => panic!("unexpected item in bindgen macro"),
}
}
// Generate wrappers for all the items that we've found
2017-12-18 12:39:14 -08:00
for function in program.free_functions.iter() {
bindgen_fn(function, &mut ret);
}
for s in program.structs.iter() {
bindgen_struct(s, &mut ret);
2017-12-18 12:39:14 -08:00
}
for i in program.imports.iter() {
bindgen_import(i, &mut ret);
}
2018-02-05 14:24:25 -08:00
for i in program.imported_structs.iter() {
bindgen_imported_struct(i, &mut ret);
}
2017-12-18 12:39:14 -08:00
// 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
// we'll eventually have it actually in its own section.
2017-12-18 12:39:14 -08:00
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
CNT.fetch_add(1, Ordering::SeqCst));
2017-12-18 14:31:01 -08:00
let generated_static_name = syn::Ident::from(generated_static_name);
let mut generated_static_value = Tokens::new();
let generated_static_length = program.wbg_literal(&mut generated_static_value);
2017-12-18 12:39:14 -08:00
2017-12-18 14:44:09 -08:00
(my_quote! {
2017-12-18 12:39:14 -08:00
#[no_mangle]
#[allow(non_upper_case_globals)]
pub static #generated_static_name: [u32; #generated_static_length] =
[#generated_static_value];
2017-12-18 12:39:14 -08:00
}).to_tokens(&mut ret);
// println!("{}", ret);
2017-12-18 14:31:01 -08:00
2017-12-14 19:31:01 -08:00
ret.into()
}
2017-12-18 12:39:14 -08:00
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
2017-12-18 14:31:01 -08:00
bindgen(&function.free_function_export_name(),
function.rust_symbol(None),
Receiver::FreeFunction(function.name),
&function.arguments,
function.ret.as_ref(),
into)
}
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
2017-12-18 14:31:01 -08:00
for f in s.functions.iter() {
bindgen_struct_fn(s, f, into);
}
for f in s.methods.iter() {
bindgen_struct_method(s, f, into);
}
let name = &s.name;
let free_fn = s.free_function();
let c = shared::name_to_descriptor(name.as_ref()) as u32;
2017-12-18 14:44:09 -08:00
(my_quote! {
impl ::wasm_bindgen::convert::WasmBoundary for #name {
type Js = u32;
const DESCRIPTOR: u32 = #c;
fn into_js(self) -> u32 {
Box::into_raw(Box::new(::wasm_bindgen::__rt::WasmRefCell::new(self))) as u32
}
unsafe fn from_js(js: u32) -> Self {
let js = js as *mut ::wasm_bindgen::__rt::WasmRefCell<#name>;
::wasm_bindgen::__rt::assert_not_null(js);
let js = Box::from_raw(js);
js.borrow_mut(); // make sure no one's borrowing
js.into_inner()
}
}
impl ::wasm_bindgen::convert::FromRefWasmBoundary for #name {
type RefAnchor = ::wasm_bindgen::__rt::Ref<'static, #name>;
unsafe fn from_js_ref(js: Self::Js) -> Self::RefAnchor {
let js = js as *mut ::wasm_bindgen::__rt::WasmRefCell<#name>;
::wasm_bindgen::__rt::assert_not_null(js);
(*js).borrow()
}
}
impl ::wasm_bindgen::convert::FromRefMutWasmBoundary for #name {
type RefAnchor = ::wasm_bindgen::__rt::RefMut<'static, #name>;
unsafe fn from_js_ref_mut(js: Self::Js) -> Self::RefAnchor {
let js = js as *mut ::wasm_bindgen::__rt::WasmRefCell<#name>;
::wasm_bindgen::__rt::assert_not_null(js);
(*js).borrow_mut()
}
}
2017-12-18 14:31:01 -08:00
#[no_mangle]
pub unsafe extern fn #free_fn(ptr: u32) {
<#name as ::wasm_bindgen::convert::WasmBoundary>::from_js(ptr);
2017-12-18 14:31:01 -08:00
}
}).to_tokens(into);
}
fn bindgen_struct_fn(s: &ast::Struct, f: &ast::Function, into: &mut Tokens) {
bindgen(&f.struct_function_export_name(s.name),
f.rust_symbol(Some(s.name)),
Receiver::StructFunction(s.name, f.name),
&f.arguments,
f.ret.as_ref(),
into)
}
fn bindgen_struct_method(s: &ast::Struct, m: &ast::Method, into: &mut Tokens) {
bindgen(&m.function.struct_function_export_name(s.name),
m.function.rust_symbol(Some(s.name)),
Receiver::StructMethod(s.name, m.mutable, m.function.name),
&m.function.arguments,
m.function.ret.as_ref(),
into)
}
enum Receiver {
FreeFunction(syn::Ident),
StructFunction(syn::Ident, syn::Ident),
StructMethod(syn::Ident, bool, syn::Ident),
}
2018-01-08 10:42:01 -08:00
fn bindgen(export_name: &syn::LitStr,
2017-12-18 14:31:01 -08:00
generated_name: syn::Ident,
receiver: Receiver,
arguments: &[ast::Type],
ret_type: Option<&ast::Type>,
into: &mut Tokens) {
2017-12-14 19:31:01 -08:00
let mut args = vec![];
2017-12-14 21:55:21 -08:00
let mut arg_conversions = vec![];
2017-12-14 19:31:01 -08:00
let mut converted_arguments = vec![];
let ret = syn::Ident::from("_ret");
2017-12-18 14:31:01 -08:00
let mut offset = 0;
if let Receiver::StructMethod(class, _, _) = receiver {
args.push(my_quote! { me: *mut ::wasm_bindgen::__rt::WasmRefCell<#class> });
2017-12-18 14:44:09 -08:00
arg_conversions.push(my_quote! {
::wasm_bindgen::__rt::assert_not_null(me);
2017-12-18 14:31:01 -08:00
let me = unsafe { &*me };
});
offset = 1;
}
for (i, ty) in arguments.iter().enumerate() {
let i = i + offset;
2017-12-14 19:31:01 -08:00
let ident = syn::Ident::from(format!("arg{}", i));
match *ty {
2017-12-14 21:55:21 -08:00
ast::Type::BorrowedStr => {
let ptr = syn::Ident::from(format!("arg{}_ptr", i));
let len = syn::Ident::from(format!("arg{}_len", i));
2017-12-18 14:44:09 -08:00
args.push(my_quote! { #ptr: *const u8 });
args.push(my_quote! { #len: usize });
arg_conversions.push(my_quote! {
2017-12-14 21:55:21 -08:00
let #ident = unsafe {
let slice = ::std::slice::from_raw_parts(#ptr, #len);
::std::str::from_utf8_unchecked(slice)
};
});
}
ast::Type::String => {
let ptr = syn::Ident::from(format!("arg{}_ptr", i));
let len = syn::Ident::from(format!("arg{}_len", i));
2017-12-18 14:44:09 -08:00
args.push(my_quote! { #ptr: *mut u8 });
args.push(my_quote! { #len: usize });
arg_conversions.push(my_quote! {
2017-12-14 21:55:21 -08:00
let #ident = unsafe {
let vec = ::std::vec::Vec::from_raw_parts(#ptr, #len, #len);
::std::string::String::from_utf8_unchecked(vec)
};
});
2017-12-14 19:31:01 -08:00
}
ast::Type::ByValue(ref t) => {
args.push(my_quote! {
#ident: <#t as ::wasm_bindgen::convert::WasmBoundary >::Js
});
2017-12-18 14:44:09 -08:00
arg_conversions.push(my_quote! {
2017-12-18 12:39:14 -08:00
let #ident = unsafe {
<#t as ::wasm_bindgen::convert::WasmBoundary>
::from_js(#ident)
2017-12-18 12:39:14 -08:00
};
});
}
ast::Type::ByRef(ref ty) => {
args.push(my_quote! {
#ident: <#ty as ::wasm_bindgen::convert::WasmBoundary>::Js
2017-12-18 12:39:14 -08:00
});
2017-12-18 14:44:09 -08:00
arg_conversions.push(my_quote! {
let #ident = unsafe {
<#ty as ::wasm_bindgen::convert::FromRefWasmBoundary>
::from_js_ref(#ident)
};
let #ident = &*#ident;
2017-12-18 12:39:14 -08:00
});
}
ast::Type::ByMutRef(ref ty) => {
args.push(my_quote! {
#ident: <#ty as ::wasm_bindgen::convert::WasmBoundary>::Js
});
arg_conversions.push(my_quote! {
let mut #ident = unsafe {
<#ty as ::wasm_bindgen::convert::FromRefMutWasmBoundary>
::from_js_ref_mut(#ident)
};
let #ident = &mut *#ident;
});
}
2017-12-14 19:31:01 -08:00
}
2017-12-18 14:44:09 -08:00
converted_arguments.push(my_quote! { #ident });
2017-12-14 19:31:01 -08:00
}
let ret_ty;
let convert_ret;
2017-12-18 14:31:01 -08:00
match ret_type {
Some(&ast::Type::String) => {
2017-12-18 14:44:09 -08:00
ret_ty = my_quote! { -> *mut String };
convert_ret = my_quote! { Box::into_raw(Box::new(#ret)) };
2017-12-14 21:55:21 -08:00
}
Some(&ast::Type::ByValue(ref t)) => {
ret_ty = my_quote! {
-> <#t as ::wasm_bindgen::convert::WasmBoundary>::Js
2017-12-18 12:39:14 -08:00
};
convert_ret = my_quote! {
<#t as ::wasm_bindgen::convert::WasmBoundary>::into_js(#ret)
};
}
Some(&ast::Type::BorrowedStr) |
Some(&ast::Type::ByMutRef(_)) |
Some(&ast::Type::ByRef(_)) => {
panic!("can't return a borrowed ref");
}
2017-12-14 19:31:01 -08:00
None => {
2017-12-18 14:44:09 -08:00
ret_ty = my_quote! {};
convert_ret = my_quote! {};
2017-12-14 19:31:01 -08:00
}
}
2017-12-18 14:44:09 -08:00
let tokens = my_quote! {
2017-12-14 19:31:01 -08:00
#[export_name = #export_name]
2017-12-18 14:31:01 -08:00
#[allow(non_snake_case)]
2017-12-14 19:31:01 -08:00
pub extern fn #generated_name(#(#args),*) #ret_ty {
2017-12-14 21:55:21 -08:00
#(#arg_conversions)*
2017-12-18 14:31:01 -08:00
let #ret = #receiver(#(#converted_arguments),*);
2017-12-14 19:31:01 -08:00
#convert_ret
}
};
tokens.to_tokens(into);
}
2017-12-18 12:39:14 -08:00
2017-12-18 14:31:01 -08:00
impl ToTokens for Receiver {
fn to_tokens(&self, tokens: &mut Tokens) {
match *self {
Receiver::FreeFunction(name) => name.to_tokens(tokens),
Receiver::StructFunction(s, name) => {
s.to_tokens(tokens);
syn::token::Colon2::default().to_tokens(tokens);
2017-12-18 14:31:01 -08:00
name.to_tokens(tokens);
}
Receiver::StructMethod(_, mutable, name) => {
2017-12-18 15:54:43 -08:00
(my_quote! { me }).to_tokens(tokens);
syn::token::Dot::default().to_tokens(tokens);
2017-12-18 14:31:01 -08:00
if mutable {
syn::Ident::from("borrow_mut").to_tokens(tokens);
} else {
syn::Ident::from("borrow").to_tokens(tokens);
}
2017-12-31 15:53:29 -08:00
tokens.append(TokenTree {
2018-01-08 10:42:01 -08:00
span: Span::def_site(),
2017-12-31 15:53:29 -08:00
kind: TokenNode::Group(Delimiter::Parenthesis,
proc_macro2::TokenStream::empty()),
});
syn::token::Dot::default().to_tokens(tokens);
2017-12-18 14:31:01 -08:00
name.to_tokens(tokens);
}
}
2017-12-18 12:39:14 -08:00
}
}
fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) {
let import_name = shared::mangled_import_name(
None,
import.function.wasm_function.name.as_ref(),
);
2018-02-05 16:39:11 -08:00
bindgen_import_function(&import.function, &import_name, tokens);
2018-02-05 14:24:25 -08:00
}
fn bindgen_imported_struct(import: &ast::ImportStruct, tokens: &mut Tokens) {
let name = import.name;
let mut methods = Tokens::new();
for f in import.functions.iter() {
let import_name = shared::mangled_import_name(
Some(&import.name.to_string()),
f.function.wasm_function.name.as_ref(),
);
bindgen_import_function(&f.function, &import_name, &mut methods);
2018-02-05 14:24:25 -08:00
}
(my_quote! {
2018-02-06 08:23:51 -08:00
pub struct #name {
obj: ::wasm_bindgen::JsValue,
2018-02-06 08:23:51 -08:00
}
2018-02-05 14:24:25 -08:00
impl #name {
#methods
}
2018-02-06 08:23:51 -08:00
impl ::wasm_bindgen::convert::WasmBoundary for #name {
type Js = <::wasm_bindgen::JsValue as
2018-02-06 08:23:51 -08:00
::wasm_bindgen::convert::WasmBoundary>::Js;
const DESCRIPTOR: u32 = <::wasm_bindgen::JsValue as
2018-02-06 08:23:51 -08:00
::wasm_bindgen::convert::WasmBoundary>::DESCRIPTOR;
fn into_js(self) -> Self::Js {
self.obj.into_js()
}
unsafe fn from_js(js: Self::Js) -> Self {
#name { obj: ::wasm_bindgen::JsValue::from_js(js) }
2018-02-06 08:23:51 -08:00
}
}
2018-02-05 14:24:25 -08:00
}).to_tokens(tokens);
}
fn bindgen_import_function(import: &ast::ImportFunction,
import_name: &str,
tokens: &mut Tokens) {
let vis = &import.rust_vis;
let ret = &import.rust_decl.output;
let fn_token = &import.rust_decl.fn_token;
let arguments = &import.rust_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");
2018-02-06 08:23:51 -08:00
let inputs = import.rust_decl.inputs.iter().collect::<Vec<_>>();
let (is_method, inputs) = match inputs.get(0) {
Some(&&syn::FnArg::Captured(_)) => (false, &inputs[..]),
Some(_) => (true, &inputs[1..]),
None => (false, &inputs[..]),
};
if is_method {
let ptr = syn::Ident::from("ptr");
abi_argument_names.push(ptr);
abi_arguments.push(my_quote! { #ptr: u32 });
arg_conversions.push(my_quote! {
let #ptr = ::wasm_bindgen::convert::ToRefWasmBoundary::to_js_ref(&self.obj);
});
}
let names = inputs
.iter()
.map(|arg| {
2018-02-06 08:23:51 -08:00
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 {
2017-12-31 14:40:57 -08:00
by_ref: None,
ident,
subpat: None,
..
}) => {
ident
}
_ => panic!("unsupported pattern in foreign function"),
}
});
2018-02-05 14:24:25 -08:00
for (ty, name) in import.wasm_function.arguments.iter().zip(names) {
match *ty {
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::ByValue(ref t) => {
abi_argument_names.push(name);
abi_arguments.push(my_quote! {
#name: <#t as ::wasm_bindgen::convert::WasmBoundary>::Js
});
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::WasmBoundary>
::into_js(#name);
});
}
ast::Type::ByMutRef(_) => panic!("urgh mut"),
ast::Type::ByRef(ref t) => {
abi_argument_names.push(name);
abi_arguments.push(my_quote! { #name: u32 });
arg_conversions.push(my_quote! {
let #name = <#t as ::wasm_bindgen::convert::ToRefWasmBoundary>
::to_js_ref(#name);
});
}
// TODO: need to test this
ast::Type::String => {
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();
::std::mem::forget(#name);
});
}
}
}
let abi_ret;
let mut convert_ret;
2018-02-05 14:24:25 -08:00
match import.wasm_function.ret {
Some(ast::Type::ByValue(ref t)) => {
abi_ret = my_quote! {
<#t as ::wasm_bindgen::convert::WasmBoundary>::Js
};
convert_ret = my_quote! {
<#t as ::wasm_bindgen::convert::WasmBoundary>::from_js(#ret_ident)
};
2017-12-20 07:35:14 -08:00
}
// TODO: add a test for this
Some(ast::Type::String) => {
let name = syn::Ident::from("__ret_strlen");
2018-02-06 08:39:49 -08:00
let name_ptr = syn::Ident::from("__ret_strlen_ptr");
abi_argument_names.push(name_ptr);
abi_arguments.push(my_quote! { #name_ptr: *mut usize });
arg_conversions.push(my_quote! {
let mut #name = 0;
2018-02-06 08:39:49 -08:00
let mut #name_ptr = &mut #name as *mut usize;
});
2018-02-06 08:39:49 -08:00
abi_ret = my_quote! { *mut u8 };
convert_ret = my_quote! {
2018-02-06 08:39:49 -08:00
String::from_utf8_unchecked(
Vec::from_raw_parts(#ret_ident, #name, #name)
)
};
}
Some(ast::Type::BorrowedStr) |
Some(ast::Type::ByRef(_)) |
Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"),
None => {
abi_ret = my_quote! { () };
convert_ret = my_quote! { () };
}
}
let mut exceptional_ret = my_quote! {};
if import.catch {
let exn_data = syn::Ident::from("exn_data");
let exn_data_ptr = syn::Ident::from("exn_data_ptr");
abi_argument_names.push(exn_data_ptr);
abi_arguments.push(my_quote! { #exn_data_ptr: *mut u32 });
arg_conversions.push(my_quote! {
let mut #exn_data = [0; 2];
2018-02-06 19:14:54 -08:00
let #exn_data_ptr = #exn_data.as_mut_ptr();
});
convert_ret = my_quote! { Ok(#convert_ret) };
exceptional_ret = my_quote! {
if #exn_data[0] == 1 {
return Err(<::wasm_bindgen::JsValue as
::wasm_bindgen::convert::WasmBoundary>::from_js(#exn_data[1]))
}
};
}
2018-02-05 14:24:25 -08:00
let name = import.ident;
let import_name = syn::Ident::from(import_name);
(quote! {
#vis #fn_token #name(#arguments) #ret {
extern {
2018-02-05 14:24:25 -08:00
fn #import_name(#(#abi_arguments),*) -> #abi_ret;
}
unsafe {
#(#arg_conversions)*
2018-02-05 14:24:25 -08:00
let #ret_ident = #import_name(#(#abi_argument_names),*);
#exceptional_ret
#convert_ret
}
}
}).to_tokens(tokens);
}