Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for i128 and u128 #4222

Merged
merged 12 commits into from
Nov 11, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
* Added bindings for `MediaStreamTrack.getCapabilities`.
[#4236](https://github.com/rustwasm/wasm-bindgen/pull/4236)

* Added WASM ABI support for `u128` and `i128`
[#4222](https://github.com/rustwasm/wasm-bindgen/pull/4222)

### Changed

* String enums now generate private TypeScript types but only if used.
Expand Down
6 changes: 6 additions & 0 deletions crates/cli-support/src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ tys! {
U32
I64
U64
I128
U128
F32
F64
BOOLEAN
Expand Down Expand Up @@ -55,6 +57,8 @@ pub enum Descriptor {
U32,
I64,
U64,
I128,
U128,
F32,
F64,
Boolean,
Expand Down Expand Up @@ -132,11 +136,13 @@ impl Descriptor {
I16 => Descriptor::I16,
I32 => Descriptor::I32,
I64 => Descriptor::I64,
I128 => Descriptor::I128,
U8 if clamped => Descriptor::ClampedU8,
U8 => Descriptor::U8,
U16 => Descriptor::U16,
U32 => Descriptor::U32,
U64 => Descriptor::U64,
U128 => Descriptor::U128,
F32 => Descriptor::F32,
F64 => Descriptor::F64,
BOOLEAN => Descriptor::Boolean,
Expand Down
55 changes: 54 additions & 1 deletion crates/cli-support/src/js/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,23 @@ fn instruction(
)
}

fn int128_to_int64x2(val: &str) -> (String, String) {
// we don't need to perform any conversion here, because the JS
// WebAssembly API will automatically convert the bigints to 64 bits
// for us. This even allows us to ignore signedness.
let low = val.to_owned();
let high = format!("{val} >> BigInt(64)");
(low, high)
}
fn int64x2_to_int128(low: String, high: String, signed: bool) -> String {
let low = format!("BigInt.asUintN(64, {low})");
if signed {
format!("({low} | ({high} << BigInt(64)))")
} else {
format!("({low} | (BigInt.asUintN(64, {high}) << BigInt(64)))")
}
}

match instr {
Instruction::ArgGet(n) => {
let arg = js.arg(*n).to_string();
Expand Down Expand Up @@ -712,6 +729,36 @@ fn instruction(
}
}

Instruction::Int128ToWasm => {
let val = js.pop();
js.assert_bigint(&val);
let (low, high) = int128_to_int64x2(&val);
js.push(low);
js.push(high);
}
Instruction::WasmToInt128 { signed } => {
let high = js.pop();
let low = js.pop();
js.push(int64x2_to_int128(low, high, *signed));
}

Instruction::OptionInt128ToWasm => {
let val = js.pop();
js.cx.expose_is_like_none();
js.assert_optional_bigint(&val);
let (low, high) = int128_to_int64x2(&val);
js.push(format!("!isLikeNone({val})"));
js.push(format!("isLikeNone({val}) ? BigInt(0) : {low}"));
js.push(format!("isLikeNone({val}) ? BigInt(0) : {high}"));
}
Instruction::OptionWasmToInt128 { signed } => {
let high = js.pop();
let low = js.pop();
let present = js.pop();
let val = int64x2_to_int128(low, high, *signed);
js.push(format!("{present} === 0 ? undefined : {val}"));
}

Instruction::WasmToStringEnum { name } => {
let index = js.pop();
js.cx.expose_string_enum(name);
Expand Down Expand Up @@ -983,6 +1030,8 @@ fn instruction(
js.push(format!(
"isLikeNone({val}) ? {zero} : {val}",
zero = if *ty == ValType::I64 {
// We can't use bigint literals for now. See:
// https://github.com/rustwasm/wasm-bindgen/issues/4246
"BigInt(0)"
} else {
"0"
Expand Down Expand Up @@ -1500,7 +1549,11 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet<TsRe
| AdapterType::F32
| AdapterType::F64
| AdapterType::NonNull => dst.push_str("number"),
AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("bigint"),
AdapterType::I64
| AdapterType::S64
| AdapterType::U64
| AdapterType::S128
| AdapterType::U128 => dst.push_str("bigint"),
AdapterType::String => dst.push_str("string"),
AdapterType::Externref => dst.push_str("any"),
AdapterType::Bool => dst.push_str("boolean"),
Expand Down
28 changes: 28 additions & 0 deletions crates/cli-support/src/wit/incoming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,20 @@ impl InstructionBuilder<'_, '_> {
Descriptor::U32 => self.number(AdapterType::U32, WasmVT::I32),
Descriptor::I64 => self.number(AdapterType::S64, WasmVT::I64),
Descriptor::U64 => self.number(AdapterType::U64, WasmVT::I64),
Descriptor::I128 => {
self.instruction(
&[AdapterType::S128],
Instruction::Int128ToWasm,
&[AdapterType::I64, AdapterType::I64],
);
}
Descriptor::U128 => {
self.instruction(
&[AdapterType::U128],
Instruction::Int128ToWasm,
&[AdapterType::I64, AdapterType::I64],
);
}
Descriptor::F32 => {
self.get(AdapterType::F32);
self.output.push(AdapterType::F32);
Expand Down Expand Up @@ -285,6 +299,20 @@ impl InstructionBuilder<'_, '_> {
Descriptor::F32 => self.in_option_sentinel64_f32(AdapterType::F32),
Descriptor::F64 => self.in_option_native(ValType::F64),
Descriptor::I64 | Descriptor::U64 => self.in_option_native(ValType::I64),
Descriptor::I128 => {
self.instruction(
&[AdapterType::S128.option()],
Instruction::OptionInt128ToWasm,
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
);
}
Descriptor::U128 => {
self.instruction(
&[AdapterType::U128.option()],
Instruction::OptionInt128ToWasm,
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
);
}
Descriptor::Boolean => {
self.instruction(
&[AdapterType::Bool.option()],
Expand Down
30 changes: 30 additions & 0 deletions crates/cli-support/src/wit/outgoing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ impl InstructionBuilder<'_, '_> {
Descriptor::U32 => self.outgoing_i32(AdapterType::U32),
Descriptor::I64 => self.outgoing_i64(AdapterType::I64),
Descriptor::U64 => self.outgoing_i64(AdapterType::U64),
Descriptor::I128 => {
self.instruction(
&[AdapterType::I64, AdapterType::I64],
Instruction::WasmToInt128 { signed: true },
&[AdapterType::S128],
);
}
Descriptor::U128 => {
self.instruction(
&[AdapterType::I64, AdapterType::I64],
Instruction::WasmToInt128 { signed: false },
&[AdapterType::U128],
);
}
Descriptor::F32 => {
self.get(AdapterType::F32);
self.output.push(AdapterType::F32);
Expand Down Expand Up @@ -267,6 +281,20 @@ impl InstructionBuilder<'_, '_> {
Descriptor::U64 => self.option_native(false, ValType::I64),
Descriptor::F32 => self.out_option_sentinel64(AdapterType::F32),
Descriptor::F64 => self.option_native(true, ValType::F64),
Descriptor::I128 => {
self.instruction(
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
Instruction::OptionWasmToInt128 { signed: true },
&[AdapterType::S128.option()],
);
}
Descriptor::U128 => {
self.instruction(
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
Instruction::OptionWasmToInt128 { signed: false },
&[AdapterType::U128.option()],
);
}
Descriptor::Boolean => {
self.instruction(
&[AdapterType::I32],
Expand Down Expand Up @@ -360,6 +388,8 @@ impl InstructionBuilder<'_, '_> {
| Descriptor::F64
| Descriptor::I64
| Descriptor::U64
| Descriptor::I128
| Descriptor::U128
| Descriptor::Boolean
| Descriptor::Char
| Descriptor::Enum { .. }
Expand Down
14 changes: 14 additions & 0 deletions crates/cli-support/src/wit/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@ pub enum AdapterType {
S16,
S32,
S64,
S128,
U8,
U16,
U32,
U64,
U128,
F32,
F64,
String,
Expand Down Expand Up @@ -145,6 +147,18 @@ pub enum Instruction {
output: AdapterType,
},

/// Pops a 128-bit integer and pushes 2 Wasm 64-bit ints.
Int128ToWasm,
/// Pops 2 Wasm 64-bit ints and pushes a 128-bit integer.
WasmToInt128 {
signed: bool,
},

OptionInt128ToWasm,
OptionWasmToInt128 {
signed: bool,
},

/// Pops a Wasm `i32` and pushes the enum variant as a string
WasmToStringEnum {
name: String,
Expand Down
7 changes: 7 additions & 0 deletions crates/cli/tests/reference/int128.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* tslint:disable */
/* eslint-disable */
export function echo_i128(a: bigint): bigint;
export function echo_u128(a: bigint): bigint;
export function echo_option_i128(a?: bigint): bigint | undefined;
export function echo_option_u128(a?: bigint): bigint | undefined;
export function throw_i128(): bigint;
74 changes: 74 additions & 0 deletions crates/cli/tests/reference/int128.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
let wasm;
export function __wbg_set_wasm(val) {
wasm = val;
}

/**
* @param {bigint} a
* @returns {bigint}
*/
export function echo_i128(a) {
const ret = wasm.echo_i128(a, a >> BigInt(64));
return (BigInt.asUintN(64, ret[0]) | (ret[1] << BigInt(64)));
}

/**
* @param {bigint} a
* @returns {bigint}
*/
export function echo_u128(a) {
const ret = wasm.echo_u128(a, a >> BigInt(64));
return (BigInt.asUintN(64, ret[0]) | (BigInt.asUintN(64, ret[1]) << BigInt(64)));
}

function isLikeNone(x) {
return x === undefined || x === null;
}
/**
* @param {bigint | undefined} [a]
* @returns {bigint | undefined}
*/
export function echo_option_i128(a) {
const ret = wasm.echo_option_i128(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a, isLikeNone(a) ? BigInt(0) : a >> BigInt(64));
return ret[0] === 0 ? undefined : (BigInt.asUintN(64, ret[1]) | (ret[2] << BigInt(64)));
}

/**
* @param {bigint | undefined} [a]
* @returns {bigint | undefined}
*/
export function echo_option_u128(a) {
const ret = wasm.echo_option_u128(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a, isLikeNone(a) ? BigInt(0) : a >> BigInt(64));
return ret[0] === 0 ? undefined : (BigInt.asUintN(64, ret[1]) | (BigInt.asUintN(64, ret[2]) << BigInt(64)));
}

const heap = new Array(128).fill(undefined);

heap.push(undefined, null, true, false);

function getObject(idx) { return heap[idx]; }

let heap_next = heap.length;

function dropObject(idx) {
if (idx < 132) return;
heap[idx] = heap_next;
heap_next = idx;
}

function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
/**
* @returns {bigint}
*/
export function throw_i128() {
const ret = wasm.throw_i128();
if (ret[3]) {
throw takeObject(ret[2]);
}
return (BigInt.asUintN(64, ret[0]) | (ret[1] << BigInt(64)));
}

28 changes: 28 additions & 0 deletions crates/cli/tests/reference/int128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn echo_i128(a: i128) -> i128 {
a
}
#[wasm_bindgen]
pub fn echo_u128(a: u128) -> u128 {
a
}

#[wasm_bindgen]
pub fn echo_option_i128(a: Option<i128>) -> Option<i128> {
a
}
#[wasm_bindgen]
pub fn echo_option_u128(a: Option<u128>) -> Option<u128> {
a
}

#[wasm_bindgen]
pub fn throw_i128() -> Result<i128, JsError> {
Ok(0_i128)
}
// #[wasm_bindgen]
// pub fn throw_option_i128() -> Result<Option<i128>, JsError> {
// Ok(None)
// }
19 changes: 19 additions & 0 deletions crates/cli/tests/reference/int128.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
(module $reference_test.wasm
(type (;0;) (func (result i64 i64 i32 i32)))
(type (;1;) (func (param i32 i64 i64) (result i32 i64 i64)))
(type (;2;) (func (param i64 i64) (result i64 i64)))
(func $"echo_option_i128 multivalue shim" (;0;) (type 1) (param i32 i64 i64) (result i32 i64 i64))
(func $"echo_option_u128 multivalue shim" (;1;) (type 1) (param i32 i64 i64) (result i32 i64 i64))
(func $"throw_i128 multivalue shim" (;2;) (type 0) (result i64 i64 i32 i32))
(func $"echo_i128 multivalue shim" (;3;) (type 2) (param i64 i64) (result i64 i64))
(func $"echo_u128 multivalue shim" (;4;) (type 2) (param i64 i64) (result i64 i64))
(memory (;0;) 17)
(export "memory" (memory 0))
(export "echo_i128" (func $"echo_i128 multivalue shim"))
(export "echo_u128" (func $"echo_u128 multivalue shim"))
(export "echo_option_i128" (func $"echo_option_i128 multivalue shim"))
(export "echo_option_u128" (func $"echo_option_u128 multivalue shim"))
(export "throw_i128" (func $"throw_i128 multivalue shim"))
(@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")
)

2 changes: 1 addition & 1 deletion crates/macro/ui-tests/missing-catch.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ error[E0277]: the trait bound `Result<wasm_bindgen::JsValue, wasm_bindgen::JsVal
i16
i32
i64
usize
i128
and $N others
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
2 changes: 1 addition & 1 deletion crates/macro/ui-tests/traits-not-implemented.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ error[E0277]: the trait bound `A: IntoWasmAbi` is not satisfied
i16
i32
i64
usize
i128
and $N others
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
Loading