diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs index 36665aba..bf74b3ab 100644 --- a/crates/backend/src/codegen.rs +++ b/crates/backend/src/codegen.rs @@ -513,6 +513,10 @@ impl ToTokens for ast::ImportType { } } + impl ::wasm_bindgen::convert::OptionIntoWasmAbi for #name { + fn none() -> Self::Abi { 0 } + } + impl ::wasm_bindgen::convert::FromWasmAbi for #name { type Abi = <::wasm_bindgen::JsValue as ::wasm_bindgen::convert::FromWasmAbi>::Abi; @@ -527,6 +531,10 @@ impl ToTokens for ast::ImportType { } } + impl ::wasm_bindgen::convert::OptionFromWasmAbi for #name { + fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } + } + impl<'a> ::wasm_bindgen::convert::IntoWasmAbi for &'a #name { type Abi = <&'a ::wasm_bindgen::JsValue as ::wasm_bindgen::convert::IntoWasmAbi>::Abi; diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index a1f6938f..7b0a39a3 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -33,6 +33,7 @@ tys! { ENUM RUST_STRUCT CHAR + OPTIONAL } #[derive(Debug)] @@ -59,6 +60,7 @@ pub enum Descriptor { Enum, RustStruct(String), Char, + Option(Box), } #[derive(Debug)] @@ -115,6 +117,7 @@ impl Descriptor { REFMUT => Descriptor::RefMut(Box::new(Descriptor::_decode(data))), SLICE => Descriptor::Slice(Box::new(Descriptor::_decode(data))), VECTOR => Descriptor::Vector(Box::new(Descriptor::_decode(data))), + OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data))), STRING => Descriptor::String, ANYREF => Descriptor::Anyref, ENUM => Descriptor::Enum, diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index c16d7511..611a71c4 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -122,20 +122,31 @@ impl<'a, 'b> Js2Rust<'a, 'b> { let i = self.arg_idx; let name = self.abi_arg(); + let (arg, optional) = match arg { + Descriptor::Option(t) => (&**t, true), + _ => (arg, false), + }; + if let Some(kind) = arg.vector_kind() { self.js_arguments .push((name.clone(), kind.js_ty().to_string())); let func = self.cx.pass_to_wasm_function(kind)?; + let val = if optional { + self.cx.expose_is_like_none(); + format!("isLikeNone({}) ? [0, 0] : {}({})", name, func, name) + } else { + format!("{}({})", func, name) + }; self.prelude(&format!( - "\ - const [ptr{i}, len{i}] = {func}({arg});\n\ - ", + "const [ptr{i}, len{i}] = {val};", i = i, - func = func, - arg = name + val = val, )); if arg.is_by_ref() { + if optional { + bail!("optional slices aren't currently supported"); + } if arg.is_mut_ref() { let get = self.cx.memview_function(kind); self.finally(&format!( @@ -165,6 +176,25 @@ impl<'a, 'b> Js2Rust<'a, 'b> { return Ok(self); } + if arg.is_anyref() { + self.js_arguments.push((name.clone(), "any".to_string())); + self.cx.expose_add_heap_object(); + if optional { + self.cx.expose_is_like_none(); + self.rust_arguments.push(format!( + "isLikeNone({0}) ? 0 : addHeapObject({0})", + name, + )); + } else { + self.rust_arguments.push(format!("addHeapObject({})", name)); + } + return Ok(self); + } + + if optional { + bail!("unsupported optional argument to rust function {:?}", arg); + } + if let Some(s) = arg.rust_struct() { self.js_arguments.push((name.clone(), s.to_string())); @@ -262,11 +292,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.js_arguments.push((name.clone(), "string".to_string())); self.rust_arguments.push(format!("{}.codePointAt(0)", name)) } - Descriptor::Anyref => { - self.js_arguments.push((name.clone(), "any".to_string())); - self.cx.expose_add_heap_object(); - self.rust_arguments.push(format!("addHeapObject({})", name)); - } _ => bail!("unsupported argument to rust function {:?}", arg), } Ok(self) @@ -282,16 +307,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> { } }; - if ty.is_ref_anyref() { - self.ret_ty = "any".to_string(); - self.cx.expose_get_object(); - self.ret_expr = format!("return getObject(RET);"); - return Ok(self); - } - - if ty.is_by_ref() { - bail!("cannot return references from Rust to JS yet") - } + let (ty, optional) = match ty { + Descriptor::Option(t) => (&**t, true), + _ => (ty, false), + }; if let Some(ty) = ty.vector_kind() { self.ret_ty = ty.js_ty().to_string(); @@ -307,16 +326,42 @@ impl<'a, 'b> Js2Rust<'a, 'b> { const mem = getUint32Memory();\n\ const ptr = mem[retptr / 4];\n\ const len = mem[retptr / 4 + 1];\n\ + {guard} const realRet = {}(ptr, len).slice();\n\ wasm.__wbindgen_free(ptr, len * {});\n\ return realRet;\n\ ", f, - ty.size() + ty.size(), + guard = if optional { "if (ptr === 0) return;" } else { "" }, ); return Ok(self); } + // No need to worry about `optional` here, the abi representation means + // that `takeObject` will naturally pluck out `undefined`. + if ty.is_anyref() { + self.ret_ty = "any".to_string(); + self.cx.expose_take_object(); + self.ret_expr = format!("return takeObject(RET);"); + return Ok(self); + } + + if optional { + bail!("unsupported optional argument to rust function {:?}", ty); + } + + if ty.is_ref_anyref() { + self.ret_ty = "any".to_string(); + self.cx.expose_get_object(); + self.ret_expr = format!("return getObject(RET);"); + return Ok(self); + } + + if ty.is_by_ref() { + bail!("cannot return references from Rust to JS yet") + } + if let Some(name) = ty.rust_struct() { self.ret_ty = name.to_string(); self.ret_expr = format!("return {name}.__construct(RET);", name = name); @@ -360,11 +405,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> { self.ret_ty = "string".to_string(); self.ret_expr = format!("return String.fromCodePoint(RET);") } - Descriptor::Anyref => { - self.ret_ty = "any".to_string(); - self.cx.expose_take_object(); - self.ret_expr = format!("return takeObject(RET);"); - } _ => bail!("unsupported return from Rust to JS {:?}", ty), } Ok(self) diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index 84e2f0b2..393e8be5 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -53,6 +53,8 @@ pub struct SubContext<'a, 'b: 'a> { pub cx: &'a mut Context<'b>, } +const INITIAL_SLAB_VALUES: &[&str] = &["undefined", "null", "true", "false"]; + impl<'a> Context<'a> { fn export(&mut self, name: &str, contents: &str, comments: Option) { let contents = contents.trim(); @@ -183,28 +185,6 @@ impl<'a> Context<'a> { )) })?; - self.bind("__wbindgen_undefined_new", &|me| { - me.expose_add_heap_object(); - Ok(String::from( - " - function() { - return addHeapObject(undefined); - } - ", - )) - })?; - - self.bind("__wbindgen_null_new", &|me| { - me.expose_add_heap_object(); - Ok(String::from( - " - function() { - return addHeapObject(null); - } - ", - )) - })?; - self.bind("__wbindgen_is_null", &|me| { me.expose_get_object(); Ok(String::from( @@ -227,17 +207,6 @@ impl<'a> Context<'a> { )) })?; - self.bind("__wbindgen_boolean_new", &|me| { - me.expose_add_heap_object(); - Ok(String::from( - " - function(v) { - return addHeapObject(v === 1); - } - ", - )) - })?; - self.bind("__wbindgen_boolean_get", &|me| { me.expose_get_object(); Ok(String::from( @@ -782,14 +751,16 @@ impl<'a> Context<'a> { " function dropRef(idx) {{ {} - let obj = slab[idx >> 1]; + idx = idx >> 1; + if (idx < {}) return; + let obj = slab[idx]; {} // If we hit 0 then free up our space in the slab - slab[idx >> 1] = slab_next; - slab_next = idx >> 1; + slab[idx] = slab_next; + slab_next = idx; }} ", - validate_owned, dec_ref + validate_owned, INITIAL_SLAB_VALUES.len(), dec_ref )); } @@ -820,12 +791,9 @@ impl<'a> Context<'a> { if !self.exposed_globals.insert("slab") { return; } - let initial_values = [ - "{ obj: null }", - "{ obj: undefined }", - "{ obj: true }", - "{ obj: false }", - ]; + let initial_values = INITIAL_SLAB_VALUES.iter() + .map(|s| format!("{{ obj: {} }}", s)) + .collect::>(); self.global(&format!("const slab = [{}];", initial_values.join(", "))); if self.config.debug { self.export( @@ -1575,6 +1543,17 @@ impl<'a> Context<'a> { name } + fn expose_is_like_none(&mut self) { + if !self.exposed_globals.insert("is_like_none") { + return + } + self.global(" + function isLikeNone(x) { + return x === undefined || x === null; + } + "); + } + fn gc(&mut self) -> Result<(), Error> { let module = mem::replace(self.module, Module::default()); let wasm_bytes = parity_wasm::serialize(module)?; diff --git a/crates/cli-support/src/js/rust2js.rs b/crates/cli-support/src/js/rust2js.rs index be8f8b90..965b685a 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -82,25 +82,36 @@ impl<'a, 'b> Rust2Js<'a, 'b> { fn argument(&mut self, arg: &Descriptor) -> Result<(), Error> { let abi = self.shim_argument(); + let (arg, optional) = match arg { + Descriptor::Option(t) => (&**t, true), + _ => (arg, false), + }; + if let Some(ty) = arg.vector_kind() { let abi2 = self.shim_argument(); let f = self.cx.expose_get_vector_from_wasm(ty); self.prelude(&format!( - "let v{0} = {func}({0}, {1});", + "let v{0} = {prefix}{func}({0}, {1});", abi, abi2, - func = f + func = f, + prefix = if optional { format!("{} == 0 ? undefined : ", abi) } else { String::new() }, )); if !arg.is_by_ref() { self.prelude(&format!( "\ - v{0} = v{0}.slice();\n\ - wasm.__wbindgen_free({0}, {1} * {size});\ + {start} + v{0} = v{0}.slice(); + wasm.__wbindgen_free({0}, {1} * {size}); + {end}\ ", abi, abi2, - size = ty.size() + size = ty.size(), + start = if optional { format!("if ({} !== 0) {{", abi) } else { String::new() }, + end = if optional { "}" } else { "" }, + )); self.cx.require_internal_export("__wbindgen_free")?; } @@ -108,6 +119,18 @@ impl<'a, 'b> Rust2Js<'a, 'b> { return Ok(()); } + // No need to special case `optional` here because `takeObject` will + // naturally work. + if arg.is_anyref() { + self.cx.expose_take_object(); + self.js_arguments.push(format!("takeObject({})", abi)); + return Ok(()) + } + + if optional { + bail!("unsupported optional argument {:?}", arg); + } + if let Some(signed) = arg.get_64bit() { let f = if signed { self.cx.expose_int64_cvt_shim() @@ -233,10 +256,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> { ref d if d.is_number() => abi, Descriptor::Boolean => format!("{} !== 0", abi), Descriptor::Char => format!("String.fromCodePoint({})", abi), - Descriptor::Anyref => { - self.cx.expose_take_object(); - format!("takeObject({})", abi) - } ref d if d.is_ref_anyref() => { self.cx.expose_get_object(); format!("getObject({})", abi) @@ -258,6 +277,10 @@ impl<'a, 'b> Rust2Js<'a, 'b> { return Ok(()); } }; + let (ty, optional) = match ty { + Descriptor::Option(t) => (&**t, true), + _ => (ty, false), + }; if ty.is_by_ref() { bail!("cannot return a reference from JS to Rust") } @@ -265,17 +288,43 @@ impl<'a, 'b> Rust2Js<'a, 'b> { let f = self.cx.pass_to_wasm_function(ty)?; self.cx.expose_uint32_memory(); self.shim_arguments.insert(0, "ret".to_string()); + let mut prelude = String::new(); + let expr = if optional { + prelude.push_str("const val = JS;"); + self.cx.expose_is_like_none(); + format!("isLikeNone(val) ? [0, 0] : {}(val)", f) + } else { + format!("{}(JS)", f) + }; self.ret_expr = format!( "\ - const [retptr, retlen] = {}(JS);\n\ + {} + const [retptr, retlen] = {}; const mem = getUint32Memory(); mem[ret / 4] = retptr; mem[ret / 4 + 1] = retlen; ", - f + prelude, + expr ); return Ok(()); } + if ty.is_anyref() { + self.cx.expose_add_heap_object(); + if optional { + self.cx.expose_is_like_none(); + self.ret_expr = " + const val = JS; + return isLikeNone(val) ? 0 : addHeapObject(val); + ".to_string(); + } else { + self.ret_expr = "return addHeapObject(JS);".to_string() + } + return Ok(()) + } + if optional { + bail!("unsupported optional return type {:?}", ty); + } if ty.is_number() { self.ret_expr = "return JS;".to_string(); return Ok(()); @@ -324,10 +373,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> { self.ret_expr = match *ty { Descriptor::Boolean => "return JS ? 1 : 0;".to_string(), Descriptor::Char => "return JS.codePointAt(0);".to_string(), - Descriptor::Anyref => { - self.cx.expose_add_heap_object(); - "return addHeapObject(JS);".to_string() - } _ => bail!("unimplemented return from JS to Rust: {:?}", ty), }; Ok(()) diff --git a/crates/web-sys/tests/all/headers.rs b/crates/web-sys/tests/all/headers.rs index 82090c3d..3c4df56f 100644 --- a/crates/web-sys/tests/all/headers.rs +++ b/crates/web-sys/tests/all/headers.rs @@ -6,14 +6,31 @@ fn headers() { .file( "src/lib.rs", r#" - #![feature(use_extern_macros)] + #![feature(use_extern_macros, wasm_import_module)] extern crate wasm_bindgen; use wasm_bindgen::prelude::*; extern crate web_sys; #[wasm_bindgen] - pub fn test_headers(_headers: &web_sys::Headers) { - // empty for now... + pub fn test_headers(headers: &web_sys::Headers) { + assert_eq!(headers.get("foo").unwrap(), None); + assert_eq!( + headers.get("content-type").unwrap(), + Some("text/plain".to_string()), + ); + assert_eq!( + headers.get("Content-Type").unwrap(), + Some("text/plain".to_string()), + ); + assert!(headers.get("").is_err()); + assert!(headers.set("", "").is_err()); + assert!(headers.set("x", "").is_ok()); + assert_eq!(headers.get("x").unwrap(), Some(String::new())); + assert!(headers.delete("x").is_ok()); + assert_eq!(headers.get("x").unwrap(), None); + assert!(headers.append("a", "y").is_ok()); + assert!(headers.append("a", "z").is_ok()); + assert_eq!(headers.get("a").unwrap(), Some("y, z".to_string())); } "#, ) diff --git a/crates/webidl/src/util.rs b/crates/webidl/src/util.rs index 0b155558..a2293c86 100644 --- a/crates/webidl/src/util.rs +++ b/crates/webidl/src/util.rs @@ -125,10 +125,6 @@ impl<'a> FirstPassRecord<'a> { ty: &webidl::ast::Type, pos: TypePosition, ) -> Option { - // nullable types are not yet supported (see issue #14) - if ty.nullable { - return None; - } let array = |base_ty: &str| { match pos { TypePosition::Argument => { @@ -140,7 +136,7 @@ impl<'a> FirstPassRecord<'a> { } }; - Some(match ty.kind { + let base_ty = match ty.kind { // `any` becomes `::wasm_bindgen::JsValue`. webidl::ast::TypeKind::Any => { simple_path_ty(vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")]) @@ -223,7 +219,25 @@ impl<'a> FirstPassRecord<'a> { | webidl::ast::TypeKind::Union(_) => { return None; } - }) + }; + if ty.nullable { + let arguments = syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: FromIterator::from_iter(vec![ + syn::GenericArgument::Type(base_ty), + ]), + gt_token: Default::default(), + }); + + let ident = raw_ident("Option"); + let seg = syn::PathSegment { ident, arguments }; + let path: syn::Path = seg.into(); + let ty = syn::TypePath { qself: None, path }; + Some(ty.into()) + } else { + Some(base_ty) + } } fn webidl_arguments_to_syn_arg_captured<'b, I>( diff --git a/guide/src/feature-reference.md b/guide/src/feature-reference.md index d361f7f7..53183ad9 100644 --- a/guide/src/feature-reference.md +++ b/guide/src/feature-reference.md @@ -28,6 +28,7 @@ are: * Borrowed exported structs (`&Foo` or `&mut Bar`) * The `JsValue` type and `&JsValue` (not mutable references) * Vectors and slices of supported integer types and of the `JsValue` type. +* Optional vectors/slices All of the above can also be returned except borrowed references. Passing `Vec` as an argument to a function is not currently supported. Strings are diff --git a/guide/src/reference.md b/guide/src/reference.md index 56022337..6eecc9d2 100644 --- a/guide/src/reference.md +++ b/guide/src/reference.md @@ -2,20 +2,16 @@ The table below provides an overview of all the types that wasm-bindgen can send/receive across the wasm ABI boundary. -| Type | `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | -|:---:|:---:|:---:|:---:|:---:| -| `str` | No | Yes | No | Yes | -| `char` | Yes | No | No | Yes | -| `bool` | Yes | No | No | Yes | -| `JsValue` | Yes | Yes | Yes | Yes | -| `Box<[JsValue]>` | Yes | No | No | Yes | -| `*const T` | Yes | No | No | Yes | -| `*mut T` | Yes | No | No | Yes | -| `u8` `i8` `u16` `i16` `u64` `i64` `isize` `size` | Yes | No | No | Yes | -| `u32` `i32` `f32` `f64` | Yes | Yes | Yes | Yes | -| `Box<[u8]>` `Box<[i8]>` `Box<[u16]>` `Box<[i16]>` `Box<[u32]>` `Box<[i32]>` `Box<[u64]>` `Box<[i64]>` | Yes | No | No | Yes | -| `Box<[f32]>` `Box<[f64]>` | Yes | No | No | Yes | -| `[u8]` `[i8]` `[u16]` `[i16]` `[u32]` `[i32]` `[u64]` `[i64]` | No | Yes | Yes | No | -| `&[u8]` `&mut[u8]` `&[i8]` `&mut[i8]` `&[u16]` `&mut[u16]` `&[i16]` `&mut[i16]` `&[u32]` `&mut[u32]` `&[i32]` `&mut[i32]` `&[u64]` `&mut[u64]` `&[i64]` `&mut[i64]` | No | No | No | Yes | -| `[f32]` `[f64]` | No | Yes | Yes | No | -| `&[f32]` `&mut[f32]` `&[f64]` `&mut[f64]` | No | No | No | Yes | \ No newline at end of file +| Type | `T` parameter | `&T` parameter | `&mut T` parameter | `T` return value | `Option` parameter | `Option` return value | +|:---:|:---:|:---:|:---:|:---:|:---:| +| `str` | No | Yes | No | Yes | Yes | No | +| `char` | Yes | No | No | Yes | No | No | +| `bool` | Yes | No | No | Yes | No | No | +| `JsValue` | Yes | Yes | Yes | Yes | No | No | +| `Box<[JsValue]>` | Yes | No | No | Yes | Yes | yes | +| `*const T` | Yes | No | No | Yes | No | No | +| `*mut T` | Yes | No | No | Yes | No | No | +| `u8` `i8` `u16` `i16` `u64` `i64` `isize` `size` | Yes | No | No | Yes | No | No | +| `u32` `i32` `f32` `f64` | Yes | Yes | Yes | Yes | No | No | +| `Box<[u8]>` `Box<[i8]>` `Box<[u16]>` `Box<[i16]>` `Box<[u32]>` `Box<[i32]>` `Box<[u64]>` `Box<[i64]>` `Box<[f32]>` `Box<[f64]`> | Yes | No | No | Yes | Yes | Yes | +| `[u8]` `[i8]` `[u16]` `[i16]` `[u32]` `[i32]` `[u64]` `[i64]` `[f32]` `[f64]` | No | Yes | Yes | No | Yes | No | diff --git a/src/convert.rs b/src/convert.rs deleted file mode 100644 index 127d109e..00000000 --- a/src/convert.rs +++ /dev/null @@ -1,610 +0,0 @@ -//! This is mostly an internal module, no stability guarantees are provided. Use -//! at your own risk. - -use core::char; -use core::mem::{self, ManuallyDrop}; -use core::ops::{Deref, DerefMut}; -use core::slice; -use core::str; - -use describe::*; -use {throw, JsValue}; - -#[cfg(feature = "std")] -use std::prelude::v1::*; - -/// A trait for anything that can be converted into a type that can cross the -/// wasm ABI directly, eg `u32` or `f64`. -/// -/// This is the opposite operation as `FromWasmAbi` and `Ref[Mut]FromWasmAbi`. -pub trait IntoWasmAbi: WasmDescribe { - /// The wasm ABI type that this converts into when crossing the ABI - /// boundary. - type Abi: WasmAbi; - - /// Convert `self` into `Self::Abi` so that it can be sent across the wasm - /// ABI boundary. - fn into_abi(self, extra: &mut Stack) -> Self::Abi; -} - -/// A trait for anything that can be recovered by-value from the wasm ABI -/// boundary, eg a Rust `u8` can be recovered from the wasm ABI `u32` type. -/// -/// This is the by-value variant of the opposite operation as `IntoWasmAbi`. -pub trait FromWasmAbi: WasmDescribe { - /// The wasm ABI type that this converts from when coming back out from the - /// ABI boundary. - type Abi: WasmAbi; - - /// Recover a `Self` from `Self::Abi`. - /// - /// # Safety - /// - /// This is only safe to call when -- and implementations may assume that -- - /// the supplied `Self::Abi` was previously generated by a call to `::into_abi()` or the moral equivalent in JS. - unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self; -} - -/// A trait for anything that can be recovered as some sort of shared reference -/// from the wasm ABI boundary. -/// -/// This is the shared reference variant of the opposite operation as -/// `IntoWasmAbi`. -pub trait RefFromWasmAbi: WasmDescribe { - /// The wasm ABI type references to `Self` are recovered from. - type Abi: WasmAbi; - - /// The type that holds the reference to `Self` for the duration of the - /// invocation of the function that has an `&Self` parameter. This is - /// required to ensure that the lifetimes don't persist beyond one function - /// call, and so that they remain anonymous. - type Anchor: Deref; - - /// Recover a `Self::Anchor` from `Self::Abi`. - /// - /// # Safety - /// - /// Same as `FromWasmAbi::from_abi`. - unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor; -} - -pub trait RefMutFromWasmAbi: WasmDescribe { - type Abi: WasmAbi; - type Anchor: DerefMut; - unsafe fn ref_mut_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor; -} - -pub trait Stack { - fn push(&mut self, bits: u32); -} - -/// An unsafe trait which represents types that are ABI-safe to pass via wasm -/// arguments. -/// -/// This is an unsafe trait to implement as there's no guarantee the type is -/// actually safe to transfer across the was boundary, it's up to you to -/// guarantee this so codegen works correctly. -pub unsafe trait WasmAbi {} - -unsafe impl WasmAbi for u32 {} -unsafe impl WasmAbi for i32 {} -unsafe impl WasmAbi for f32 {} -unsafe impl WasmAbi for f64 {} - -#[repr(C)] -pub struct WasmSlice { - pub ptr: u32, - pub len: u32, -} - -unsafe impl WasmAbi for WasmSlice {} - -macro_rules! simple { - ($($t:tt)*) => ($( - impl IntoWasmAbi for $t { - type Abi = $t; - - #[inline] - fn into_abi(self, _extra: &mut Stack) -> $t { self } - } - - impl FromWasmAbi for $t { - type Abi = $t; - - #[inline] - unsafe fn from_abi(js: $t, _extra: &mut Stack) -> $t { js } - } - )*) -} - -simple!(u32 i32 f32 f64); - -macro_rules! sixtyfour { - ($($t:tt)*) => ($( - impl IntoWasmAbi for $t { - type Abi = WasmSlice; - - #[inline] - fn into_abi(self, _extra: &mut Stack) -> WasmSlice { - WasmSlice { - ptr: self as u32, - len: (self >> 32) as u32, - } - } - } - - impl FromWasmAbi for $t { - type Abi = WasmSlice; - - #[inline] - unsafe fn from_abi(js: WasmSlice, _extra: &mut Stack) -> $t { - (js.ptr as $t) | ((js.len as $t) << 32) - } - } - )*) -} - -sixtyfour!(i64 u64); - -macro_rules! as_u32 { - ($($t:tt)*) => ($( - impl IntoWasmAbi for $t { - type Abi = u32; - - #[inline] - fn into_abi(self, _extra: &mut Stack) -> u32 { self as u32 } - } - - impl FromWasmAbi for $t { - type Abi = u32; - - #[inline] - unsafe fn from_abi(js: u32, _extra: &mut Stack) -> $t { js as $t } - } - )*) -} - -as_u32!(i8 u8 i16 u16 isize usize); - -impl IntoWasmAbi for bool { - type Abi = u32; - - #[inline] - fn into_abi(self, _extra: &mut Stack) -> u32 { - self as u32 - } -} - -impl FromWasmAbi for bool { - type Abi = u32; - - #[inline] - unsafe fn from_abi(js: u32, _extra: &mut Stack) -> bool { - js != 0 - } -} - -impl IntoWasmAbi for char { - type Abi = u32; - - #[inline] - fn into_abi(self, _extra: &mut Stack) -> u32 { - self as u32 - } -} - -impl FromWasmAbi for char { - type Abi = u32; - - #[inline] - unsafe fn from_abi(js: u32, _extra: &mut Stack) -> char { - char::from_u32_unchecked(js) - } -} - -impl IntoWasmAbi for *const T { - type Abi = u32; - - fn into_abi(self, _extra: &mut Stack) -> u32 { - self as u32 - } -} - -impl FromWasmAbi for *const T { - type Abi = u32; - - unsafe fn from_abi(js: u32, _extra: &mut Stack) -> *const T { - js as *const T - } -} - -impl IntoWasmAbi for *mut T { - type Abi = u32; - - fn into_abi(self, _extra: &mut Stack) -> u32 { - self as u32 - } -} - -impl FromWasmAbi for *mut T { - type Abi = u32; - - unsafe fn from_abi(js: u32, _extra: &mut Stack) -> *mut T { - js as *mut T - } -} - -macro_rules! vectors { - ($($t:ident)*) => ($( - #[cfg(feature = "std")] - impl IntoWasmAbi for Box<[$t]> { - type Abi = WasmSlice; - - #[inline] - fn into_abi(self, extra: &mut Stack) -> WasmSlice { - let ptr = self.as_ptr(); - let len = self.len(); - mem::forget(self); - WasmSlice { - ptr: ptr.into_abi(extra), - len: len as u32, - } - } - } - - #[cfg(feature = "std")] - impl FromWasmAbi for Box<[$t]> { - type Abi = WasmSlice; - - #[inline] - unsafe fn from_abi(js: WasmSlice, extra: &mut Stack) -> Self { - let ptr = <*mut $t>::from_abi(js.ptr, extra); - let len = js.len as usize; - Vec::from_raw_parts(ptr, len, len).into_boxed_slice() - } - } - - impl<'a> IntoWasmAbi for &'a [$t] { - type Abi = WasmSlice; - - #[inline] - fn into_abi(self, extra: &mut Stack) -> WasmSlice { - WasmSlice { - ptr: self.as_ptr().into_abi(extra), - len: self.len() as u32, - } - } - } - - impl<'a> IntoWasmAbi for &'a mut [$t] { - type Abi = WasmSlice; - - #[inline] - fn into_abi(self, extra: &mut Stack) -> WasmSlice { - (&*self).into_abi(extra) - } - } - - impl RefFromWasmAbi for [$t] { - type Abi = WasmSlice; - type Anchor = &'static [$t]; - - #[inline] - unsafe fn ref_from_abi(js: WasmSlice, extra: &mut Stack) -> &'static [$t] { - slice::from_raw_parts( - <*const $t>::from_abi(js.ptr, extra), - js.len as usize, - ) - } - } - - impl RefMutFromWasmAbi for [$t] { - type Abi = WasmSlice; - type Anchor = &'static mut [$t]; - - #[inline] - unsafe fn ref_mut_from_abi(js: WasmSlice, extra: &mut Stack) - -> &'static mut [$t] - { - slice::from_raw_parts_mut( - <*mut $t>::from_abi(js.ptr, extra), - js.len as usize, - ) - } - } - )*) -} - -vectors! { - u8 i8 u16 i16 u32 i32 u64 i64 f32 f64 -} - -if_std! { - impl IntoWasmAbi for Vec where Box<[T]>: IntoWasmAbi { - type Abi = as IntoWasmAbi>::Abi; - - fn into_abi(self, extra: &mut Stack) -> Self::Abi { - self.into_boxed_slice().into_abi(extra) - } - } - - impl FromWasmAbi for Vec where Box<[T]>: FromWasmAbi { - type Abi = as FromWasmAbi>::Abi; - - unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self { - >::from_abi(js, extra).into() - } - } - - impl IntoWasmAbi for String { - type Abi = as IntoWasmAbi>::Abi; - - #[inline] - fn into_abi(self, extra: &mut Stack) -> Self::Abi { - self.into_bytes().into_abi(extra) - } - } - - impl FromWasmAbi for String { - type Abi = as FromWasmAbi>::Abi; - - #[inline] - unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self { - String::from_utf8_unchecked(>::from_abi(js, extra)) - } - } -} - -impl<'a> IntoWasmAbi for &'a str { - type Abi = <&'a [u8] as IntoWasmAbi>::Abi; - - #[inline] - fn into_abi(self, extra: &mut Stack) -> Self::Abi { - self.as_bytes().into_abi(extra) - } -} - -impl RefFromWasmAbi for str { - type Abi = <[u8] as RefFromWasmAbi>::Abi; - type Anchor = &'static str; - - #[inline] - unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor { - str::from_utf8_unchecked(<[u8]>::ref_from_abi(js, extra)) - } -} - -impl IntoWasmAbi for JsValue { - type Abi = u32; - - #[inline] - fn into_abi(self, _extra: &mut Stack) -> u32 { - let ret = self.idx; - mem::forget(self); - return ret; - } -} - -impl FromWasmAbi for JsValue { - type Abi = u32; - - #[inline] - unsafe fn from_abi(js: u32, _extra: &mut Stack) -> JsValue { - JsValue { idx: js } - } -} - -impl<'a> IntoWasmAbi for &'a JsValue { - type Abi = u32; - - #[inline] - fn into_abi(self, _extra: &mut Stack) -> u32 { - self.idx - } -} - -impl RefFromWasmAbi for JsValue { - type Abi = u32; - type Anchor = ManuallyDrop; - - #[inline] - unsafe fn ref_from_abi(js: u32, _extra: &mut Stack) -> Self::Anchor { - ManuallyDrop::new(JsValue { idx: js }) - } -} - -if_std! { - impl IntoWasmAbi for Box<[JsValue]> { - type Abi = WasmSlice; - - #[inline] - fn into_abi(self, extra: &mut Stack) -> WasmSlice { - let ptr = self.as_ptr(); - let len = self.len(); - mem::forget(self); - WasmSlice { - ptr: ptr.into_abi(extra), - len: len as u32, - } - } - } - - impl FromWasmAbi for Box<[JsValue]> { - type Abi = WasmSlice; - - #[inline] - unsafe fn from_abi(js: WasmSlice, extra: &mut Stack) -> Self { - let ptr = <*mut JsValue>::from_abi(js.ptr, extra); - let len = js.len as usize; - Vec::from_raw_parts(ptr, len, len).into_boxed_slice() - } - } -} - -pub struct GlobalStack { - next: usize, -} - -impl GlobalStack { - #[inline] - pub unsafe fn new() -> GlobalStack { - GlobalStack { next: 0 } - } -} - -impl Stack for GlobalStack { - #[inline] - fn push(&mut self, val: u32) { - use __rt::{ - __wbindgen_global_argument_ptr as global_ptr, - GLOBAL_STACK_CAP, - }; - unsafe { - assert!(self.next < GLOBAL_STACK_CAP); - *global_ptr().offset(self.next as isize) = val; - self.next += 1; - } - } -} - -macro_rules! stack_closures { - ($( ($($var:ident)*) )*) => ($( - impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a (Fn($($var),*) -> R + 'b) - where $($var: FromWasmAbi,)* - R: IntoWasmAbi - { - type Abi = u32; - - fn into_abi(self, extra: &mut Stack) -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( - a: usize, - b: usize, - $($var: <$var as FromWasmAbi>::Abi),* - ) -> ::Abi { - if a == 0 { - throw("closure invoked recursively or destroyed already"); - } - let f: &Fn($($var),*) -> R = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*).into_abi(&mut GlobalStack::new()) - } - unsafe { - let (a, b): (usize, usize) = mem::transmute(self); - extra.push(a as u32); - extra.push(b as u32); - invoke::<$($var,)* R> as u32 - } - } - } - - impl<'a, 'b, $($var,)*> IntoWasmAbi for &'a (Fn($($var),*) + 'b) - where $($var: FromWasmAbi,)* - { - type Abi = u32; - - fn into_abi(self, extra: &mut Stack) -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* >( - a: usize, - b: usize, - $($var: <$var as FromWasmAbi>::Abi),* - ) { - if a == 0 { - throw("closure invoked recursively or destroyed already"); - } - let f: &Fn($($var),*) = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*) - } - unsafe { - let (a, b): (usize, usize) = mem::transmute(self); - extra.push(a as u32); - extra.push(b as u32); - invoke::<$($var,)*> as u32 - } - } - } - - impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a mut (FnMut($($var),*) -> R + 'b) - where $($var: FromWasmAbi,)* - R: IntoWasmAbi - { - type Abi = u32; - - fn into_abi(self, extra: &mut Stack) -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( - a: usize, - b: usize, - $($var: <$var as FromWasmAbi>::Abi),* - ) -> ::Abi { - if a == 0 { - throw("closure invoked recursively or destroyed already"); - } - let f: &mut FnMut($($var),*) -> R = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*).into_abi(&mut GlobalStack::new()) - } - unsafe { - let (a, b): (usize, usize) = mem::transmute(self); - extra.push(a as u32); - extra.push(b as u32); - invoke::<$($var,)* R> as u32 - } - } - } - - impl<'a, 'b, $($var,)*> IntoWasmAbi for &'a mut (FnMut($($var),*) + 'b) - where $($var: FromWasmAbi,)* - { - type Abi = u32; - - fn into_abi(self, extra: &mut Stack) -> u32 { - #[allow(non_snake_case)] - unsafe extern fn invoke<$($var: FromWasmAbi,)* >( - a: usize, - b: usize, - $($var: <$var as FromWasmAbi>::Abi),* - ) { - if a == 0 { - throw("closure invoked recursively or destroyed already"); - } - let f: &mut FnMut($($var),*) = mem::transmute((a, b)); - let mut _stack = GlobalStack::new(); - $( - let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); - )* - f($($var),*) - } - unsafe { - let (a, b): (usize, usize) = mem::transmute(self); - extra.push(a as u32); - extra.push(b as u32); - invoke::<$($var,)*> as u32 - } - } - } - )*) -} - -stack_closures! { - () - (A) - (A B) - (A B C) - (A B C D) - (A B C D E) - (A B C D E F) - (A B C D E F G) -} diff --git a/src/convert/closures.rs b/src/convert/closures.rs new file mode 100644 index 00000000..98512860 --- /dev/null +++ b/src/convert/closures.rs @@ -0,0 +1,146 @@ +use core::mem; + +use convert::{FromWasmAbi, IntoWasmAbi, GlobalStack, Stack}; +use throw; + +macro_rules! stack_closures { + ($( ($($var:ident)*) )*) => ($( + impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a (Fn($($var),*) -> R + 'b) + where $($var: FromWasmAbi,)* + R: IntoWasmAbi + { + type Abi = u32; + + fn into_abi(self, extra: &mut Stack) -> u32 { + #[allow(non_snake_case)] + unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( + a: usize, + b: usize, + $($var: <$var as FromWasmAbi>::Abi),* + ) -> ::Abi { + if a == 0 { + throw("closure invoked recursively or destroyed already"); + } + let f: &Fn($($var),*) -> R = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*).into_abi(&mut GlobalStack::new()) + } + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + extra.push(a as u32); + extra.push(b as u32); + invoke::<$($var,)* R> as u32 + } + } + } + + impl<'a, 'b, $($var,)*> IntoWasmAbi for &'a (Fn($($var),*) + 'b) + where $($var: FromWasmAbi,)* + { + type Abi = u32; + + fn into_abi(self, extra: &mut Stack) -> u32 { + #[allow(non_snake_case)] + unsafe extern fn invoke<$($var: FromWasmAbi,)* >( + a: usize, + b: usize, + $($var: <$var as FromWasmAbi>::Abi),* + ) { + if a == 0 { + throw("closure invoked recursively or destroyed already"); + } + let f: &Fn($($var),*) = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*) + } + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + extra.push(a as u32); + extra.push(b as u32); + invoke::<$($var,)*> as u32 + } + } + } + + impl<'a, 'b, $($var,)* R> IntoWasmAbi for &'a mut (FnMut($($var),*) -> R + 'b) + where $($var: FromWasmAbi,)* + R: IntoWasmAbi + { + type Abi = u32; + + fn into_abi(self, extra: &mut Stack) -> u32 { + #[allow(non_snake_case)] + unsafe extern fn invoke<$($var: FromWasmAbi,)* R: IntoWasmAbi>( + a: usize, + b: usize, + $($var: <$var as FromWasmAbi>::Abi),* + ) -> ::Abi { + if a == 0 { + throw("closure invoked recursively or destroyed already"); + } + let f: &mut FnMut($($var),*) -> R = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*).into_abi(&mut GlobalStack::new()) + } + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + extra.push(a as u32); + extra.push(b as u32); + invoke::<$($var,)* R> as u32 + } + } + } + + impl<'a, 'b, $($var,)*> IntoWasmAbi for &'a mut (FnMut($($var),*) + 'b) + where $($var: FromWasmAbi,)* + { + type Abi = u32; + + fn into_abi(self, extra: &mut Stack) -> u32 { + #[allow(non_snake_case)] + unsafe extern fn invoke<$($var: FromWasmAbi,)* >( + a: usize, + b: usize, + $($var: <$var as FromWasmAbi>::Abi),* + ) { + if a == 0 { + throw("closure invoked recursively or destroyed already"); + } + let f: &mut FnMut($($var),*) = mem::transmute((a, b)); + let mut _stack = GlobalStack::new(); + $( + let $var = <$var as FromWasmAbi>::from_abi($var, &mut _stack); + )* + f($($var),*) + } + unsafe { + let (a, b): (usize, usize) = mem::transmute(self); + extra.push(a as u32); + extra.push(b as u32); + invoke::<$($var,)*> as u32 + } + } + } + )*) +} + +stack_closures! { + () + (A) + (A B) + (A B C) + (A B C D) + (A B C D E) + (A B C D E F) + (A B C D E F G) +} + diff --git a/src/convert/impls.rs b/src/convert/impls.rs new file mode 100644 index 00000000..0cdc6cd1 --- /dev/null +++ b/src/convert/impls.rs @@ -0,0 +1,204 @@ +use core::char; +use core::mem::{self, ManuallyDrop}; + +use convert::slices::WasmSlice; +use convert::{Stack, FromWasmAbi, IntoWasmAbi, RefFromWasmAbi}; +use convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; +use JsValue; + +macro_rules! simple { + ($($t:tt)*) => ($( + impl IntoWasmAbi for $t { + type Abi = $t; + + #[inline] + fn into_abi(self, _extra: &mut Stack) -> $t { self } + } + + impl FromWasmAbi for $t { + type Abi = $t; + + #[inline] + unsafe fn from_abi(js: $t, _extra: &mut Stack) -> $t { js } + } + )*) +} + +simple!(u32 i32 f32 f64); + +macro_rules! sixtyfour { + ($($t:tt)*) => ($( + impl IntoWasmAbi for $t { + type Abi = WasmSlice; + + #[inline] + fn into_abi(self, _extra: &mut Stack) -> WasmSlice { + WasmSlice { + ptr: self as u32, + len: (self >> 32) as u32, + } + } + } + + impl FromWasmAbi for $t { + type Abi = WasmSlice; + + #[inline] + unsafe fn from_abi(js: WasmSlice, _extra: &mut Stack) -> $t { + (js.ptr as $t) | ((js.len as $t) << 32) + } + } + )*) +} + +sixtyfour!(i64 u64); + +macro_rules! as_u32 { + ($($t:tt)*) => ($( + impl IntoWasmAbi for $t { + type Abi = u32; + + #[inline] + fn into_abi(self, _extra: &mut Stack) -> u32 { self as u32 } + } + + impl FromWasmAbi for $t { + type Abi = u32; + + #[inline] + unsafe fn from_abi(js: u32, _extra: &mut Stack) -> $t { js as $t } + } + )*) +} + +as_u32!(i8 u8 i16 u16 isize usize); + +impl IntoWasmAbi for bool { + type Abi = u32; + + #[inline] + fn into_abi(self, _extra: &mut Stack) -> u32 { + self as u32 + } +} + +impl FromWasmAbi for bool { + type Abi = u32; + + #[inline] + unsafe fn from_abi(js: u32, _extra: &mut Stack) -> bool { + js != 0 + } +} + +impl IntoWasmAbi for char { + type Abi = u32; + + #[inline] + fn into_abi(self, _extra: &mut Stack) -> u32 { + self as u32 + } +} + +impl FromWasmAbi for char { + type Abi = u32; + + #[inline] + unsafe fn from_abi(js: u32, _extra: &mut Stack) -> char { + char::from_u32_unchecked(js) + } +} + +impl IntoWasmAbi for *const T { + type Abi = u32; + + fn into_abi(self, _extra: &mut Stack) -> u32 { + self as u32 + } +} + +impl FromWasmAbi for *const T { + type Abi = u32; + + unsafe fn from_abi(js: u32, _extra: &mut Stack) -> *const T { + js as *const T + } +} + +impl IntoWasmAbi for *mut T { + type Abi = u32; + + fn into_abi(self, _extra: &mut Stack) -> u32 { + self as u32 + } +} + +impl FromWasmAbi for *mut T { + type Abi = u32; + + unsafe fn from_abi(js: u32, _extra: &mut Stack) -> *mut T { + js as *mut T + } +} + +impl IntoWasmAbi for JsValue { + type Abi = u32; + + #[inline] + fn into_abi(self, _extra: &mut Stack) -> u32 { + let ret = self.idx; + mem::forget(self); + return ret; + } +} + +impl FromWasmAbi for JsValue { + type Abi = u32; + + #[inline] + unsafe fn from_abi(js: u32, _extra: &mut Stack) -> JsValue { + JsValue { idx: js } + } +} + +impl<'a> IntoWasmAbi for &'a JsValue { + type Abi = u32; + + #[inline] + fn into_abi(self, _extra: &mut Stack) -> u32 { + self.idx + } +} + +impl RefFromWasmAbi for JsValue { + type Abi = u32; + type Anchor = ManuallyDrop; + + #[inline] + unsafe fn ref_from_abi(js: u32, _extra: &mut Stack) -> Self::Anchor { + ManuallyDrop::new(JsValue { idx: js }) + } +} + +impl IntoWasmAbi for Option { + type Abi = T::Abi; + + fn into_abi(self, extra: &mut Stack) -> T::Abi { + match self { + Some(me) => me.into_abi(extra), + None => T::none(), + } + } +} + +impl FromWasmAbi for Option { + type Abi = T::Abi; + + unsafe fn from_abi(js: T::Abi, extra: &mut Stack) -> Self { + if T::is_none(&js) { + None + } else { + Some(T::from_abi(js, extra)) + } + } +} diff --git a/src/convert/mod.rs b/src/convert/mod.rs new file mode 100644 index 00000000..0163293c --- /dev/null +++ b/src/convert/mod.rs @@ -0,0 +1,36 @@ +//! This is mostly an internal module, no stability guarantees are provided. Use +//! at your own risk. + +mod traits; +mod impls; +mod slices; +mod closures; + +pub use self::slices::WasmSlice; +pub use self::traits::*; + +pub struct GlobalStack { + next: usize, +} + +impl GlobalStack { + #[inline] + pub unsafe fn new() -> GlobalStack { + GlobalStack { next: 0 } + } +} + +impl Stack for GlobalStack { + #[inline] + fn push(&mut self, val: u32) { + use __rt::{ + __wbindgen_global_argument_ptr as global_ptr, + GLOBAL_STACK_CAP, + }; + unsafe { + assert!(self.next < GLOBAL_STACK_CAP); + *global_ptr().offset(self.next as isize) = val; + self.next += 1; + } + } +} diff --git a/src/convert/slices.rs b/src/convert/slices.rs new file mode 100644 index 00000000..59276ae6 --- /dev/null +++ b/src/convert/slices.rs @@ -0,0 +1,240 @@ +#[cfg(feature = "std")] +use std::prelude::v1::*; + +use core::slice; +use core::str; + +use convert::{WasmAbi, IntoWasmAbi, FromWasmAbi, RefFromWasmAbi, RefMutFromWasmAbi}; +use convert::{Stack, OptionIntoWasmAbi}; + +if_std! { + use core::mem; + use convert::OptionFromWasmAbi; +} + +#[repr(C)] +pub struct WasmSlice { + pub ptr: u32, + pub len: u32, +} + +unsafe impl WasmAbi for WasmSlice {} + +#[inline] +fn null_slice() -> WasmSlice { + WasmSlice { ptr: 0, len: 0 } +} + +macro_rules! vectors { + ($($t:ident)*) => ($( + if_std! { + impl IntoWasmAbi for Box<[$t]> { + type Abi = WasmSlice; + + #[inline] + fn into_abi(self, extra: &mut Stack) -> WasmSlice { + let ptr = self.as_ptr(); + let len = self.len(); + mem::forget(self); + WasmSlice { + ptr: ptr.into_abi(extra), + len: len as u32, + } + } + } + + impl OptionIntoWasmAbi for Box<[$t]> { + fn none() -> WasmSlice { null_slice() } + } + + impl FromWasmAbi for Box<[$t]> { + type Abi = WasmSlice; + + #[inline] + unsafe fn from_abi(js: WasmSlice, extra: &mut Stack) -> Self { + let ptr = <*mut $t>::from_abi(js.ptr, extra); + let len = js.len as usize; + Vec::from_raw_parts(ptr, len, len).into_boxed_slice() + } + } + + impl OptionFromWasmAbi for Box<[$t]> { + fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } + } + } + + impl<'a> IntoWasmAbi for &'a [$t] { + type Abi = WasmSlice; + + #[inline] + fn into_abi(self, extra: &mut Stack) -> WasmSlice { + WasmSlice { + ptr: self.as_ptr().into_abi(extra), + len: self.len() as u32, + } + } + } + + impl<'a> OptionIntoWasmAbi for &'a [$t] { + fn none() -> WasmSlice { null_slice() } + } + + impl<'a> IntoWasmAbi for &'a mut [$t] { + type Abi = WasmSlice; + + #[inline] + fn into_abi(self, extra: &mut Stack) -> WasmSlice { + (&*self).into_abi(extra) + } + } + + impl<'a> OptionIntoWasmAbi for &'a mut [$t] { + fn none() -> WasmSlice { null_slice() } + } + + impl RefFromWasmAbi for [$t] { + type Abi = WasmSlice; + type Anchor = &'static [$t]; + + #[inline] + unsafe fn ref_from_abi(js: WasmSlice, extra: &mut Stack) -> &'static [$t] { + slice::from_raw_parts( + <*const $t>::from_abi(js.ptr, extra), + js.len as usize, + ) + } + } + + impl RefMutFromWasmAbi for [$t] { + type Abi = WasmSlice; + type Anchor = &'static mut [$t]; + + #[inline] + unsafe fn ref_mut_from_abi(js: WasmSlice, extra: &mut Stack) + -> &'static mut [$t] + { + slice::from_raw_parts_mut( + <*mut $t>::from_abi(js.ptr, extra), + js.len as usize, + ) + } + } + )*) +} + +vectors! { + u8 i8 u16 i16 u32 i32 u64 i64 f32 f64 +} + +if_std! { + impl IntoWasmAbi for Vec where Box<[T]>: IntoWasmAbi { + type Abi = as IntoWasmAbi>::Abi; + + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + self.into_boxed_slice().into_abi(extra) + } + } + + impl OptionIntoWasmAbi for Vec where Box<[T]>: IntoWasmAbi { + fn none() -> WasmSlice { null_slice() } + } + + impl FromWasmAbi for Vec where Box<[T]>: FromWasmAbi { + type Abi = as FromWasmAbi>::Abi; + + unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self { + >::from_abi(js, extra).into() + } + } + + impl OptionFromWasmAbi for Vec where Box<[T]>: FromWasmAbi { + fn is_none(abi: &WasmSlice) -> bool { abi.ptr == 0 } + } + + impl IntoWasmAbi for String { + type Abi = as IntoWasmAbi>::Abi; + + #[inline] + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + self.into_bytes().into_abi(extra) + } + } + + impl OptionIntoWasmAbi for String { + fn none() -> WasmSlice { null_slice() } + } + + impl FromWasmAbi for String { + type Abi = as FromWasmAbi>::Abi; + + #[inline] + unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self { + String::from_utf8_unchecked(>::from_abi(js, extra)) + } + } + + impl OptionFromWasmAbi for String { + fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } + } +} + +impl<'a> IntoWasmAbi for &'a str { + type Abi = <&'a [u8] as IntoWasmAbi>::Abi; + + #[inline] + fn into_abi(self, extra: &mut Stack) -> Self::Abi { + self.as_bytes().into_abi(extra) + } +} + +impl<'a> OptionIntoWasmAbi for &'a str { + fn none() -> WasmSlice { null_slice() } +} + +impl RefFromWasmAbi for str { + type Abi = <[u8] as RefFromWasmAbi>::Abi; + type Anchor = &'static str; + + #[inline] + unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor { + str::from_utf8_unchecked(<[u8]>::ref_from_abi(js, extra)) + } +} + +if_std! { + use JsValue; + + impl IntoWasmAbi for Box<[JsValue]> { + type Abi = WasmSlice; + + #[inline] + fn into_abi(self, extra: &mut Stack) -> WasmSlice { + let ptr = self.as_ptr(); + let len = self.len(); + mem::forget(self); + WasmSlice { + ptr: ptr.into_abi(extra), + len: len as u32, + } + } + } + + impl OptionIntoWasmAbi for Box<[JsValue]> { + fn none() -> WasmSlice { null_slice() } + } + + impl FromWasmAbi for Box<[JsValue]> { + type Abi = WasmSlice; + + #[inline] + unsafe fn from_abi(js: WasmSlice, extra: &mut Stack) -> Self { + let ptr = <*mut JsValue>::from_abi(js.ptr, extra); + let len = js.len as usize; + Vec::from_raw_parts(ptr, len, len).into_boxed_slice() + } + } + + impl OptionFromWasmAbi for Box<[JsValue]> { + fn is_none(slice: &WasmSlice) -> bool { slice.ptr == 0 } + } +} diff --git a/src/convert/traits.rs b/src/convert/traits.rs new file mode 100644 index 00000000..c3664168 --- /dev/null +++ b/src/convert/traits.rs @@ -0,0 +1,108 @@ +use core::ops::{Deref, DerefMut}; + +use describe::*; + +/// A trait for anything that can be converted into a type that can cross the +/// wasm ABI directly, eg `u32` or `f64`. +/// +/// This is the opposite operation as `FromWasmAbi` and `Ref[Mut]FromWasmAbi`. +pub trait IntoWasmAbi: WasmDescribe { + /// The wasm ABI type that this converts into when crossing the ABI + /// boundary. + type Abi: WasmAbi; + + /// Convert `self` into `Self::Abi` so that it can be sent across the wasm + /// ABI boundary. + fn into_abi(self, extra: &mut Stack) -> Self::Abi; +} + +/// A trait for anything that can be recovered by-value from the wasm ABI +/// boundary, eg a Rust `u8` can be recovered from the wasm ABI `u32` type. +/// +/// This is the by-value variant of the opposite operation as `IntoWasmAbi`. +pub trait FromWasmAbi: WasmDescribe { + /// The wasm ABI type that this converts from when coming back out from the + /// ABI boundary. + type Abi: WasmAbi; + + /// Recover a `Self` from `Self::Abi`. + /// + /// # Safety + /// + /// This is only safe to call when -- and implementations may assume that -- + /// the supplied `Self::Abi` was previously generated by a call to `::into_abi()` or the moral equivalent in JS. + unsafe fn from_abi(js: Self::Abi, extra: &mut Stack) -> Self; +} + +/// A trait for anything that can be recovered as some sort of shared reference +/// from the wasm ABI boundary. +/// +/// This is the shared reference variant of the opposite operation as +/// `IntoWasmAbi`. +pub trait RefFromWasmAbi: WasmDescribe { + /// The wasm ABI type references to `Self` are recovered from. + type Abi: WasmAbi; + + /// The type that holds the reference to `Self` for the duration of the + /// invocation of the function that has an `&Self` parameter. This is + /// required to ensure that the lifetimes don't persist beyond one function + /// call, and so that they remain anonymous. + type Anchor: Deref; + + /// Recover a `Self::Anchor` from `Self::Abi`. + /// + /// # Safety + /// + /// Same as `FromWasmAbi::from_abi`. + unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor; +} + +/// Dual of the `RefFromWasmAbi` trait, except for mutable references. +pub trait RefMutFromWasmAbi: WasmDescribe { + /// Same as `RefFromWasmAbi::Abi` + type Abi: WasmAbi; + /// Same as `RefFromWasmAbi::Anchor` + type Anchor: DerefMut; + /// Same as `RefFromWasmAbi::ref_from_abi` + unsafe fn ref_mut_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor; +} + +/// Indicates that this type can be passed to JS as `Option`. +/// +/// This trait is used when implementing `IntoWasmAbi for Option`. +pub trait OptionIntoWasmAbi: IntoWasmAbi { + /// Returns an ABI instance indicating "none", which JS will interpret as + /// the `None` branch of this option. + /// + /// It should be guaranteed that the `IntoWasmAbi` can never produce the ABI + /// value returned here. + fn none() -> Self::Abi; +} + +/// Indicates that this type can be received from JS as `Option`. +/// +/// This trait is used when implementing `FromWasmAbi for Option`. +pub trait OptionFromWasmAbi: FromWasmAbi { + /// Tests whether the argument is a "none" instance. If so it will be + /// deserialized as `None`, and otherwise it will be passed to + /// `FromWasmAbi`. + fn is_none(abi: &Self::Abi) -> bool; +} + +pub trait Stack { + fn push(&mut self, bits: u32); +} + +/// An unsafe trait which represents types that are ABI-safe to pass via wasm +/// arguments. +/// +/// This is an unsafe trait to implement as there's no guarantee the type is +/// actually safe to transfer across the was boundary, it's up to you to +/// guarantee this so codegen works correctly. +pub unsafe trait WasmAbi {} + +unsafe impl WasmAbi for u32 {} +unsafe impl WasmAbi for i32 {} +unsafe impl WasmAbi for f32 {} +unsafe impl WasmAbi for f64 {} diff --git a/src/describe.rs b/src/describe.rs index b88438f2..34c90e2b 100644 --- a/src/describe.rs +++ b/src/describe.rs @@ -38,6 +38,7 @@ tys! { ENUM RUST_STRUCT CHAR + OPTIONAL } pub fn inform(a: u32) { @@ -195,3 +196,10 @@ doit! { (A B C D E F) (A B C D E F G) } + +impl WasmDescribe for Option { + fn describe() { + inform(OPTIONAL); + T::describe(); + } +} diff --git a/src/lib.rs b/src/lib.rs index a1a2df11..1992b76c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,8 +61,8 @@ pub struct JsValue { idx: u32, } -const JSIDX_NULL: u32 = 0; -const JSIDX_UNDEFINED: u32 = 2; +const JSIDX_UNDEFINED: u32 = 0; +const JSIDX_NULL: u32 = 2; const JSIDX_TRUE: u32 = 4; const JSIDX_FALSE: u32 = 6; const JSIDX_RESERVED: u32 = 8; @@ -345,11 +345,8 @@ externs! { fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32; fn __wbindgen_number_new(f: f64) -> u32; fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64; - fn __wbindgen_null_new() -> u32; - fn __wbindgen_undefined_new() -> u32; fn __wbindgen_is_null(idx: u32) -> u32; fn __wbindgen_is_undefined(idx: u32) -> u32; - fn __wbindgen_boolean_new(val: u32) -> u32; fn __wbindgen_boolean_get(idx: u32) -> u32; fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32; fn __wbindgen_is_symbol(idx: u32) -> u32; @@ -673,6 +670,11 @@ pub mod __rt { #[no_mangle] pub unsafe extern fn __wbindgen_free(ptr: *mut u8, size: usize) { + // This happens for zero-length slices, and in that case `ptr` is + // likely bogus so don't actually send this to the system allocator + if size == 0 { + return + } let align = mem::align_of::(); let layout = Layout::from_size_align_unchecked(size, align); System.dealloc(ptr, layout); diff --git a/tests/all/api.rs b/tests/all/api.rs index 599b9f88..1bfeccdd 100644 --- a/tests/all/api.rs +++ b/tests/all/api.rs @@ -194,3 +194,40 @@ fn eq_works() { ) .test(); } + +#[test] +fn null_keeps_working() { + project() + .file( + "src/lib.rs", + r#" + #![feature(use_extern_macros, wasm_import_module)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./foo")] + extern { + fn take_null(val: JsValue); + } + + #[wasm_bindgen] + pub fn test() { + take_null(JsValue::null()); + take_null(JsValue::null()); + } + "#, + ) + .file( + "foo.js", + r#" + import { strictEqual } from "assert"; + + export function take_null(n) { + strictEqual(n, null); + } + "#, + ) + .test(); +} diff --git a/tests/all/import_class.rs b/tests/all/import_class.rs index 799df65d..f07e1da7 100644 --- a/tests/all/import_class.rs +++ b/tests/all/import_class.rs @@ -505,3 +505,103 @@ fn deny_missing_docs() { ) .test(); } + +#[test] +fn options() { + project() + .file( + "src/lib.rs", + r#" + #![feature(use_extern_macros, wasm_import_module)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./foo")] + extern { + pub type Foo; + #[wasm_bindgen(constructor)] + fn new() -> Foo; + + fn take_none(val: Option); + fn take_some(val: Option); + fn return_null() -> Option; + fn return_undefined() -> Option; + fn return_some() -> Option; + fn run_rust_tests(); + } + + #[wasm_bindgen] + pub fn test() { + take_none(None); + take_some(Some(Foo::new())); + assert!(return_null().is_none()); + assert!(return_undefined().is_none()); + assert!(return_some().is_some()); + run_rust_tests(); + } + + #[wasm_bindgen] + pub fn rust_take_none(a: Option) { + assert!(a.is_none()); + } + + #[wasm_bindgen] + pub fn rust_take_some(a: Option) { + assert!(a.is_some()); + } + + #[wasm_bindgen] + pub fn rust_return_none() -> Option { + None + } + + #[wasm_bindgen] + pub fn rust_return_some() -> Option { + Some(Foo::new()) + } + "#, + ) + .file( + "foo.js", + r#" + import { strictEqual } from "assert"; + import * as wasm from "./out"; + + export class Foo { + } + + export function take_none(val) { + strictEqual(val, undefined); + } + + export function take_some(val) { + strictEqual(val === undefined, false); + } + + export function return_null() { + return null; + } + + export function return_undefined() { + return undefined; + } + + export function return_some() { + return new Foo(); + } + + export function run_rust_tests() { + wasm.rust_take_none(); + wasm.rust_take_none(null); + wasm.rust_take_none(undefined); + wasm.rust_take_some(new Foo()); + strictEqual(wasm.rust_return_none(), undefined); + strictEqual(wasm.rust_return_none(), undefined); + strictEqual(wasm.rust_return_some() === undefined, false); + } + "#, + ) + .test(); +} diff --git a/tests/all/simple.rs b/tests/all/simple.rs index f8998f83..1e7682b6 100644 --- a/tests/all/simple.rs +++ b/tests/all/simple.rs @@ -447,3 +447,118 @@ fn binding_to_unimplemented_apis_doesnt_break_everything() { ) .test(); } + +#[test] +fn optional_slices() { + project() + .file( + "src/lib.rs", + r#" + #![feature(use_extern_macros, wasm_custom_section, wasm_import_module)] + extern crate wasm_bindgen; + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./foo")] + extern { + fn optional_str_none(a: Option<&str>); + fn optional_str_some(a: Option<&str>); + fn optional_slice_none(a: Option<&[u8]>); + fn optional_slice_some(a: Option<&[u8]>); + + fn optional_string_none(a: Option); + fn optional_string_some(a: Option); + fn optional_string_some_empty(a: Option); + + fn return_string_none() -> Option; + fn return_string_some() -> Option; + + fn run_rust_tests(); + } + + #[wasm_bindgen] + pub fn test() { + optional_str_none(None); + optional_str_some(Some("x")); + optional_slice_none(None); + optional_slice_some(Some(&[1, 2, 3])); + optional_string_none(None); + optional_string_some_empty(Some(String::new())); + optional_string_some(Some("abcd".to_string())); + + assert_eq!(return_string_none(), None); + assert_eq!(return_string_some(), Some("foo".to_string())); + run_rust_tests(); + } + + #[wasm_bindgen] + pub fn take_optional_str_none(x: Option) { + assert!(x.is_none()) + } + #[wasm_bindgen] + pub fn take_optional_str_some(x: Option) { + assert_eq!(x, Some(String::from("hello"))); + } + #[wasm_bindgen] + pub fn return_optional_str_none() -> Option { + None + } + #[wasm_bindgen] + pub fn return_optional_str_some() -> Option { + Some("world".to_string()) + } + "#, + ) + .file( + "foo.js", + r#" + import { strictEqual } from "assert"; + import * as wasm from "./out"; + + export function optional_str_none(x) { + strictEqual(x, undefined); + } + + export function optional_str_some(x) { + strictEqual(x, 'x'); + } + + export function optional_slice_none(x) { + strictEqual(x, undefined); + } + + export function optional_slice_some(x) { + strictEqual(x.length, 3); + strictEqual(x[0], 1); + strictEqual(x[1], 2); + strictEqual(x[2], 3); + } + + export function optional_string_none(x) { + strictEqual(x, undefined); + } + + export function optional_string_some(x) { + strictEqual(x, 'abcd'); + } + + export function optional_string_some_empty(x) { + strictEqual(x, ''); + } + + export function return_string_none() {} + export function return_string_some() { + return 'foo'; + } + + export function run_rust_tests() { + wasm.take_optional_str_none(); + wasm.take_optional_str_none(null); + wasm.take_optional_str_none(undefined); + wasm.take_optional_str_some('hello'); + strictEqual(wasm.return_optional_str_none(), undefined); + strictEqual(wasm.return_optional_str_some(), 'world'); + } + "# + ) + .test(); +}