diff --git a/.appveyor.yml b/.appveyor.yml index f63aa9f4..7ff3012f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -8,7 +8,7 @@ environment: DEPLOY: 1 install: - - ps: Install-Product node 9 + - ps: Install-Product node 10 - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin diff --git a/.travis.yml b/.travis.yml index 70aedb88..296a522e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -61,7 +61,7 @@ matrix: install: - curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash - source ~/.nvm/nvm.sh - - nvm install 9 + - nvm install 10 - yarn notifications: diff --git a/README.md b/README.md index 479353a8..c2e68d87 100644 --- a/README.md +++ b/README.md @@ -492,7 +492,7 @@ All structs referenced through arguments to functions should be defined in the macro itself. Arguments allowed implement the `WasmBoundary` trait, and examples are: -* Integers (not u64/i64) +* Integers (u64/i64 require `BigInt` support) * Floats * Borrowed strings (`&str`) * Owned strings (`String`) diff --git a/crates/cli-support/src/descriptor.rs b/crates/cli-support/src/descriptor.rs index b98a2ec5..0b07579c 100644 --- a/crates/cli-support/src/descriptor.rs +++ b/crates/cli-support/src/descriptor.rs @@ -79,6 +79,8 @@ pub enum VectorKind { U16, I32, U32, + I64, + U64, F32, F64, String, @@ -139,8 +141,6 @@ impl Descriptor { Descriptor::U16 | Descriptor::I32 | Descriptor::U32 | - Descriptor::I64 | - Descriptor::U64 | Descriptor::F32 | Descriptor::F64 | Descriptor::Enum => true, @@ -148,6 +148,14 @@ impl Descriptor { } } + pub fn get_64bit(&self) -> Option { + match *self { + Descriptor::I64 => Some(true), + Descriptor::U64 => Some(false), + _ => None, + } + } + pub fn is_ref_anyref(&self) -> bool { match *self { Descriptor::Ref(ref s) => s.is_anyref(), @@ -199,9 +207,11 @@ impl Descriptor { Descriptor::I8 => Some(VectorKind::I8), Descriptor::I16 => Some(VectorKind::I16), Descriptor::I32 => Some(VectorKind::I32), + Descriptor::I64 => Some(VectorKind::I64), Descriptor::U8 => Some(VectorKind::U8), Descriptor::U16 => Some(VectorKind::U16), Descriptor::U32 => Some(VectorKind::U32), + Descriptor::U64 => Some(VectorKind::U64), Descriptor::F32 => Some(VectorKind::F32), Descriptor::F64 => Some(VectorKind::F64), Descriptor::Anyref => Some(VectorKind::Anyref), @@ -290,6 +300,8 @@ impl VectorKind { VectorKind::U16 => "Uint16Array", VectorKind::I32 => "Int32Array", VectorKind::U32 => "Uint32Array", + VectorKind::I64 => "BigInt64Array", + VectorKind::U64 => "BigUint64Array", VectorKind::F32 => "Float32Array", VectorKind::F64 => "Float64Array", VectorKind::Anyref => "any[]", @@ -305,6 +317,8 @@ impl VectorKind { VectorKind::U16 => 2, VectorKind::I32 => 4, VectorKind::U32 => 4, + VectorKind::I64 => 8, + VectorKind::U64 => 8, VectorKind::F32 => 4, VectorKind::F64 => 8, VectorKind::Anyref => 4, diff --git a/crates/cli-support/src/js/js2rust.rs b/crates/cli-support/src/js/js2rust.rs index ce8b67ea..be304751 100644 --- a/crates/cli-support/src/js/js2rust.rs +++ b/crates/cli-support/src/js/js2rust.rs @@ -168,6 +168,29 @@ impl<'a, 'b> Js2Rust<'a, 'b> { return Ok(self) } + if let Some(signed) = arg.get_64bit() { + let f = if signed { + self.cx.expose_int64_cvt_shim() + } else { + self.cx.expose_uint64_cvt_shim() + }; + self.cx.expose_uint32_memory(); + self.cx.expose_global_argument_ptr()?; + self.js_arguments.push((name.clone(), "BigInt".to_string())); + self.prelude(&format!("\ + {f}[0] = {name};\n\ + const lo{i} = u32CvtShim[0];\n\ + const hi{i} = u32CvtShim[1];\n\ + ", + i = i, + f = f, + name = name, + )); + self.rust_arguments.push(format!("lo{}", i)); + self.rust_arguments.push(format!("hi{}", i)); + return Ok(self) + } + if arg.is_ref_anyref() { self.js_arguments.push((name.clone(), "any".to_string())); self.cx.expose_borrowed_objects(); @@ -252,6 +275,25 @@ impl<'a, 'b> Js2Rust<'a, 'b> { return Ok(self) } + if let Some(signed) = ty.get_64bit() { + self.ret_ty = "BigInt".to_string(); + self.cx.expose_global_argument_ptr()?; + let f = if signed { + self.cx.expose_int64_memory(); + "getInt64Memory" + } else { + self.cx.expose_uint64_memory(); + "getUint64Memory" + }; + self.prelude("const retptr = globalArgumentPtr();"); + self.rust_arguments.insert(0, "retptr".to_string()); + self.ret_expr = format!("\ + RET;\n\ + return {}()[retptr / 8];\n\ + ", f); + return Ok(self) + } + match *ty { Descriptor::Boolean => { self.ret_ty = "boolean".to_string(); diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs index f73065bb..3bfe9b8c 100644 --- a/crates/cli-support/src/js/mod.rs +++ b/crates/cli-support/src/js/mod.rs @@ -787,80 +787,52 @@ impl<'a> Context<'a> { } fn expose_pass_array8_to_wasm(&mut self) -> Result<(), Error> { - if !self.exposed_globals.insert("pass_array8_to_wasm") { - return Ok(()); - } - self.require_internal_export("__wbindgen_malloc")?; self.expose_uint8_memory(); - self.global(&format!(" - function passArray8ToWasm(arg) {{ - const ptr = wasm.__wbindgen_malloc(arg.length); - getUint8Memory().set(arg, ptr); - return [ptr, arg.length]; - }} - ")); - Ok(()) + self.pass_array_to_wasm("passArray8ToWasm", "getUint8Memory", 1) } fn expose_pass_array16_to_wasm(&mut self) -> Result<(), Error> { - if !self.exposed_globals.insert("pass_array16_to_wasm") { - return Ok(()); - } - self.require_internal_export("__wbindgen_malloc")?; self.expose_uint16_memory(); - self.global(&format!(" - function passArray16ToWasm(arg) {{ - const ptr = wasm.__wbindgen_malloc(arg.length * 2); - getUint16Memory().set(arg, ptr / 2); - return [ptr, arg.length]; - }} - ")); - Ok(()) + self.pass_array_to_wasm("passArray16ToWasm", "getUint16Memory", 2) } fn expose_pass_array32_to_wasm(&mut self) -> Result<(), Error> { - if !self.exposed_globals.insert("pass_array32_to_wasm") { - return Ok(()) - } - self.require_internal_export("__wbindgen_malloc")?; self.expose_uint32_memory(); - self.global(&format!(" - function passArray32ToWasm(arg) {{ - const ptr = wasm.__wbindgen_malloc(arg.length * 4); - getUint32Memory().set(arg, ptr / 4); - return [ptr, arg.length]; - }} - ")); - Ok(()) + self.pass_array_to_wasm("passArray32ToWasm", "getUint32Memory", 4) + } + + fn expose_pass_array64_to_wasm(&mut self) -> Result<(), Error> { + self.expose_uint64_memory(); + self.pass_array_to_wasm("passArray64ToWasm", "getUint64Memory", 8) } fn expose_pass_array_f32_to_wasm(&mut self) -> Result<(), Error> { - if !self.exposed_globals.insert("pass_array_f32_to_wasm") { - return Ok(()) - } - self.require_internal_export("__wbindgen_malloc")?; - self.global(&format!(" - function passArrayF32ToWasm(arg) {{ - const ptr = wasm.__wbindgen_malloc(arg.length * 4); - new Float32Array(wasm.memory.buffer).set(arg, ptr / 4); - return [ptr, arg.length]; - }} - ")); - Ok(()) + self.expose_f32_memory(); + self.pass_array_to_wasm("passArrayF32ToWasm", "getFloat32Memory", 4) } fn expose_pass_array_f64_to_wasm(&mut self) -> Result<(), Error> { - if !self.exposed_globals.insert("pass_array_f64_to_wasm") { + self.expose_f64_memory(); + self.pass_array_to_wasm("passArrayF64ToWasm", "getFloat64Memory", 8) + } + + fn pass_array_to_wasm(&mut self, + name: &'static str, + delegate: &str, + size: usize) -> Result<(), Error> + { + if !self.exposed_globals.insert(name) { return Ok(()) } self.require_internal_export("__wbindgen_malloc")?; + self.expose_uint64_memory(); self.global(&format!(" - function passArrayF64ToWasm(arg) {{ - const ptr = wasm.__wbindgen_malloc(arg.length * 8); - new Float64Array(wasm.memory.buffer).set(arg, ptr / 8); + function {}(arg) {{ + const ptr = wasm.__wbindgen_malloc(arg.length * {size}); + {}().set(arg, ptr / {size}); return [ptr, arg.length]; }} - ")); + ", name, delegate, size = size)); Ok(()) } @@ -980,6 +952,16 @@ impl<'a> Context<'a> { self.arrayget("getArrayU32FromWasm", "getUint32Memory", 4); } + fn expose_get_array_i64_from_wasm(&mut self) { + self.expose_int64_memory(); + self.arrayget("getArrayI64FromWasm", "getInt64Memory", 8); + } + + fn expose_get_array_u64_from_wasm(&mut self) { + self.expose_uint64_memory(); + self.arrayget("getArrayU64FromWasm", "getUint64Memory", 8); + } + fn expose_get_array_f32_from_wasm(&mut self) { self.expose_f32_memory(); self.arrayget("getArrayF32FromWasm", "getFloat32Memory", 4); @@ -1029,6 +1011,14 @@ impl<'a> Context<'a> { self.memview("getUint32Memory", "Uint32Array"); } + fn expose_int64_memory(&mut self) { + self.memview("getInt64Memory", "BigInt64Array"); + } + + fn expose_uint64_memory(&mut self) { + self.memview("getUint64Memory", "BigUint64Array"); + } + fn expose_f32_memory(&mut self) { self.memview("getFloat32Memory", "Float32Array"); } @@ -1067,6 +1057,14 @@ impl<'a> Context<'a> { self.expose_uint32_memory(); "getUint32Memory" } + VectorKind::I64 => { + self.expose_int64_memory(); + "getInt64Memory" + } + VectorKind::U64 => { + self.expose_uint64_memory(); + "getUint64Memory" + } VectorKind::F32 => { self.expose_f32_memory(); "getFloat32Memory" @@ -1204,6 +1202,11 @@ impl<'a> Context<'a> { self.expose_pass_array32_to_wasm()?; "passArray32ToWasm" } + VectorKind::I64 | + VectorKind::U64 => { + self.expose_pass_array64_to_wasm()?; + "passArray64ToWasm" + } VectorKind::F32 => { self.expose_pass_array_f32_to_wasm()?; "passArrayF32ToWasm" @@ -1249,6 +1252,14 @@ impl<'a> Context<'a> { self.expose_get_array_u32_from_wasm(); "getArrayU32FromWasm" } + VectorKind::I64 => { + self.expose_get_array_i64_from_wasm(); + "getArrayI64FromWasm" + } + VectorKind::U64 => { + self.expose_get_array_u64_from_wasm(); + "getArrayU64FromWasm" + } VectorKind::F32 => { self.expose_get_array_f32_from_wasm(); "getArrayF32FromWasm" @@ -1320,6 +1331,35 @@ impl<'a> Context<'a> { "); } + fn expose_u32_cvt_shim(&mut self) -> &'static str { + let name = "u32CvtShim"; + if !self.exposed_globals.insert(name) { + return name + } + self.global(&format!("const {} = new Uint32Array(2);", name)); + name + } + + fn expose_int64_cvt_shim(&mut self) -> &'static str { + let name = "int64CvtShim"; + if !self.exposed_globals.insert(name) { + return name + } + let n = self.expose_u32_cvt_shim(); + self.global(&format!("const {} = new BigInt64Array({}.buffer);", name, n)); + name + } + + fn expose_uint64_cvt_shim(&mut self) -> &'static str { + let name = "uint64CvtShim"; + if !self.exposed_globals.insert(name) { + return name + } + let n = self.expose_u32_cvt_shim(); + self.global(&format!("const {} = new BigUint64Array({}.buffer);", name, n)); + name + } + 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 15296081..5e2eeca0 100644 --- a/crates/cli-support/src/js/rust2js.rs +++ b/crates/cli-support/src/js/rust2js.rs @@ -98,6 +98,28 @@ impl<'a, 'b> Rust2Js<'a, 'b> { return Ok(()) } + if let Some(signed) = arg.get_64bit() { + let f = if signed { + self.cx.expose_int64_cvt_shim() + } else { + self.cx.expose_uint64_cvt_shim() + }; + let hi = self.shim_argument(); + let name = format!("n{}", abi); + self.prelude(&format!("\ + u32CvtShim[0] = {lo};\n\ + u32CvtShim[1] = {hi};\n\ + const {name} = {f}[0];\n\ + ", + lo = abi, + hi = hi, + f = f, + name = name, + )); + self.js_arguments.push(name); + return Ok(()) + } + if let Some(class) = arg.rust_struct() { if arg.is_by_ref() { bail!("cannot invoke JS functions with custom ref types yet") @@ -229,6 +251,21 @@ impl<'a, 'b> Rust2Js<'a, 'b> { self.ret_expr = "return JS;".to_string(); return Ok(()) } + if let Some(signed) = ty.get_64bit() { + let f = if signed { + self.cx.expose_int64_memory(); + "getInt64Memory" + } else { + self.cx.expose_uint64_memory(); + "getUint64Memory" + }; + self.shim_arguments.insert(0, "ret".to_string()); + self.ret_expr = format!("\ + const val = JS;\n\ + {}()[ret / 8] = val;\n\ + ", f); + return Ok(()) + } self.ret_expr = match *ty { Descriptor::Boolean => "return JS ? 1 : 0;".to_string(), Descriptor::Anyref => { diff --git a/src/convert.rs b/src/convert.rs index 7c19d0b7..1a1279bc 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -55,9 +55,7 @@ pub trait Stack { pub unsafe trait WasmAbi {} unsafe impl WasmAbi for u32 {} -unsafe impl WasmAbi for u64 {} unsafe impl WasmAbi for i32 {} -unsafe impl WasmAbi for i64 {} unsafe impl WasmAbi for f32 {} unsafe impl WasmAbi for f64 {} @@ -83,7 +81,30 @@ macro_rules! simple { )*) } -simple!(u32 u64 i32 i64 f32 f64); +simple!(u32 i32 f32 f64); + +macro_rules! sixtyfour { + ($($t:tt)*) => ($( + impl IntoWasmAbi for $t { + type Abi = WasmSlice; + 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; + 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)*) => ($( @@ -217,7 +238,7 @@ macro_rules! vectors { } vectors! { - u8 i8 u16 i16 u32 i32 f32 f64 + u8 i8 u16 i16 u32 i32 u64 i64 f32 f64 } if_std! { diff --git a/tests/all/main.rs b/tests/all/main.rs index c149ed51..749d016b 100644 --- a/tests/all/main.rs +++ b/tests/all/main.rs @@ -4,7 +4,8 @@ use std::env; use std::fs::{self, File}; use std::io::{self, Write, Read}; use std::path::{PathBuf, Path}; -use std::process::Command; +use std::process::{Command, Stdio}; +use std::sync::{Once, ONCE_INIT}; use std::sync::atomic::*; use std::time::Instant; @@ -18,6 +19,7 @@ struct Project { no_std: bool, serde: bool, rlib: bool, + node_args: Vec, deps: Vec, } @@ -33,6 +35,7 @@ fn project() -> Project { serde: false, rlib: false, deps: Vec::new(), + node_args: Vec::new(), files: vec![ ("Cargo.lock".to_string(), lockfile), @@ -120,6 +123,40 @@ fn root() -> PathBuf { return me } +fn assert_bigint_support() -> Option<&'static str> { + static BIGINT_SUPPORED: AtomicUsize = ATOMIC_USIZE_INIT; + static INIT: Once = ONCE_INIT; + + INIT.call_once(|| { + let mut cmd = Command::new("node"); + cmd.arg("-e").arg("BigInt"); + cmd.stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if cmd.status().unwrap().success() { + BIGINT_SUPPORED.store(1, Ordering::SeqCst); + return + } + + cmd.arg("--harmony-bigint"); + if cmd.status().unwrap().success() { + BIGINT_SUPPORED.store(2, Ordering::SeqCst); + return + } + }); + + match BIGINT_SUPPORED.load(Ordering::SeqCst) { + 1 => return None, + 2 => return Some("--harmony-bigint"), + _ => { + panic!("the version of node.js that is installed for these tests \ + does not support `BigInt`, you may wish to try installing \ + node 10 to fix this") + } + } + +} + impl Project { fn file(&mut self, name: &str, contents: &str) -> &mut Project { self.files.push((name.to_string(), contents.to_string())); @@ -165,6 +202,13 @@ impl Project { format!("test{}", IDX.with(|x| *x)) } + fn requires_bigint(&mut self) -> &mut Project { + if let Some(arg) = assert_bigint_support() { + self.node_args.push(arg.to_string()); + } + self + } + fn build(&mut self) -> (PathBuf, PathBuf) { let mut manifest = format!(r#" [package] @@ -265,6 +309,7 @@ impl Project { if self.node { let mut cmd = Command::new("node"); + cmd.args(&self.node_args); cmd.arg(root.join("run-node.js")) .current_dir(&root); run(&mut cmd, "node"); @@ -281,6 +326,7 @@ impl Project { run(&mut cmd, "yarn"); let mut cmd = Command::new("node"); + cmd.args(&self.node_args); cmd.arg(root.join("bundle.js")) .current_dir(&root); run(&mut cmd, "node"); @@ -335,3 +381,4 @@ mod simple; mod slice; mod structural; mod non_wasm; +mod u64; diff --git a/tests/all/u64.rs b/tests/all/u64.rs new file mode 100644 index 00000000..6fc4cc6c --- /dev/null +++ b/tests/all/u64.rs @@ -0,0 +1,100 @@ +use super::project; + +#[test] +fn works() { + project() + .requires_bigint() + .file("src/lib.rs", r#" + #![feature(proc_macro, wasm_custom_section, wasm_import_module)] + + extern crate wasm_bindgen; + + use wasm_bindgen::prelude::*; + + #[wasm_bindgen(module = "./test")] + extern { + fn js_i64_round(a: i64) -> i64; + fn js_u64_round(a: u64) -> u64; + } + + #[wasm_bindgen] + pub fn zero() -> u64 { 0 } + #[wasm_bindgen] + pub fn one() -> u64 { 1 } + #[wasm_bindgen] + pub fn neg_one() -> i64 { -1 } + #[wasm_bindgen] + pub fn u32_max() -> u64 { u32::max_value() as u64 } + #[wasm_bindgen] + pub fn i32_min() -> i64 { i32::min_value() as i64 } + #[wasm_bindgen] + pub fn u64_max() -> u64 { u64::max_value() } + #[wasm_bindgen] + pub fn i64_min() -> i64 { i64::min_value() } + + #[wasm_bindgen] + pub fn i64_round(a: i64) -> i64 { js_i64_round(a) } + #[wasm_bindgen] + pub fn u64_round(a: u64) -> u64 { js_u64_round(a) } + + #[wasm_bindgen] + pub fn i64_slice(a: &[i64]) -> Vec { a.to_vec() } + + #[wasm_bindgen] + pub fn u64_slice(a: &[u64]) -> Vec { a.to_vec() } + "#) + .file("test.js", r#" + import * as wasm from './out'; + + function assertEq(a, b) { + console.log(a, '?=', b); + if (a === b) + return; + throw new Error('not equal'); + } + + function assertArrayEq(a, b) { + console.log(a, '?=', b); + if (a.length !== b.length) + throw new Error('not equal'); + for (let i = 0; i < a.length; i++) + assertEq(a[i], b[i]); + } + + export function test() { + assertEq(wasm.zero(), BigInt(0)); + assertEq(wasm.one(), BigInt(1)); + assertEq(wasm.neg_one(), BigInt(-1)); + assertEq(wasm.u32_max(), BigInt(4294967295)); + assertEq(wasm.i32_min(), BigInt(-2147483648)); + assertEq(wasm.u64_max(), BigInt('18446744073709551615')); + assertEq(wasm.i64_min(), BigInt('-9223372036854775808')); + + assertEq(wasm.i64_round(BigInt(0)), BigInt(0)); + assertEq(wasm.i64_round(BigInt(1)), BigInt(1)); + assertEq(wasm.i64_round(BigInt(-1)), BigInt(-1)); + assertEq(wasm.u64_round(BigInt(0)), BigInt(0)); + assertEq(wasm.u64_round(BigInt(1)), BigInt(1)); + assertEq(wasm.u64_round(BigInt(1) << BigInt(64)), BigInt(0)); + + const u64_max = BigInt('18446744073709551615'); + const i64_min = BigInt('-9223372036854775808'); + assertEq(wasm.i64_round(i64_min), i64_min); + assertEq(wasm.u64_round(u64_max), u64_max); + + assertArrayEq(wasm.u64_slice([]), new BigUint64Array()); + assertArrayEq(wasm.i64_slice([]), new BigInt64Array()); + const arr1 = new BigUint64Array([BigInt(1), BigInt(2)]); + assertArrayEq(wasm.u64_slice([BigInt(1), BigInt(2)]), arr1); + const arr2 = new BigInt64Array([BigInt(1), BigInt(2)]); + assertArrayEq(wasm.i64_slice([BigInt(1), BigInt(2)]), arr2); + + assertArrayEq(wasm.i64_slice([i64_min]), new BigInt64Array([i64_min])); + assertArrayEq(wasm.u64_slice([u64_max]), new BigUint64Array([u64_max])); + } + + export function js_i64_round(a) { return a; } + export function js_u64_round(a) { return a; } + "#) + .test(); +}