Classes are now working!

This commit is contained in:
Alex Crichton 2017-12-18 14:31:01 -08:00
parent 7c510a8a7e
commit 6593b5ef69
8 changed files with 396 additions and 91 deletions

View File

@ -8,3 +8,6 @@ wasm-bindgen-macro = { path = "crates/wasm-bindgen-macro" }
[dev-dependencies]
test-support = { path = "crates/test-support" }
[workspace]
members = ["crates/wasm-bindgen-cli"]

View File

@ -9,6 +9,9 @@ use std::sync::atomic::*;
use std::sync::{Once, ONCE_INIT};
use std::time::Instant;
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst));
pub struct Project {
files: Vec<(String, String)>,
}
@ -25,10 +28,12 @@ pub fn project() -> Project {
files: vec![
("Cargo.toml".to_string(), format!(r#"
[package]
name = "test"
name = "test{}"
version = "0.0.1"
authors = []
[workspace]
[lib]
crate-type = ["cdylib"]
@ -37,7 +42,7 @@ pub fn project() -> Project {
[profile.dev]
opt-level = 2 # TODO: decrease when upstream is not buggy
"#, dir.display())),
"#, IDX.with(|x| *x), dir.display())),
("Cargo.lock".to_string(), lockfile),
@ -60,8 +65,6 @@ pub fn project() -> Project {
}
pub fn root() -> PathBuf {
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
thread_local!(static IDX: usize = CNT.fetch_add(1, Ordering::SeqCst));
let idx = IDX.with(|x| *x);
let mut me = env::current_exe().unwrap();
@ -123,7 +126,8 @@ impl Project {
.env("CARGO_TARGET_DIR", &target_dir);
run(&mut cmd, "cargo");
let mut out = target_dir.join("wasm32-unknown-unknown/debug/test.wasm");
let idx = IDX.with(|x| *x);
let mut out = target_dir.join(&format!("wasm32-unknown-unknown/debug/test{}.wasm", idx));
if Command::new("wasm-gc").output().is_ok() {
let tmp = out;
out = tmp.with_extension("gc.wasm");

View File

@ -4,7 +4,11 @@ use shared;
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_token: bool,
pub exports: Vec<(String, String)>,
pub classes: Vec<String>,
pub nodejs: bool,
}
@ -14,6 +18,9 @@ impl Js {
for f in program.free_functions.iter() {
self.generate_free_function(f);
}
for s in program.structs.iter() {
self.generate_struct(s);
}
}
pub fn generate_free_function(&mut self, func: &shared::Function) {
@ -28,12 +35,76 @@ impl Js {
return
}
let mut dst = format!("function {}(", func.name);
let ret = self.generate_function(&format!("function {}", func.name),
&func.name,
false,
&func.arguments,
func.ret.as_ref());
self.exports.push((func.name.clone(), ret));
}
pub fn generate_struct(&mut self, s: &shared::Struct) {
let mut dst = String::new();
self.expose_token = true;
self.expose_global_exports = true;
dst.push_str(&format!("
class {} {{
constructor(ptr, sym) {{
_checkToken(sym);
this.__wasmPtr = ptr;
}}
free() {{
const ptr = this.__wasmPtr;
this.__wasmPtr = 0;
exports.{}(ptr);
}}
", s.name, s.free_function()));
for function in s.functions.iter() {
let f = self.generate_function(
&format!("static {}", function.name),
&function.struct_function_export_name(&s.name),
false,
&function.arguments,
function.ret.as_ref(),
);
dst.push_str(&f);
dst.push_str("\n");
}
for method in s.methods.iter() {
let f = self.generate_function(
&format!("{}", method.function.name),
&method.function.struct_function_export_name(&s.name),
true,
&method.function.arguments,
method.function.ret.as_ref(),
);
dst.push_str(&f);
dst.push_str("\n");
}
dst.push_str("}\n");
self.classes.push(dst);
self.exports.push((s.name.clone(), s.name.clone()));
}
fn generate_function(&mut self,
name: &str,
wasm_name: &str,
is_method: bool,
arguments: &[shared::Type],
ret: Option<&shared::Type>) -> String {
let mut dst = format!("{}(", name);
let mut passed_args = String::new();
let mut arg_conversions = String::new();
let mut destructors = String::new();
for (i, arg) in func.arguments.iter().enumerate() {
if is_method {
passed_args.push_str("this.__wasmPtr");
}
for (i, arg) in arguments.iter().enumerate() {
let name = format!("arg{}", i);
if i > 0 {
dst.push_str(", ");
@ -50,23 +121,17 @@ impl Js {
shared::Type::Number => pass(&name),
shared::Type::BorrowedStr |
shared::Type::String => {
if self.nodejs {
arg_conversions.push_str(&format!("\
const buf{i} = Buffer.from({arg});
const len{i} = buf{i}.length;
const ptr{i} = exports.__wbindgen_malloc(len{i});
let memory{i} = new Uint8Array(memory.buffer);
buf{i}.copy(memory{i}, ptr{i});
", i = i, arg = name));
pass(&format!("ptr{}", i));
pass(&format!("len{}", i));
if let shared::Type::BorrowedStr = *arg {
destructors.push_str(&format!("\n\
exports.__wbindgen_free(ptr{i}, len{i});\n\
", i = i));
}
} else {
panic!("strings not implemented for browser");
self.expose_global_exports = true;
self.expose_pass_string_to_wasm = true;
arg_conversions.push_str(&format!("\
const [ptr{i}, len{i}] = passStringToWasm({arg});
", i = i, arg = name));
pass(&format!("ptr{}", i));
pass(&format!("len{}", i));
if let shared::Type::BorrowedStr = *arg {
destructors.push_str(&format!("\n\
exports.__wbindgen_free(ptr{i}, len{i});\n\
", i = i));
}
}
shared::Type::ByRef(_) |
@ -85,59 +150,107 @@ impl Js {
}
}
}
let convert_ret = match func.ret {
let convert_ret = match ret {
None |
Some(shared::Type::Number) => format!("return ret;"),
Some(shared::Type::BorrowedStr) |
Some(shared::Type::ByMutRef(_)) |
Some(shared::Type::ByRef(_)) => panic!(),
Some(shared::Type::ByValue(ref name)) => {
Some(&shared::Type::Number) => format!("return ret;"),
Some(&shared::Type::BorrowedStr) |
Some(&shared::Type::ByMutRef(_)) |
Some(&shared::Type::ByRef(_)) => panic!(),
Some(&shared::Type::ByValue(ref name)) => {
format!("\
return {name}.__wasmWrap(ret);
return new {name}(ret, token);
", name = name)
}
Some(shared::Type::String) => {
if self.nodejs {
format!("\
const mem = new Uint8Array(memory.buffer);
const ptr = exports.__wbindgen_boxed_str_ptr(ret);
const len = exports.__wbindgen_boxed_str_len(ret);
const buf = Buffer.from(mem.slice(ptr, ptr + len));
const realRet = buf.toString();
exports.__wbindgen_boxed_str_free(ret);
return realRet;
")
} else {
panic!("strings not implemented for browser");
}
Some(&shared::Type::String) => {
self.expose_get_string_from_wasm = true;
format!("return getStringFromWasm(ret);")
}
};
dst.push_str(") {\n ");
dst.push_str(&arg_conversions);
dst.push_str(&format!("\
try {{
self.expose_global_exports = true;
if destructors.len() == 0 {
dst.push_str(&format!("\
const ret = exports.{f}({passed});
{convert_ret}
}} finally {{
{destructors}
}}
", f = func.name, passed = passed_args, destructors = destructors,
convert_ret = convert_ret));
dst.push_str("};");
self.exports.push((func.name.clone(), dst));
", f = wasm_name, passed = passed_args, convert_ret = convert_ret));
} else {
dst.push_str(&format!("\
try {{
const ret = exports.{f}({passed});
{convert_ret}
}} finally {{
{destructors}
}}
", f = wasm_name, passed = passed_args, destructors = destructors,
convert_ret = convert_ret));
}
dst.push_str("}");
return dst
}
pub fn to_string(&self) -> String {
let mut globals = String::new();
if self.expose_global_memory {
if self.expose_global_memory ||
self.expose_pass_string_to_wasm ||
self.expose_get_string_from_wasm
{
globals.push_str("const memory = obj.instance.exports.memory;\n");
}
if self.expose_global_exports {
if self.expose_global_exports ||
self.expose_pass_string_to_wasm ||
self.expose_get_string_from_wasm
{
globals.push_str("const exports = obj.instance.exports;\n");
}
if self.expose_token {
globals.push_str("\
const token = Symbol('foo');
function _checkToken(sym) {
if (token != sym)
throw new Error('cannot invoke `new` directly');
}
");
}
if self.expose_pass_string_to_wasm {
if self.nodejs {
globals.push_str("
function passStringToWasm(arg) {
const buf = Buffer.from(arg);
const len = buf.length;
const ptr = exports.__wbindgen_malloc(len);
let array = new Uint8Array(memory.buffer);
buf.copy(array, ptr);
return [ptr, len];
}
");
} else {
panic!("browser strings not implemented yet");
}
}
if self.expose_get_string_from_wasm {
if self.nodejs {
globals.push_str("
function getStringFromWasm(ptr) {
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 ret = buf.toString();
exports.__wbindgen_boxed_str_free(ptr);
return ret;
}
");
} else {
panic!("strings not implemented for browser");
}
}
let mut exports = String::new();
for class in self.classes.iter() {
exports.push_str(class);
exports.push_str("\n");
}
for &(ref name, ref body) in self.exports.iter() {
exports.push_str("obj.");
exports.push_str(name);
@ -146,12 +259,12 @@ impl Js {
exports.push_str(";\n");
}
format!("
const function xform(obj) {{
function xform(obj) {{
{}
{}
return obj;
}}
export const function instantiate(bytes, imports) {{
export function instantiate(bytes, imports) {{
return WebAssembly.instantiate(bytes, imports).then(xform);
}}
", globals, exports)

View File

@ -88,6 +88,7 @@ impl Object {
pub fn generate_js(&self) -> String {
let mut js = js::Js::default();
js.nodejs = self.nodejs;
js.generate_program(&self.program);
js.to_string()
}

View File

@ -24,7 +24,6 @@ pub enum Type {
pub struct Struct {
pub name: syn::Ident,
pub ctor: Option<Function>,
pub methods: Vec<Method>,
pub functions: Vec<Function>,
}
@ -118,16 +117,30 @@ impl Function {
Function { name: input.ident, arguments, ret }
}
pub fn export_name(&self) -> syn::Lit {
pub fn free_function_export_name(&self) -> syn::Lit {
let name = self.shared().free_function_export_name();
syn::Lit {
value: syn::LitKind::Other(Literal::string(self.name.sym.as_str())),
value: syn::LitKind::Other(Literal::string(&name)),
span: Default::default(),
}
}
pub fn rust_symbol(&self) -> syn::Ident {
let generated_name = format!("__wasm_bindgen_generated_{}",
self.name.sym.as_str());
pub fn struct_function_export_name(&self, s: syn::Ident) -> syn::Lit {
let name = self.shared().struct_function_export_name(s.sym.as_str());
syn::Lit {
value: syn::LitKind::Other(Literal::string(&name)),
span: Default::default(),
}
}
pub fn rust_symbol(&self, namespace: Option<syn::Ident>) -> syn::Ident {
let mut generated_name = format!("__wasm_bindgen_generated");
if let Some(ns) = namespace {
generated_name.push_str("_");
generated_name.push_str(ns.sym.as_str());
}
generated_name.push_str("_");
generated_name.push_str(self.name.sym.as_str());
syn::Ident::from(generated_name)
}
@ -220,12 +233,15 @@ impl Struct {
pub fn from(s: &syn::ItemStruct) -> Struct {
Struct {
name: s.ident,
ctor: None,
methods: Vec::new(),
functions: Vec::new(),
}
}
pub fn free_function(&self) -> syn::Ident {
syn::Ident::from(self.shared().free_function())
}
pub fn push_item(&mut self, item: &syn::ImplItem) {
let method = match *item {
syn::ImplItem::Const(_) => panic!("const definitions aren't supported"),
@ -299,7 +315,6 @@ impl Struct {
pub fn shared(&self) -> shared::Struct {
shared::Struct {
name: self.name.to_string(),
ctor: self.ctor.as_ref().unwrap().shared(),
functions: self.functions.iter().map(|f| f.shared()).collect(),
methods: self.methods.iter().map(|f| f.shared()).collect(),
}

View File

@ -60,6 +60,7 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
static CNT: AtomicUsize = ATOMIC_USIZE_INIT;
let generated_static_name = format!("__WASM_BINDGEN_GENERATED{}",
CNT.fetch_add(1, Ordering::SeqCst));
let generated_static_name = syn::Ident::from(generated_static_name);
let mut generated_static = String::from("wbg:");
generated_static.push_str(&serde_json::to_string(&program.shared()).unwrap());
let generated_static_value = syn::Lit {
@ -75,22 +76,89 @@ pub fn wasm_bindgen(input: TokenStream) -> TokenStream {
*#generated_static_value;
}).to_tokens(&mut ret);
// println!("{}", ret);
ret.into()
}
fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
let export_name = function.export_name();
let generated_name = function.rust_symbol();
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) {
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();
(quote! {
#[no_mangle]
pub unsafe extern fn #free_fn(ptr: *mut ::std::cell::RefCell<#name>) {
assert!(!ptr.is_null());
drop(Box::from_raw(ptr));
}
}).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),
}
fn bindgen(export_name: &syn::Lit,
generated_name: syn::Ident,
receiver: Receiver,
arguments: &[ast::Type],
ret_type: Option<&ast::Type>,
into: &mut Tokens) {
let mut args = vec![];
let mut arg_conversions = vec![];
let real_name = &function.name;
let mut converted_arguments = vec![];
let ret = syn::Ident::from("_ret");
let mut malloc = false;
let mut boxed_str = false;
for (i, ty) in function.arguments.iter().enumerate() {
let mut offset = 0;
if let Receiver::StructMethod(class, _, _) = receiver {
args.push(quote! { me: *mut ::std::cell::RefCell<#class> });
arg_conversions.push(quote! {
assert!(!me.is_null());
let me = unsafe { &*me };
});
offset = 1;
}
for (i, ty) in arguments.iter().enumerate() {
let i = i + offset;
let ident = syn::Ident::from(format!("arg{}", i));
match *ty {
ast::Type::Integer(i) => {
@ -153,23 +221,23 @@ fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
}
let ret_ty;
let convert_ret;
match function.ret {
Some(ast::Type::Integer(i)) => {
match ret_type {
Some(&ast::Type::Integer(i)) => {
ret_ty = quote! { -> #i };
convert_ret = quote! { #ret };
}
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) => {
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) => {
boxed_str = !BOXED_STR_GENERATED.swap(true, Ordering::SeqCst);
ret_ty = quote! { -> *mut String };
convert_ret = quote! { Box::into_raw(Box::new(#ret)) };
}
Some(ast::Type::ByValue(name)) => {
Some(&ast::Type::ByValue(name)) => {
ret_ty = quote! { -> *mut ::std::cell::RefCell<#name> };
convert_ret = quote! {
Box::into_raw(Box::new(::std::cell::RefCell<#ret>))
Box::into_raw(Box::new(::std::cell::RefCell::new(#ret)))
};
}
None => {
@ -224,20 +292,38 @@ fn bindgen_fn(function: &ast::Function, into: &mut Tokens) {
#malloc
#boxed_str
#[no_mangle]
#[export_name = #export_name]
#[allow(non_snake_case)]
pub extern fn #generated_name(#(#args),*) #ret_ty {
#(#arg_conversions)*
let #ret = #real_name(#(#converted_arguments),*);
let #ret = #receiver(#(#converted_arguments),*);
#convert_ret
}
};
// println!("{}", tokens);
tokens.to_tokens(into);
}
fn bindgen_struct(s: &ast::Struct, into: &mut Tokens) {
if s.ctor.is_none() {
panic!("struct `{}` needs a `new` function to construct it", s.name);
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::tokens::Colon2::default().to_tokens(tokens);
name.to_tokens(tokens);
}
Receiver::StructMethod(_, mutable, name) => {
syn::Ident::from("me").to_tokens(tokens);
syn::tokens::Dot::default().to_tokens(tokens);
if mutable {
syn::Ident::from("borrow_mut").to_tokens(tokens);
} else {
syn::Ident::from("borrow").to_tokens(tokens);
}
tokens.append_delimited("(", Default::default(), |_| ());
syn::tokens::Dot::default().to_tokens(tokens);
name.to_tokens(tokens);
}
}
}
}

View File

@ -10,7 +10,6 @@ pub struct Program {
#[derive(Serialize, Deserialize)]
pub struct Struct {
pub name: String,
pub ctor: Function,
pub functions: Vec<Function>,
pub methods: Vec<Method>,
}
@ -28,6 +27,33 @@ pub struct Function {
pub ret: Option<Type>,
}
impl Struct {
pub fn free_function(&self) -> String {
let mut name = format!("__wbindgen_");
name.extend(self.name
.chars()
.flat_map(|s| s.to_lowercase()));
name.push_str("_free");
return name
}
}
impl Function {
pub fn free_function_export_name(&self) -> String {
self.name.clone()
}
pub fn struct_function_export_name(&self, struct_: &str) -> String {
let mut name = struct_
.chars()
.flat_map(|s| s.to_lowercase())
.collect::<String>();
name.push_str("_");
name.push_str(&self.name);
return name
}
}
#[derive(Serialize, Deserialize)]
pub enum Type {
Number,

View File

@ -21,7 +21,7 @@ fn simple() {
}
pub fn with_contents(a: u32) -> Foo {
Foo::with_contents(a)
Foo { contents: a }
}
pub fn add(&mut self, amt: u32) -> u32 {
@ -35,15 +35,72 @@ fn simple() {
import * as assert from "assert";
export function test(wasm) {
const r = new wasm.Foo();
const r = wasm.Foo.new();
assert.strictEqual(r.add(0), 0);
assert.strictEqual(r.add(1), 1);
assert.strictEqual(r.add(1), 2);
r.free();
const r2 = wasm.Foo.with_contents(10);
assert.strictEqual(r.add(1), 11);
assert.strictEqual(r.add(2), 13);
assert.strictEqual(r.add(3), 16);
assert.strictEqual(r2.add(1), 11);
assert.strictEqual(r2.add(2), 13);
assert.strictEqual(r2.add(3), 16);
r2.free();
}
"#)
.test();
}
#[test]
fn strings() {
test_support::project()
.file("src/lib.rs", r#"
#![feature(proc_macro)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
wasm_bindgen! {
pub struct Foo {
name: u32,
}
pub struct Bar {
contents: String,
}
impl Foo {
pub fn new() -> Foo {
Foo { name: 0 }
}
pub fn set(&mut self, amt: u32) {
self.name = amt;
}
pub fn bar(&self, mix: &str) -> Bar {
Bar { contents: format!("foo-{}-{}", mix, self.name) }
}
}
impl Bar {
pub fn name(&self) -> String {
self.contents.clone()
}
}
}
"#)
.file("test.js", r#"
import * as assert from "assert";
export function test(wasm) {
const r = wasm.Foo.new();
r.set(3);
let bar = r.bar('baz');
r.free();
assert.strictEqual(bar.name(), "foo-baz-3");
bar.free();
}
"#)
.test();