From 3c58aa7310b69b2351246f48f62598b0533e4e35 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 16 Feb 2018 18:58:37 -0800 Subject: [PATCH] Support integer/float slices/vectors Closes #5 --- README.md | 1 + crates/wasm-bindgen-cli-support/src/js.rs | 463 ++++++++++++++++++++-- crates/wasm-bindgen-macro/src/ast.rs | 131 +++++- crates/wasm-bindgen-macro/src/lib.rs | 105 ++--- crates/wasm-bindgen-shared/src/lib.rs | 20 +- tests/slice.rs | 232 +++++++++++ 6 files changed, 863 insertions(+), 89 deletions(-) create mode 100644 tests/slice.rs diff --git a/README.md b/README.md index 67e06c94b3f..690506c0757 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,7 @@ are: * Imported types in a foreign module annotated with `#[wasm_bindgen]` * Borrowed exported structs (`&Foo` or `&mut Bar`) * The `JsValue` type and `&JsValue` (not mutable references) +* Vectors and slices of supported integer types All of the above can also be returned except borrowed references. Strings are implemented with shim functions to copy data in/out of the Rust heap. That is, a diff --git a/crates/wasm-bindgen-cli-support/src/js.rs b/crates/wasm-bindgen-cli-support/src/js.rs index a6db0f323c4..774f47f36a1 100644 --- a/crates/wasm-bindgen-cli-support/src/js.rs +++ b/crates/wasm-bindgen-cli-support/src/js.rs @@ -467,6 +467,79 @@ impl<'a> Context<'a> { } } + fn expose_pass_array8_to_wasm(&mut self) { + if !self.exposed_globals.insert("pass_array8_to_wasm") { + return + } + self.required_internal_exports.insert("__wbindgen_malloc"); + self.expose_uint8_memory(); + self.globals.push_str(&format!(" + function passArray8ToWasm(arg) {{ + const ptr = wasm.__wbindgen_malloc(arg.byteLength); + getUint8Memory().set(arg, ptr); + return [ptr, arg.length]; + }} + ")); + } + + fn expose_pass_array16_to_wasm(&mut self) { + if !self.exposed_globals.insert("pass_array16_to_wasm") { + return + } + self.required_internal_exports.insert("__wbindgen_malloc"); + self.expose_uint16_memory(); + self.globals.push_str(&format!(" + function passArray16ToWasm(arg) {{ + const ptr = wasm.__wbindgen_malloc(arg.byteLength); + getUint16Memory().set(arg, ptr / 2); + return [ptr, arg.length]; + }} + ")); + } + + fn expose_pass_array32_to_wasm(&mut self) { + if !self.exposed_globals.insert("pass_array32_to_wasm") { + return + } + self.required_internal_exports.insert("__wbindgen_malloc"); + self.expose_uint32_memory(); + self.globals.push_str(&format!(" + function passArray32ToWasm(arg) {{ + const ptr = wasm.__wbindgen_malloc(arg.byteLength); + getUint32Memory().set(arg, ptr / 4); + return [ptr, arg.length]; + }} + ")); + } + + fn expose_pass_array_f32_to_wasm(&mut self) { + if !self.exposed_globals.insert("pass_array_f32_to_wasm") { + return + } + self.required_internal_exports.insert("__wbindgen_malloc"); + self.globals.push_str(&format!(" + function passArrayF32ToWasm(arg) {{ + const ptr = wasm.__wbindgen_malloc(arg.byteLength); + new Float32Array(wasm.memory.buffer).set(arg, ptr / 4); + return [ptr, arg.length]; + }} + ")); + } + + fn expose_pass_array_f64_to_wasm(&mut self) { + if !self.exposed_globals.insert("pass_array_f64_to_wasm") { + return + } + self.required_internal_exports.insert("__wbindgen_malloc"); + self.globals.push_str(&format!(" + function passArrayF64ToWasm(arg) {{ + const ptr = wasm.__wbindgen_malloc(arg.byteLength); + new Float64Array(wasm.memory.buffer).set(arg, ptr / 8); + return [ptr, arg.length]; + }} + ")); + } + fn expose_text_encoder(&mut self) { if !self.exposed_globals.insert("text_encoder") { return @@ -523,6 +596,110 @@ impl<'a> Context<'a> { } } + fn expose_get_array_i8_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_i8_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayI8FromWasm(ptr, len) {{ + const mem = getUint8Memory(); + const slice = mem.slice(ptr, ptr + len); + return new Int8Array(slice); + }} + ")); + } + + fn expose_get_array_u8_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_u8_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayU8FromWasm(ptr, len) {{ + const mem = getUint8Memory(); + const slice = mem.slice(ptr, ptr + len); + return new Uint8Array(slice); + }} + ")); + } + + fn expose_get_array_i16_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_i16_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayI16FromWasm(ptr, len) {{ + const mem = getUint16Memory(); + const slice = mem.slice(ptr / 2, ptr / 2 + len); + return new Int16Array(slice); + }} + ")); + } + + fn expose_get_array_u16_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_u16_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayU16FromWasm(ptr, len) {{ + const mem = getUint16Memory(); + const slice = mem.slice(ptr / 2, ptr / 2 + len); + return new Uint16Array(slice); + }} + ")); + } + + fn expose_get_array_i32_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_i32_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayI32FromWasm(ptr, len) {{ + const mem = getUint32Memory(); + const slice = mem.slice(ptr / 4, ptr / 4 + len); + return new Int32Array(slice); + }} + ")); + } + + fn expose_get_array_u32_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_u32_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayU32FromWasm(ptr, len) {{ + const mem = getUint32Memory(); + const slice = mem.slice(ptr / 4, ptr / 4 + len); + return new Uint32Array(slice); + }} + ")); + } + + fn expose_get_array_f32_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_f32_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayF32FromWasm(ptr, len) {{ + const mem = new Float32Array(wasm.memory.buffer); + const slice = mem.slice(ptr / 4, ptr / 4 + len); + return new Float32Array(slice); + }} + ")); + } + + fn expose_get_array_f64_from_wasm(&mut self) { + if !self.exposed_globals.insert("get_array_f64_from_wasm") { + return + } + self.globals.push_str(&format!(" + function getArrayF64FromWasm(ptr, len) {{ + const mem = new Float64Array(wasm.memory.buffer); + const slice = mem.slice(ptr / 8, ptr / 8 + len); + return new Float64Array(slice); + }} + ")); + } + fn expose_uint8_memory(&mut self) { if !self.exposed_globals.insert("uint8_memory") { return @@ -538,6 +715,21 @@ impl<'a> Context<'a> { ")); } + fn expose_uint16_memory(&mut self) { + if !self.exposed_globals.insert("uint16_memory") { + return + } + self.globals.push_str(&format!(" + let cachedUint16Memory = null; + function getUint16Memory() {{ + if (cachedUint16Memory === null || + cachedUint16Memory.buffer !== wasm.memory.buffer) + cachedUint16Memory = new Uint16Array(wasm.memory.buffer); + return cachedUint16Memory; + }} + ")); + } + fn expose_uint32_memory(&mut self) { if !self.exposed_globals.insert("uint32_memory") { return @@ -640,6 +832,76 @@ impl<'a> Context<'a> { let c = char::from_u32(c).unwrap(); &self.custom_type_names[&c] } + + fn pass_to_wasm_function(&mut self, ty: &VectorType) -> &'static str { + match ty.kind { + VectorKind::String => { + self.expose_pass_string_to_wasm(); + "passStringToWasm" + } + VectorKind::I8 | VectorKind::U8 => { + self.expose_pass_array8_to_wasm(); + "passArray8ToWasm" + } + VectorKind::I16 | VectorKind::U16 => { + self.expose_pass_array16_to_wasm(); + "passArray16ToWasm" + } + VectorKind::I32 | VectorKind::U32 => { + self.expose_pass_array32_to_wasm(); + "passArray32ToWasm" + } + VectorKind::F32 => { + self.expose_pass_array_f32_to_wasm(); + "passArrayF32ToWasm" + } + VectorKind::F64 => { + self.expose_pass_array_f64_to_wasm(); + "passArrayF64ToWasm" + } + } + } + + fn expose_get_vector_from_wasm(&mut self, ty: &VectorType) -> &'static str { + match ty.kind { + VectorKind::String => { + self.expose_get_string_from_wasm(); + "getStringFromWasm" + } + VectorKind::I8 => { + self.expose_get_array_i8_from_wasm(); + "getArrayI8FromWasm" + } + VectorKind::U8 => { + self.expose_get_array_u8_from_wasm(); + "getArrayU8FromWasm" + } + VectorKind::I16 => { + self.expose_get_array_i16_from_wasm(); + "getArrayI16FromWasm" + } + VectorKind::U16 => { + self.expose_get_array_u16_from_wasm(); + "getArrayU16FromWasm" + } + VectorKind::I32 => { + self.expose_get_array_i32_from_wasm(); + "getArrayI32FromWasm" + } + VectorKind::U32 => { + self.expose_get_array_u32_from_wasm(); + "getArrayU32FromWasm" + } + VectorKind::F32 => { + self.expose_get_array_f32_from_wasm(); + "getArrayF32FromWasm" + } + VectorKind::F64 => { + self.expose_get_array_f64_from_wasm(); + "getArrayF64FromWasm" + } + } + } } impl<'a, 'b> SubContext<'a, 'b> { @@ -743,15 +1005,33 @@ impl<'a, 'b> SubContext<'a, 'b> { pass(&format!("arg{i} ? 1 : 0", i = i)) } shared::TYPE_BORROWED_STR | - shared::TYPE_STRING => { - dst_ts.push_str(": string"); - self.cx.expose_pass_string_to_wasm(); + shared::TYPE_STRING | + shared::TYPE_VECTOR_U8 | + shared::TYPE_VECTOR_I8 | + shared::TYPE_SLICE_U8 | + shared::TYPE_SLICE_I8 | + shared::TYPE_VECTOR_U16 | + shared::TYPE_VECTOR_I16 | + shared::TYPE_SLICE_U16 | + shared::TYPE_SLICE_I16 | + shared::TYPE_VECTOR_U32 | + shared::TYPE_VECTOR_I32 | + shared::TYPE_SLICE_U32 | + shared::TYPE_SLICE_I32 | + shared::TYPE_VECTOR_F32 | + shared::TYPE_VECTOR_F64 | + shared::TYPE_SLICE_F32 | + shared::TYPE_SLICE_F64 => { + let ty = VectorType::from(*arg); + dst_ts.push_str(": "); + dst_ts.push_str(ty.js_ty()); + let func = self.cx.pass_to_wasm_function(&ty); arg_conversions.push_str(&format!("\ - const [ptr{i}, len{i}] = passStringToWasm({arg}); - ", i = i, arg = name)); + const [ptr{i}, len{i}] = {func}({arg}); + ", i = i, func = func, arg = name)); pass(&format!("ptr{}", i)); pass(&format!("len{}", i)); - if *arg == shared::TYPE_BORROWED_STR { + if ty.owned { destructors.push_str(&format!("\n\ wasm.__wbindgen_free(ptr{i}, len{i});\n\ ", i = i)); @@ -823,19 +1103,29 @@ impl<'a, 'b> SubContext<'a, 'b> { self.cx.expose_take_object(); format!("return takeObject(ret);") } - Some(shared::TYPE_STRING) => { - dst_ts.push_str(": string"); - self.cx.expose_get_string_from_wasm(); + Some(shared::TYPE_STRING) | + Some(shared::TYPE_VECTOR_U8) | + Some(shared::TYPE_VECTOR_I8) | + Some(shared::TYPE_VECTOR_U16) | + Some(shared::TYPE_VECTOR_I16) | + Some(shared::TYPE_VECTOR_U32) | + Some(shared::TYPE_VECTOR_I32) | + Some(shared::TYPE_VECTOR_F32) | + Some(shared::TYPE_VECTOR_F64) => { + let ty = VectorType::from(function.ret.unwrap()); + dst_ts.push_str(": "); + dst_ts.push_str(ty.js_ty()); + let f = self.cx.expose_get_vector_from_wasm(&ty); self.cx.required_internal_exports.insert("__wbindgen_boxed_str_ptr"); self.cx.required_internal_exports.insert("__wbindgen_boxed_str_len"); self.cx.required_internal_exports.insert("__wbindgen_boxed_str_free"); format!(" const ptr = wasm.__wbindgen_boxed_str_ptr(ret); const len = wasm.__wbindgen_boxed_str_len(ret); - const realRet = getStringFromWasm(ptr, len); + const realRet = {}(ptr, len); wasm.__wbindgen_boxed_str_free(ret); return realRet; - ") + ", f) } Some(shared::TYPE_JS_REF) | Some(shared::TYPE_BORROWED_STR) => panic!(), @@ -925,22 +1215,39 @@ impl<'a, 'b> SubContext<'a, 'b> { invoc_args.push(format!("arg{} != 0", i)); abi_args.push(format!("arg{}", i)); } - shared::TYPE_BORROWED_STR => { - self.cx.expose_get_string_from_wasm(); - invoc_args.push(format!("getStringFromWasm(ptr{0}, len{0})", i)); - abi_args.push(format!("ptr{}", i)); - abi_args.push(format!("len{}", i)); - } - shared::TYPE_STRING => { - self.cx.expose_get_string_from_wasm(); + shared::TYPE_BORROWED_STR | + shared::TYPE_STRING | + shared::TYPE_VECTOR_U8 | + shared::TYPE_VECTOR_I8 | + shared::TYPE_SLICE_U8 | + shared::TYPE_SLICE_I8 | + shared::TYPE_VECTOR_U16 | + shared::TYPE_VECTOR_I16 | + shared::TYPE_SLICE_U16 | + shared::TYPE_SLICE_I16 | + shared::TYPE_VECTOR_U32 | + shared::TYPE_VECTOR_I32 | + shared::TYPE_SLICE_U32 | + shared::TYPE_SLICE_I32 | + shared::TYPE_VECTOR_F32 | + shared::TYPE_VECTOR_F64 | + shared::TYPE_SLICE_F32 | + shared::TYPE_SLICE_F64 => { + let ty = VectorType::from(*arg); + let f = self.cx.expose_get_vector_from_wasm(&ty); abi_args.push(format!("ptr{}", i)); abi_args.push(format!("len{}", i)); extra.push_str(&format!(" - let arg{0} = getStringFromWasm(ptr{0}, len{0}); - wasm.__wbindgen_free(ptr{0}, len{0}); - ", i)); + let arg{0} = {func}(ptr{0}, len{0}); + ", i, func = f)); invoc_args.push(format!("arg{}", i)); - self.cx.required_internal_exports.insert("__wbindgen_free"); + + if ty.owned { + extra.push_str(&format!(" + wasm.__wbindgen_free(ptr{0}, len{0}); + ", i)); + self.cx.required_internal_exports.insert("__wbindgen_free"); + } } shared::TYPE_JS_OWNED => { self.cx.expose_take_object(); @@ -1001,15 +1308,24 @@ impl<'a, 'b> SubContext<'a, 'b> { self.cx.expose_add_heap_object(); format!("return addHeapObject({});", invoc) } - Some(shared::TYPE_STRING) => { - self.cx.expose_pass_string_to_wasm(); + Some(shared::TYPE_STRING) | + Some(shared::TYPE_VECTOR_U8) | + Some(shared::TYPE_VECTOR_I8) | + Some(shared::TYPE_VECTOR_U16) | + Some(shared::TYPE_VECTOR_I16) | + Some(shared::TYPE_VECTOR_U32) | + Some(shared::TYPE_VECTOR_I32) | + Some(shared::TYPE_VECTOR_F32) | + Some(shared::TYPE_VECTOR_F64) => { + let ty = VectorType::from(import.function.ret.unwrap()); + let f = self.cx.pass_to_wasm_function(&ty); self.cx.expose_uint32_memory(); abi_args.push("wasmretptr".to_string()); format!(" - const [retptr, retlen] = passStringToWasm({}); + const [retptr, retlen] = {}({}); getUint32Memory()[wasmretptr / 4] = retlen; return retptr; - ", invoc) + ", f, invoc) } None => invoc, _ => unimplemented!(), @@ -1042,3 +1358,96 @@ impl<'a, 'b> SubContext<'a, 'b> { self.cx.globals.push_str("\n"); } } + +struct VectorType { + owned: bool, + kind: VectorKind, +} + +enum VectorKind { + String, + I8, + U8, + I16, + U16, + I32, + U32, + F32, + F64, +} + +impl VectorType { + fn from(desc: char) -> VectorType { + match desc { + shared::TYPE_BORROWED_STR => { + VectorType { owned: false, kind: VectorKind::String } + } + shared::TYPE_STRING => { + VectorType { owned: true, kind: VectorKind::String } + } + shared::TYPE_VECTOR_U8 => { + VectorType { owned: true, kind: VectorKind::U8 } + } + shared::TYPE_VECTOR_I8 => { + VectorType { owned: true, kind: VectorKind::I8 } + } + shared::TYPE_SLICE_U8 => { + VectorType { owned: false, kind: VectorKind::U8 } + } + shared::TYPE_SLICE_I8 => { + VectorType { owned: false, kind: VectorKind::I8 } + } + shared::TYPE_VECTOR_U16 => { + VectorType { owned: true, kind: VectorKind::U16 } + } + shared::TYPE_VECTOR_I16 => { + VectorType { owned: true, kind: VectorKind::I16 } + } + shared::TYPE_SLICE_U16 => { + VectorType { owned: false, kind: VectorKind::U16 } + } + shared::TYPE_SLICE_I16 => { + VectorType { owned: false, kind: VectorKind::I16 } + } + shared::TYPE_VECTOR_U32 => { + VectorType { owned: true, kind: VectorKind::U32 } + } + shared::TYPE_VECTOR_I32 => { + VectorType { owned: true, kind: VectorKind::I32 } + } + shared::TYPE_SLICE_U32 => { + VectorType { owned: false, kind: VectorKind::U32 } + } + shared::TYPE_SLICE_I32 => { + VectorType { owned: false, kind: VectorKind::I32 } + } + shared::TYPE_VECTOR_F32 => { + VectorType { owned: true, kind: VectorKind::F32 } + } + shared::TYPE_VECTOR_F64 => { + VectorType { owned: true, kind: VectorKind::F64 } + } + shared::TYPE_SLICE_F32 => { + VectorType { owned: false, kind: VectorKind::F32 } + } + shared::TYPE_SLICE_F64 => { + VectorType { owned: false, kind: VectorKind::F64 } + } + _ => panic!() + } + } + + fn js_ty(&self) -> &str { + match self.kind { + VectorKind::String => "string", + VectorKind::I8 => "Int8Array", + VectorKind::U8 => "Uint8Array", + VectorKind::I16 => "Int16Array", + VectorKind::U16 => "Uint16Array", + VectorKind::I32 => "Int32Array", + VectorKind::U32 => "Uint32Array", + VectorKind::F32 => "Float32Array", + VectorKind::F64 => "Float64Array", + } + } +} diff --git a/crates/wasm-bindgen-macro/src/ast.rs b/crates/wasm-bindgen-macro/src/ast.rs index f3e74ef78b4..0920cdac0c0 100644 --- a/crates/wasm-bindgen-macro/src/ast.rs +++ b/crates/wasm-bindgen-macro/src/ast.rs @@ -49,14 +49,25 @@ pub struct Struct { pub enum Type { // special - BorrowedStr, - String, + Vector(VectorType, bool), ByRef(syn::Type), ByMutRef(syn::Type), ByValue(syn::Type), } +pub enum VectorType { + String, + I8, + U8, + I16, + U16, + I32, + U32, + F32, + F64, +} + impl Program { pub fn push_item(&mut self, item: syn::Item, @@ -216,8 +227,8 @@ impl Program { Type::ByMutRef(_) => { panic!("first method argument cannot be mutable ref") } - Type::String | Type::BorrowedStr => { - panic!("method receivers cannot be strings") + Type::Vector(..) => { + panic!("method receivers cannot be vectors") } }; let class_name = match *class { @@ -422,10 +433,15 @@ impl Type { syn::Type::Path(syn::TypePath { qself: None, ref path }) => { let ident = extract_path_ident(path); match ident.as_ref().map(|s| s.as_ref()) { - Some("str") => return Type::BorrowedStr, + Some("str") => return Type::Vector(VectorType::String, false), _ => {} } } + syn::Type::Slice(ref slice) => { + if let Some(ty) = VectorType::from(&slice.elem) { + return Type::Vector(ty, false) + } + } _ => {} } return if r.mutability.is_some() { @@ -434,10 +450,29 @@ impl Type { Type::ByRef((*r.elem).clone()) } } - syn::Type::Path(syn::TypePath { qself: None, ref path }) => { - let ident = extract_path_ident(path); - match ident.as_ref().map(|s| s.as_ref()) { - Some("String") => return Type::String, + syn::Type::Path(syn::TypePath { qself: None, ref path }) + if path.leading_colon.is_none() && path.segments.len() == 1 => + { + let seg = path.segments.first().unwrap().into_value(); + match seg.arguments { + syn::PathArguments::None => { + match seg.ident.as_ref() { + "String" => return Type::Vector(VectorType::String, true), + _ => {} + } + } + syn::PathArguments::AngleBracketed(ref t) + if seg.ident == "Vec" && t.args.len() == 1 => + { + match **t.args.first().unwrap().value() { + syn::GenericArgument::Type(ref t) => { + if let Some(ty) = VectorType::from(t) { + return Type::Vector(ty, true) + } + } + _ => {} + } + } _ => {} } } @@ -449,8 +484,24 @@ impl Type { fn wbg_literal(&self, a: &mut LiteralBuilder) { match *self { - Type::BorrowedStr => a.char(shared::TYPE_BORROWED_STR), - Type::String => a.char(shared::TYPE_STRING), + Type::Vector(VectorType::String, true) => a.char(shared::TYPE_STRING), + Type::Vector(VectorType::String, false) => a.char(shared::TYPE_BORROWED_STR), + Type::Vector(VectorType::U8, true) => a.char(shared::TYPE_VECTOR_U8), + Type::Vector(VectorType::U8, false) => a.char(shared::TYPE_SLICE_U8), + Type::Vector(VectorType::I8, true) => a.char(shared::TYPE_VECTOR_I8), + Type::Vector(VectorType::I8, false) => a.char(shared::TYPE_SLICE_I8), + Type::Vector(VectorType::U16, true) => a.char(shared::TYPE_VECTOR_U16), + Type::Vector(VectorType::U16, false) => a.char(shared::TYPE_SLICE_U16), + Type::Vector(VectorType::I16, true) => a.char(shared::TYPE_VECTOR_I16), + Type::Vector(VectorType::I16, false) => a.char(shared::TYPE_SLICE_I16), + Type::Vector(VectorType::U32, true) => a.char(shared::TYPE_VECTOR_U32), + Type::Vector(VectorType::U32, false) => a.char(shared::TYPE_SLICE_U32), + Type::Vector(VectorType::I32, true) => a.char(shared::TYPE_VECTOR_I32), + Type::Vector(VectorType::I32, false) => a.char(shared::TYPE_SLICE_I32), + Type::Vector(VectorType::F32, true) => a.char(shared::TYPE_VECTOR_F32), + Type::Vector(VectorType::F32, false) => a.char(shared::TYPE_SLICE_F32), + Type::Vector(VectorType::F64, true) => a.char(shared::TYPE_VECTOR_F64), + Type::Vector(VectorType::F64, false) => a.char(shared::TYPE_SLICE_F64), Type::ByValue(ref t) => { a.as_char(my_quote! { <#t as ::wasm_bindgen::convert::WasmBoundary>::DESCRIPTOR @@ -849,3 +900,61 @@ fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) } syn::parse_error() } + +fn ungroup(input: &syn::Type) -> &syn::Type { + match *input { + syn::Type::Group(ref t) => &t.elem, + _ => input, + } +} + +impl VectorType { + fn from(ty: &syn::Type) -> Option { + let path = match *ungroup(ty) { + syn::Type::Path(syn::TypePath { qself: None, ref path }) => path, + _ => return None, + }; + match extract_path_ident(path)?.as_ref() { + "i8" => Some(VectorType::I8), + "u8" => Some(VectorType::U8), + "i16" => Some(VectorType::I16), + "u16" => Some(VectorType::U16), + "i32" => Some(VectorType::I32), + "u32" => Some(VectorType::U32), + "f32" => Some(VectorType::F32), + "f64" => Some(VectorType::F64), + _ => None, + } + } + + pub fn abi_element(&self) -> syn::Ident { + match *self { + VectorType::String => syn::Ident::from("u8"), + VectorType::I8 => syn::Ident::from("i8"), + VectorType::U8 => syn::Ident::from("u8"), + VectorType::I16 => syn::Ident::from("i16"), + VectorType::U16 => syn::Ident::from("u16"), + VectorType::I32 => syn::Ident::from("i32"), + VectorType::U32 => syn::Ident::from("u32"), + VectorType::F32 => syn::Ident::from("f32"), + VectorType::F64 => syn::Ident::from("f64"), + } + } +} + +impl ToTokens for VectorType { + fn to_tokens(&self, tokens: &mut Tokens) { + let me = match *self { + VectorType::String => my_quote! { String }, + VectorType::I8 => my_quote! { Vec }, + VectorType::U8 => my_quote! { Vec }, + VectorType::I16 => my_quote! { Vec }, + VectorType::U16 => my_quote! { Vec }, + VectorType::I32 => my_quote! { Vec }, + VectorType::U32 => my_quote! { Vec }, + VectorType::F32 => my_quote! { Vec }, + VectorType::F64 => my_quote! { Vec }, + }; + me.to_tokens(tokens); + } +} diff --git a/crates/wasm-bindgen-macro/src/lib.rs b/crates/wasm-bindgen-macro/src/lib.rs index dbe0288bab3..92bc40ef48f 100644 --- a/crates/wasm-bindgen-macro/src/lib.rs +++ b/crates/wasm-bindgen-macro/src/lib.rs @@ -144,29 +144,40 @@ fn bindgen_export(export: &ast::Export, into: &mut Tokens) { let i = i + offset; let ident = syn::Ident::from(format!("arg{}", i)); match *ty { - ast::Type::BorrowedStr => { + ast::Type::Vector(ref ty, owned) => { let ptr = syn::Ident::from(format!("arg{}_ptr", i)); let len = syn::Ident::from(format!("arg{}_len", i)); - args.push(my_quote! { #ptr: *const u8 }); + let abi_ty = ty.abi_element(); + args.push(my_quote! { #ptr: *const #abi_ty }); args.push(my_quote! { #len: usize }); - arg_conversions.push(my_quote! { - 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)); - args.push(my_quote! { #ptr: *mut u8 }); - args.push(my_quote! { #len: usize }); - arg_conversions.push(my_quote! { - let #ident = unsafe { - let vec = ::std::vec::Vec::from_raw_parts(#ptr, #len, #len); - ::std::string::String::from_utf8_unchecked(vec) - }; - }); + if owned { + arg_conversions.push(my_quote! { + let #ident = unsafe { + ::std::vec::Vec::from_raw_parts(#ptr, #len, #len) + }; + }); + } else { + arg_conversions.push(my_quote! { + let #ident = unsafe { + ::std::slice::from_raw_parts(#ptr, #len) + }; + }); + } + if let ast::VectorType::String = *ty { + if owned { + arg_conversions.push(my_quote! { + let #ident = unsafe { + ::std::string::String::from_utf8_unchecked(#ident) + }; + }); + } else { + arg_conversions.push(my_quote! { + let #ident = unsafe { + ::std::str::from_utf8_unchecked(#ident) + }; + }); + } + } } ast::Type::ByValue(ref t) => { args.push(my_quote! { @@ -209,8 +220,8 @@ fn bindgen_export(export: &ast::Export, into: &mut Tokens) { let ret_ty; let convert_ret; match export.function.ret { - Some(ast::Type::String) => { - ret_ty = my_quote! { -> *mut String }; + Some(ast::Type::Vector(ref ty, true)) => { + ret_ty = my_quote! { -> *mut #ty }; convert_ret = my_quote! { Box::into_raw(Box::new(#ret)) }; } Some(ast::Type::ByValue(ref t)) => { @@ -221,7 +232,7 @@ fn bindgen_export(export: &ast::Export, into: &mut Tokens) { <#t as ::wasm_bindgen::convert::WasmBoundary>::into_js(#ret) }; } - Some(ast::Type::BorrowedStr) | + Some(ast::Type::Vector(_, false)) | Some(ast::Type::ByMutRef(_)) | Some(ast::Type::ByRef(_)) => { panic!("can't return a borrowed ref"); @@ -344,17 +355,21 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { for (i, (ty, name)) in import.function.arguments.iter().zip(names).enumerate() { match *ty { - ast::Type::BorrowedStr => { + ast::Type::Vector(ref ty, owned) => { 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 }); + let abi_ty = ty.abi_element(); + abi_arguments.push(my_quote! { #ptr: *const #abi_ty }); abi_arguments.push(my_quote! { #len: usize }); arg_conversions.push(my_quote! { let #ptr = #name.as_ptr(); let #len = #name.len(); }); + if owned { + arg_conversions.push(my_quote! { ::std::mem::forget(#name); }); + } } ast::Type::ByValue(ref t) => { abi_argument_names.push(name); @@ -389,20 +404,6 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { }); } } - // 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; @@ -417,25 +418,31 @@ fn bindgen_import(import: &ast::Import, tokens: &mut Tokens) { }; } - // TODO: add a test for this - Some(ast::Type::String) => { - let name = syn::Ident::from("__ret_strlen"); - let name_ptr = syn::Ident::from("__ret_strlen_ptr"); + Some(ast::Type::Vector(ref ty, true)) => { + let name = syn::Ident::from("__ret_len"); + let name_ptr = syn::Ident::from("__ret_len_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; let mut #name_ptr = &mut #name as *mut usize; }); - abi_ret = my_quote! { *mut u8 }; - convert_ret = my_quote! { - String::from_utf8_unchecked( + let abi_ty = ty.abi_element(); + abi_ret = my_quote! { *mut #abi_ty }; + if let ast::VectorType::String = *ty { + convert_ret = my_quote! { + String::from_utf8_unchecked( + Vec::from_raw_parts(#ret_ident, #name, #name) + ) + }; + } else { + convert_ret = my_quote! { Vec::from_raw_parts(#ret_ident, #name, #name) - ) - }; + }; + } } - Some(ast::Type::BorrowedStr) | Some(ast::Type::ByRef(_)) | + Some(ast::Type::Vector(_, false)) | Some(ast::Type::ByMutRef(_)) => panic!("can't return a borrowed ref"), None => { abi_ret = my_quote! { () }; diff --git a/crates/wasm-bindgen-shared/src/lib.rs b/crates/wasm-bindgen-shared/src/lib.rs index a47a0f95bdf..d497ef18a0a 100644 --- a/crates/wasm-bindgen-shared/src/lib.rs +++ b/crates/wasm-bindgen-shared/src/lib.rs @@ -81,8 +81,24 @@ pub const TYPE_NUMBER: char = '\u{5e}'; pub const TYPE_BORROWED_STR: char = '\u{5f}'; pub const TYPE_STRING: char = '\u{60}'; pub const TYPE_BOOLEAN: char = '\u{61}'; -pub const TYPE_JS_OWNED: char = '\u{62}'; -pub const TYPE_JS_REF: char = '\u{63}'; +pub const TYPE_SLICE_U8: char = '\u{62}'; +pub const TYPE_VECTOR_U8: char = '\u{63}'; +pub const TYPE_SLICE_I8: char = '\u{64}'; +pub const TYPE_VECTOR_I8: char = '\u{65}'; +pub const TYPE_SLICE_U16: char = '\u{66}'; +pub const TYPE_VECTOR_U16: char = '\u{67}'; +pub const TYPE_SLICE_I16: char = '\u{68}'; +pub const TYPE_VECTOR_I16: char = '\u{69}'; +pub const TYPE_SLICE_U32: char = '\u{6a}'; +pub const TYPE_VECTOR_U32: char = '\u{6b}'; +pub const TYPE_SLICE_I32: char = '\u{6c}'; +pub const TYPE_VECTOR_I32: char = '\u{6d}'; +pub const TYPE_VECTOR_F32: char = '\u{6e}'; +pub const TYPE_SLICE_F32: char = '\u{6f}'; +pub const TYPE_VECTOR_F64: char = '\u{70}'; +pub const TYPE_SLICE_F64: char = '\u{71}'; +pub const TYPE_JS_OWNED: char = '\u{72}'; +pub const TYPE_JS_REF: char = '\u{73}'; pub const TYPE_CUSTOM_START: u32 = 0x64; pub const TYPE_CUSTOM_REF_FLAG: u32 = 1; diff --git a/tests/slice.rs b/tests/slice.rs new file mode 100644 index 00000000000..60ff0db2e45 --- /dev/null +++ b/tests/slice.rs @@ -0,0 +1,232 @@ +extern crate test_support; + +#[test] +fn export() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + macro_rules! doit { + ($($i:ident)*) => ($( + #[no_mangle] + #[wasm_bindgen] + pub extern fn $i(a: &[$i]) -> Vec<$i> { + assert_eq!(a.len(), 2); + assert_eq!(a[0], 1 as $i); + assert_eq!(a[1], 2 as $i); + a.to_vec() + } + )*) + } + + + doit! { i8 u8 i16 u16 i32 u32 f32 f64 } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import * as wasm from "./out"; + + function assert_arrays_equal(a: any, b: any) { + console.log(a, b); + assert.strictEqual(a.length, b.length); + assert.strictEqual(a.byteLength, b.byteLength); + for (let i = 0; i < a.length; i++) { + assert.strictEqual(a[i], b[i]); + } + } + + export function test() { + const i8 = new Int8Array(2); + i8[0] = 1; + i8[1] = 2; + assert_arrays_equal(wasm.i8(i8), i8); + const u8 = new Uint8Array(2); + u8[0] = 1; + u8[1] = 2; + assert_arrays_equal(wasm.u8(u8), u8); + + const i16 = new Int16Array(2); + i16[0] = 1; + i16[1] = 2; + assert_arrays_equal(wasm.i16(i16), i16); + const u16 = new Uint16Array(2); + u16[0] = 1; + u16[1] = 2; + assert_arrays_equal(wasm.u16(u16), u16); + + const i32 = new Int32Array(2); + i32[0] = 1; + i32[1] = 2; + wasm.i32(i32); + assert_arrays_equal(wasm.i32(i32), i32); + const u32 = new Uint32Array(2); + u32[0] = 1; + u32[1] = 2; + assert_arrays_equal(wasm.u32(u32), u32); + + const f32 = new Float32Array(2); + f32[0] = 1; + f32[1] = 2; + assert_arrays_equal(wasm.f32(f32), f32); + const f64 = new Float64Array(2); + f64[0] = 1; + f64[1] = 2; + assert_arrays_equal(wasm.f64(f64), f64); + } + "#) + .test(); +} + +#[test] +fn import() { + test_support::project() + .file("src/lib.rs", r#" + #![feature(proc_macro)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + macro_rules! doit { + ($(($rust:ident, $js:ident, $i:ident))*) => ($( + #[wasm_bindgen(module = "./test")] + extern { + fn $js(a: &[$i]) -> Vec<$i>; + } + + #[no_mangle] + #[wasm_bindgen] + pub extern fn $rust(a: &[$i]) -> Vec<$i> { + assert_eq!(a.len(), 2); + assert_eq!(a[0], 1 as $i); + assert_eq!(a[1], 2 as $i); + $js(a) + } + )*) + } + + + doit! { + (rust_i8, js_i8, i8) + (rust_u8, js_u8, u8) + (rust_i16, js_i16, i16) + (rust_u16, js_u16, u16) + (rust_i32, js_i32, i32) + (rust_u32, js_u32, u32) + (rust_f32, js_f32, f32) + (rust_f64, js_f64, f64) + } + "#) + .file("test.ts", r#" + import * as assert from "assert"; + import * as wasm from "./out"; + + export function js_i8(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + export function js_u8(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + export function js_i16(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + export function js_u16(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + export function js_i32(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + export function js_u32(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + export function js_f32(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + export function js_f64(a: any): any { + assert.strictEqual(a.length, 2); + assert.strictEqual(a[0], 1); + assert.strictEqual(a[1], 2); + return a; + } + + function assert_arrays_equal(a: any, b: any) { + console.log(a, b); + assert.strictEqual(a.length, b.length); + assert.strictEqual(a.byteLength, b.byteLength); + for (let i = 0; i < a.length; i++) { + assert.strictEqual(a[i], b[i]); + } + } + + export function test() { + const i8 = new Int8Array(2); + i8[0] = 1; + i8[1] = 2; + assert_arrays_equal(wasm.rust_i8(i8), i8); + const u8 = new Uint8Array(2); + u8[0] = 1; + u8[1] = 2; + assert_arrays_equal(wasm.rust_u8(u8), u8); + + const i16 = new Int16Array(2); + i16[0] = 1; + i16[1] = 2; + assert_arrays_equal(wasm.rust_i16(i16), i16); + const u16 = new Uint16Array(2); + u16[0] = 1; + u16[1] = 2; + assert_arrays_equal(wasm.rust_u16(u16), u16); + + const i32 = new Int32Array(2); + i32[0] = 1; + i32[1] = 2; + assert_arrays_equal(wasm.rust_i32(i32), i32); + const u32 = new Uint32Array(2); + u32[0] = 1; + u32[1] = 2; + assert_arrays_equal(wasm.rust_u32(u32), u32); + + const f32 = new Float32Array(2); + f32[0] = 1; + f32[1] = 2; + assert_arrays_equal(wasm.rust_f32(f32), f32); + const f64 = new Float64Array(2); + f64[0] = 1; + f64[1] = 2; + assert_arrays_equal(wasm.rust_f64(f64), f64); + } + "#) + .test(); +}