Skip to content

Commit

Permalink
Add support for i128 and u128 (#4222)
Browse files Browse the repository at this point in the history
  • Loading branch information
RunDevelopment authored Nov 11, 2024
1 parent 2463d0d commit df9893b
Show file tree
Hide file tree
Showing 17 changed files with 397 additions and 7 deletions.
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

0 comments on commit df9893b

Please sign in to comment.