Merge pull request #188 from rustwasm/u64

Map u64/i64 to BigInt in JS
This commit is contained in:
Alex Crichton 2018-05-05 22:08:36 -05:00 committed by GitHub
commit 6d167116a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 364 additions and 63 deletions

View File

@ -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

View File

@ -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:

View File

@ -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`)

View File

@ -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<bool> {
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,

View File

@ -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();

View File

@ -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)?;

View File

@ -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 => {

View File

@ -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! {

View File

@ -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<String>,
deps: Vec<String>,
}
@ -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;

100
tests/all/u64.rs Normal file
View File

@ -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<i64> { a.to_vec() }
#[wasm_bindgen]
pub fn u64_slice(a: &[u64]) -> Vec<u64> { 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();
}