From 0472c758f324e1b35befdcef391091e2ef7a8931 Mon Sep 17 00:00:00 2001 From: RunDevelopment Date: Tue, 15 Oct 2024 15:00:14 +0200 Subject: [PATCH] Support regular JS numbers for incoming 64-bit integers --- crates/cli-support/src/js/binding.rs | 34 ++++++++++++- crates/cli/tests/reference/add.d.ts | 4 ++ crates/cli/tests/reference/add.js | 74 ++++++++++++++++++++++++++++ crates/cli/tests/reference/add.rs | 20 ++++++++ crates/cli/tests/reference/add.wat | 19 +++++-- crates/cli/tests/reference/date.d.ts | 3 ++ crates/cli/tests/reference/date.js | 19 +++++++ crates/cli/tests/reference/date.rs | 14 ++++++ crates/cli/tests/reference/date.wat | 9 ++++ tests/wasm/optional_primitives.js | 11 +++++ 10 files changed, 202 insertions(+), 5 deletions(-) create mode 100644 crates/cli/tests/reference/date.d.ts create mode 100644 crates/cli/tests/reference/date.js create mode 100644 crates/cli/tests/reference/date.rs create mode 100644 crates/cli/tests/reference/date.wat diff --git a/crates/cli-support/src/js/binding.rs b/crates/cli-support/src/js/binding.rs index ccbfea09141..96177e7a991 100644 --- a/crates/cli-support/src/js/binding.rs +++ b/crates/cli-support/src/js/binding.rs @@ -553,6 +553,29 @@ impl<'a, 'b> JsBuilder<'a, 'b> { )); } + /// The given value is a number (`typeof val === 'number'`), it is + /// converted to a bigint. If the value is anything else, it will be + /// returned as is. + /// + /// If the number is not an integer, it will be truncated to an integer. + /// If the number is NaN, +Infinity, or -Infinity, the conversion will fail + /// and result in a runtime JS `RangeError`. + /// + /// The returned string the name of a variable that holds the converted + /// value. + fn number_to_bigint(&mut self, val: &str) -> String { + // This conversion relies on the WebAssembly API for correctness. The + // API automatically adjusts bigint values that are out of range, so we + // just have to pass it any bigint. We truncate the number to an integer + // to be consistent with the truncation the WebAssembly API does for + // 32-bit integers. + let new_var = format!("bigint{}", self.tmp()); + self.prelude(&format!( + "const {new_var} = typeof {val} === 'number' ? BigInt(Math.trunc({val})) : {val};" + )); + new_var + } + fn string_to_memory( &mut self, mem: walrus::MemoryId, @@ -675,11 +698,16 @@ fn instruction( } Instruction::IntToWasm { input, .. } => { - let val = js.pop(); + let mut val = js.pop(); if matches!( input, AdapterType::I64 | AdapterType::S64 | AdapterType::U64 ) { + // Regular JS numbers are commonly used for integers >32 bits, + // because they can represent integers up to 2^53 exactly. To + // support this, we allow both number and bigint for 64-bit + // integers. + val = js.number_to_bigint(&val); js.assert_bigint(&val); } else { js.assert_number(&val); @@ -921,9 +949,11 @@ fn instruction( } Instruction::FromOptionNative { ty } => { - let val = js.pop(); + let mut val = js.pop(); js.cx.expose_is_like_none(); if *ty == ValType::I64 { + // same as for Instruction::IntToWasm + val = js.number_to_bigint(&val); js.assert_optional_bigint(&val); } else { js.assert_optional_number(&val); diff --git a/crates/cli/tests/reference/add.d.ts b/crates/cli/tests/reference/add.d.ts index f11140413c4..3baabeff357 100644 --- a/crates/cli/tests/reference/add.d.ts +++ b/crates/cli/tests/reference/add.d.ts @@ -2,3 +2,7 @@ /* eslint-disable */ export function add_u32(a: number, b: number): number; export function add_i32(a: number, b: number): number; +export function add_u64(a: bigint, b: bigint): bigint; +export function add_i64(a: bigint, b: bigint): bigint; +export function add_option_u64(a: bigint | undefined, b: bigint): bigint | undefined; +export function add_option_i64(a: bigint | undefined, b: bigint): bigint | undefined; diff --git a/crates/cli/tests/reference/add.js b/crates/cli/tests/reference/add.js index 51914d7d296..78e706a9d9b 100644 --- a/crates/cli/tests/reference/add.js +++ b/crates/cli/tests/reference/add.js @@ -23,3 +23,77 @@ export function add_i32(a, b) { return ret; } +/** + * @param {bigint} a + * @param {bigint} b + * @returns {bigint} + */ +export function add_u64(a, b) { + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + const ret = wasm.add_u64(bigint0, bigint1); + return BigInt.asUintN(64, ret); +} + +/** + * @param {bigint} a + * @param {bigint} b + * @returns {bigint} + */ +export function add_i64(a, b) { + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + const ret = wasm.add_i64(bigint0, bigint1); + return ret; +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} +/** + * @param {bigint | undefined} a + * @param {bigint} b + * @returns {bigint | undefined} + */ +export function add_option_u64(a, b) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + wasm.add_option_u64(retptr, !isLikeNone(bigint0), isLikeNone(bigint0) ? BigInt(0) : bigint0, bigint1); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r2 = getDataViewMemory0().getBigInt64(retptr + 8 * 1, true); + return r0 === 0 ? undefined : BigInt.asUintN(64, r2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** + * @param {bigint | undefined} a + * @param {bigint} b + * @returns {bigint | undefined} + */ +export function add_option_i64(a, b) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const bigint0 = typeof a === 'number' ? BigInt(Math.trunc(a)) : a; + const bigint1 = typeof b === 'number' ? BigInt(Math.trunc(b)) : b; + wasm.add_option_i64(retptr, !isLikeNone(bigint0), isLikeNone(bigint0) ? BigInt(0) : bigint0, bigint1); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r2 = getDataViewMemory0().getBigInt64(retptr + 8 * 1, true); + return r0 === 0 ? undefined : r2; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + diff --git a/crates/cli/tests/reference/add.rs b/crates/cli/tests/reference/add.rs index f2cc3e7a05f..0b382cb8f28 100644 --- a/crates/cli/tests/reference/add.rs +++ b/crates/cli/tests/reference/add.rs @@ -9,3 +9,23 @@ pub fn add_u32(a: u32, b: u32) -> u32 { pub fn add_i32(a: i32, b: i32) -> i32 { a + b } + +#[wasm_bindgen] +pub fn add_u64(a: u64, b: u64) -> u64 { + a + b +} + +#[wasm_bindgen] +pub fn add_i64(a: i64, b: i64) -> i64 { + a + b +} + +#[wasm_bindgen] +pub fn add_option_u64(a: Option, b: u64) -> Option { + a.map(|a| a + b) +} + +#[wasm_bindgen] +pub fn add_option_i64(a: Option, b: i64) -> Option { + a.map(|a| a + b) +} diff --git a/crates/cli/tests/reference/add.wat b/crates/cli/tests/reference/add.wat index 62ab9c0876b..ac0408332c5 100644 --- a/crates/cli/tests/reference/add.wat +++ b/crates/cli/tests/reference/add.wat @@ -1,11 +1,24 @@ (module $reference_test.wasm - (type (;0;) (func (param i32 i32) (result i32))) - (func $add_u32 (;0;) (type 0) (param i32 i32) (result i32)) - (func $add_i32 (;1;) (type 0) (param i32 i32) (result i32)) + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32 i64 i64))) + (type (;3;) (func (param i64 i64) (result i64))) + (func $add_option_u64 (;0;) (type 2) (param i32 i32 i64 i64)) + (func $add_option_i64 (;1;) (type 2) (param i32 i32 i64 i64)) + (func $add_u32 (;2;) (type 1) (param i32 i32) (result i32)) + (func $add_i32 (;3;) (type 1) (param i32 i32) (result i32)) + (func $add_u64 (;4;) (type 3) (param i64 i64) (result i64)) + (func $add_i64 (;5;) (type 3) (param i64 i64) (result i64)) + (func $__wbindgen_add_to_stack_pointer (;6;) (type 0) (param i32) (result i32)) (memory (;0;) 17) (export "memory" (memory 0)) (export "add_u32" (func $add_u32)) (export "add_i32" (func $add_i32)) + (export "add_u64" (func $add_u64)) + (export "add_i64" (func $add_i64)) + (export "add_option_u64" (func $add_option_u64)) + (export "add_option_i64" (func $add_option_i64)) + (export "__wbindgen_add_to_stack_pointer" (func $__wbindgen_add_to_stack_pointer)) (@custom "target_features" (after code) "\02+\0fmutable-globals+\08sign-ext") ) diff --git a/crates/cli/tests/reference/date.d.ts b/crates/cli/tests/reference/date.d.ts new file mode 100644 index 00000000000..ab20322f079 --- /dev/null +++ b/crates/cli/tests/reference/date.d.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export function date_now(): bigint; diff --git a/crates/cli/tests/reference/date.js b/crates/cli/tests/reference/date.js new file mode 100644 index 00000000000..fbf0bc8c2fa --- /dev/null +++ b/crates/cli/tests/reference/date.js @@ -0,0 +1,19 @@ +let wasm; +export function __wbg_set_wasm(val) { + wasm = val; +} + +/** + * @returns {bigint} + */ +export function date_now() { + const ret = wasm.date_now(); + return BigInt.asUintN(64, ret); +} + +export function __wbg_now_df71ca5a8f45b624() { + const ret = Date.now(); + const bigint1 = typeof ret === 'number' ? BigInt(Math.trunc(ret)) : ret; + return bigint1; +}; + diff --git a/crates/cli/tests/reference/date.rs b/crates/cli/tests/reference/date.rs new file mode 100644 index 00000000000..3cafddd3d56 --- /dev/null +++ b/crates/cli/tests/reference/date.rs @@ -0,0 +1,14 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + pub type Date; + + #[wasm_bindgen(static_method_of = Date)] + pub fn now() -> u64; +} + +#[wasm_bindgen] +pub fn date_now() -> u64 { + Date::now() +} diff --git a/crates/cli/tests/reference/date.wat b/crates/cli/tests/reference/date.wat new file mode 100644 index 00000000000..1546c06227b --- /dev/null +++ b/crates/cli/tests/reference/date.wat @@ -0,0 +1,9 @@ +(module $reference_test.wasm + (type (;0;) (func (result i64))) + (func $date_now (;0;) (type 0) (result i64)) + (memory (;0;) 17) + (export "memory" (memory 0)) + (export "date_now" (func $date_now)) + (@custom "target_features" (after code) "\02+\0fmutable-globals+\08sign-ext") +) + diff --git a/tests/wasm/optional_primitives.js b/tests/wasm/optional_primitives.js index 89bf4df7eab..9047732d401 100644 --- a/tests/wasm/optional_primitives.js +++ b/tests/wasm/optional_primitives.js @@ -85,12 +85,23 @@ exports.js_works = () => { assert.strictEqual(wasm.optional_i64_identity(wasm.optional_i64_neg_one()), BigInt('-1')); assert.strictEqual(wasm.optional_i64_identity(wasm.optional_i64_min()), BigInt('-9223372036854775808')); assert.strictEqual(wasm.optional_i64_identity(wasm.optional_i64_max()), BigInt('9223372036854775807')); + assert.strictEqual(wasm.optional_i64_identity(0), BigInt('0')); + assert.strictEqual(wasm.optional_i64_identity(1.99), BigInt('1')); + assert.strictEqual(wasm.optional_i64_identity(-1.99), BigInt('-1')); + assert.throws(() => wasm.optional_i64_identity(NaN)); + assert.throws(() => wasm.optional_i64_identity(Infinity)); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_none()), undefined); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_zero()), BigInt('0')); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_one()), BigInt('1')); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_min()), BigInt('0')); assert.strictEqual(wasm.optional_u64_identity(wasm.optional_u64_max()), BigInt('18446744073709551615')); + assert.strictEqual(wasm.optional_u64_identity(0), BigInt('0')); + assert.strictEqual(wasm.optional_u64_identity(1.99), BigInt('1')); + assert.strictEqual(wasm.optional_u64_identity(-1n), BigInt('18446744073709551615')); // because modulo 2^64 + assert.strictEqual(wasm.optional_u64_identity(-1.99), BigInt('18446744073709551615')); // because modulo 2^64 + assert.throws(() => wasm.optional_u64_identity(NaN)); + assert.throws(() => wasm.optional_u64_identity(Infinity)); assert.strictEqual(wasm.optional_bool_identity(wasm.optional_bool_none()), undefined); assert.strictEqual(wasm.optional_bool_identity(wasm.optional_bool_false()), false);