Skip to content

Commit df9893b

Browse files
Add support for i128 and u128 (#4222)
1 parent 2463d0d commit df9893b

File tree

17 files changed

+397
-7
lines changed

17 files changed

+397
-7
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
* Added bindings for `MediaStreamTrack.getCapabilities`.
2121
[#4236](https://github.com/rustwasm/wasm-bindgen/pull/4236)
2222

23+
* Added WASM ABI support for `u128` and `i128`
24+
[#4222](https://github.com/rustwasm/wasm-bindgen/pull/4222)
25+
2326
### Changed
2427

2528
* String enums now generate private TypeScript types but only if used.

crates/cli-support/src/descriptor.rs

+6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ tys! {
1919
U32
2020
I64
2121
U64
22+
I128
23+
U128
2224
F32
2325
F64
2426
BOOLEAN
@@ -55,6 +57,8 @@ pub enum Descriptor {
5557
U32,
5658
I64,
5759
U64,
60+
I128,
61+
U128,
5862
F32,
5963
F64,
6064
Boolean,
@@ -132,11 +136,13 @@ impl Descriptor {
132136
I16 => Descriptor::I16,
133137
I32 => Descriptor::I32,
134138
I64 => Descriptor::I64,
139+
I128 => Descriptor::I128,
135140
U8 if clamped => Descriptor::ClampedU8,
136141
U8 => Descriptor::U8,
137142
U16 => Descriptor::U16,
138143
U32 => Descriptor::U32,
139144
U64 => Descriptor::U64,
145+
U128 => Descriptor::U128,
140146
F32 => Descriptor::F32,
141147
F64 => Descriptor::F64,
142148
BOOLEAN => Descriptor::Boolean,

crates/cli-support/src/js/binding.rs

+54-1
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,23 @@ fn instruction(
620620
)
621621
}
622622

623+
fn int128_to_int64x2(val: &str) -> (String, String) {
624+
// we don't need to perform any conversion here, because the JS
625+
// WebAssembly API will automatically convert the bigints to 64 bits
626+
// for us. This even allows us to ignore signedness.
627+
let low = val.to_owned();
628+
let high = format!("{val} >> BigInt(64)");
629+
(low, high)
630+
}
631+
fn int64x2_to_int128(low: String, high: String, signed: bool) -> String {
632+
let low = format!("BigInt.asUintN(64, {low})");
633+
if signed {
634+
format!("({low} | ({high} << BigInt(64)))")
635+
} else {
636+
format!("({low} | (BigInt.asUintN(64, {high}) << BigInt(64)))")
637+
}
638+
}
639+
623640
match instr {
624641
Instruction::ArgGet(n) => {
625642
let arg = js.arg(*n).to_string();
@@ -712,6 +729,36 @@ fn instruction(
712729
}
713730
}
714731

732+
Instruction::Int128ToWasm => {
733+
let val = js.pop();
734+
js.assert_bigint(&val);
735+
let (low, high) = int128_to_int64x2(&val);
736+
js.push(low);
737+
js.push(high);
738+
}
739+
Instruction::WasmToInt128 { signed } => {
740+
let high = js.pop();
741+
let low = js.pop();
742+
js.push(int64x2_to_int128(low, high, *signed));
743+
}
744+
745+
Instruction::OptionInt128ToWasm => {
746+
let val = js.pop();
747+
js.cx.expose_is_like_none();
748+
js.assert_optional_bigint(&val);
749+
let (low, high) = int128_to_int64x2(&val);
750+
js.push(format!("!isLikeNone({val})"));
751+
js.push(format!("isLikeNone({val}) ? BigInt(0) : {low}"));
752+
js.push(format!("isLikeNone({val}) ? BigInt(0) : {high}"));
753+
}
754+
Instruction::OptionWasmToInt128 { signed } => {
755+
let high = js.pop();
756+
let low = js.pop();
757+
let present = js.pop();
758+
let val = int64x2_to_int128(low, high, *signed);
759+
js.push(format!("{present} === 0 ? undefined : {val}"));
760+
}
761+
715762
Instruction::WasmToStringEnum { name } => {
716763
let index = js.pop();
717764
js.cx.expose_string_enum(name);
@@ -983,6 +1030,8 @@ fn instruction(
9831030
js.push(format!(
9841031
"isLikeNone({val}) ? {zero} : {val}",
9851032
zero = if *ty == ValType::I64 {
1033+
// We can't use bigint literals for now. See:
1034+
// https://github.com/rustwasm/wasm-bindgen/issues/4246
9861035
"BigInt(0)"
9871036
} else {
9881037
"0"
@@ -1500,7 +1549,11 @@ fn adapter2ts(ty: &AdapterType, dst: &mut String, refs: Option<&mut HashSet<TsRe
15001549
| AdapterType::F32
15011550
| AdapterType::F64
15021551
| AdapterType::NonNull => dst.push_str("number"),
1503-
AdapterType::I64 | AdapterType::S64 | AdapterType::U64 => dst.push_str("bigint"),
1552+
AdapterType::I64
1553+
| AdapterType::S64
1554+
| AdapterType::U64
1555+
| AdapterType::S128
1556+
| AdapterType::U128 => dst.push_str("bigint"),
15041557
AdapterType::String => dst.push_str("string"),
15051558
AdapterType::Externref => dst.push_str("any"),
15061559
AdapterType::Bool => dst.push_str("boolean"),

crates/cli-support/src/wit/incoming.rs

+28
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ impl InstructionBuilder<'_, '_> {
9090
Descriptor::U32 => self.number(AdapterType::U32, WasmVT::I32),
9191
Descriptor::I64 => self.number(AdapterType::S64, WasmVT::I64),
9292
Descriptor::U64 => self.number(AdapterType::U64, WasmVT::I64),
93+
Descriptor::I128 => {
94+
self.instruction(
95+
&[AdapterType::S128],
96+
Instruction::Int128ToWasm,
97+
&[AdapterType::I64, AdapterType::I64],
98+
);
99+
}
100+
Descriptor::U128 => {
101+
self.instruction(
102+
&[AdapterType::U128],
103+
Instruction::Int128ToWasm,
104+
&[AdapterType::I64, AdapterType::I64],
105+
);
106+
}
93107
Descriptor::F32 => {
94108
self.get(AdapterType::F32);
95109
self.output.push(AdapterType::F32);
@@ -285,6 +299,20 @@ impl InstructionBuilder<'_, '_> {
285299
Descriptor::F32 => self.in_option_sentinel64_f32(AdapterType::F32),
286300
Descriptor::F64 => self.in_option_native(ValType::F64),
287301
Descriptor::I64 | Descriptor::U64 => self.in_option_native(ValType::I64),
302+
Descriptor::I128 => {
303+
self.instruction(
304+
&[AdapterType::S128.option()],
305+
Instruction::OptionInt128ToWasm,
306+
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
307+
);
308+
}
309+
Descriptor::U128 => {
310+
self.instruction(
311+
&[AdapterType::U128.option()],
312+
Instruction::OptionInt128ToWasm,
313+
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
314+
);
315+
}
288316
Descriptor::Boolean => {
289317
self.instruction(
290318
&[AdapterType::Bool.option()],

crates/cli-support/src/wit/outgoing.rs

+30
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,20 @@ impl InstructionBuilder<'_, '_> {
6565
Descriptor::U32 => self.outgoing_i32(AdapterType::U32),
6666
Descriptor::I64 => self.outgoing_i64(AdapterType::I64),
6767
Descriptor::U64 => self.outgoing_i64(AdapterType::U64),
68+
Descriptor::I128 => {
69+
self.instruction(
70+
&[AdapterType::I64, AdapterType::I64],
71+
Instruction::WasmToInt128 { signed: true },
72+
&[AdapterType::S128],
73+
);
74+
}
75+
Descriptor::U128 => {
76+
self.instruction(
77+
&[AdapterType::I64, AdapterType::I64],
78+
Instruction::WasmToInt128 { signed: false },
79+
&[AdapterType::U128],
80+
);
81+
}
6882
Descriptor::F32 => {
6983
self.get(AdapterType::F32);
7084
self.output.push(AdapterType::F32);
@@ -267,6 +281,20 @@ impl InstructionBuilder<'_, '_> {
267281
Descriptor::U64 => self.option_native(false, ValType::I64),
268282
Descriptor::F32 => self.out_option_sentinel64(AdapterType::F32),
269283
Descriptor::F64 => self.option_native(true, ValType::F64),
284+
Descriptor::I128 => {
285+
self.instruction(
286+
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
287+
Instruction::OptionWasmToInt128 { signed: true },
288+
&[AdapterType::S128.option()],
289+
);
290+
}
291+
Descriptor::U128 => {
292+
self.instruction(
293+
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
294+
Instruction::OptionWasmToInt128 { signed: false },
295+
&[AdapterType::U128.option()],
296+
);
297+
}
270298
Descriptor::Boolean => {
271299
self.instruction(
272300
&[AdapterType::I32],
@@ -360,6 +388,8 @@ impl InstructionBuilder<'_, '_> {
360388
| Descriptor::F64
361389
| Descriptor::I64
362390
| Descriptor::U64
391+
| Descriptor::I128
392+
| Descriptor::U128
363393
| Descriptor::Boolean
364394
| Descriptor::Char
365395
| Descriptor::Enum { .. }

crates/cli-support/src/wit/standard.rs

+14
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,12 @@ pub enum AdapterType {
7575
S16,
7676
S32,
7777
S64,
78+
S128,
7879
U8,
7980
U16,
8081
U32,
8182
U64,
83+
U128,
8284
F32,
8385
F64,
8486
String,
@@ -145,6 +147,18 @@ pub enum Instruction {
145147
output: AdapterType,
146148
},
147149

150+
/// Pops a 128-bit integer and pushes 2 Wasm 64-bit ints.
151+
Int128ToWasm,
152+
/// Pops 2 Wasm 64-bit ints and pushes a 128-bit integer.
153+
WasmToInt128 {
154+
signed: bool,
155+
},
156+
157+
OptionInt128ToWasm,
158+
OptionWasmToInt128 {
159+
signed: bool,
160+
},
161+
148162
/// Pops a Wasm `i32` and pushes the enum variant as a string
149163
WasmToStringEnum {
150164
name: String,
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export function echo_i128(a: bigint): bigint;
4+
export function echo_u128(a: bigint): bigint;
5+
export function echo_option_i128(a?: bigint): bigint | undefined;
6+
export function echo_option_u128(a?: bigint): bigint | undefined;
7+
export function throw_i128(): bigint;

crates/cli/tests/reference/int128.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
let wasm;
2+
export function __wbg_set_wasm(val) {
3+
wasm = val;
4+
}
5+
6+
/**
7+
* @param {bigint} a
8+
* @returns {bigint}
9+
*/
10+
export function echo_i128(a) {
11+
const ret = wasm.echo_i128(a, a >> BigInt(64));
12+
return (BigInt.asUintN(64, ret[0]) | (ret[1] << BigInt(64)));
13+
}
14+
15+
/**
16+
* @param {bigint} a
17+
* @returns {bigint}
18+
*/
19+
export function echo_u128(a) {
20+
const ret = wasm.echo_u128(a, a >> BigInt(64));
21+
return (BigInt.asUintN(64, ret[0]) | (BigInt.asUintN(64, ret[1]) << BigInt(64)));
22+
}
23+
24+
function isLikeNone(x) {
25+
return x === undefined || x === null;
26+
}
27+
/**
28+
* @param {bigint | undefined} [a]
29+
* @returns {bigint | undefined}
30+
*/
31+
export function echo_option_i128(a) {
32+
const ret = wasm.echo_option_i128(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a, isLikeNone(a) ? BigInt(0) : a >> BigInt(64));
33+
return ret[0] === 0 ? undefined : (BigInt.asUintN(64, ret[1]) | (ret[2] << BigInt(64)));
34+
}
35+
36+
/**
37+
* @param {bigint | undefined} [a]
38+
* @returns {bigint | undefined}
39+
*/
40+
export function echo_option_u128(a) {
41+
const ret = wasm.echo_option_u128(!isLikeNone(a), isLikeNone(a) ? BigInt(0) : a, isLikeNone(a) ? BigInt(0) : a >> BigInt(64));
42+
return ret[0] === 0 ? undefined : (BigInt.asUintN(64, ret[1]) | (BigInt.asUintN(64, ret[2]) << BigInt(64)));
43+
}
44+
45+
const heap = new Array(128).fill(undefined);
46+
47+
heap.push(undefined, null, true, false);
48+
49+
function getObject(idx) { return heap[idx]; }
50+
51+
let heap_next = heap.length;
52+
53+
function dropObject(idx) {
54+
if (idx < 132) return;
55+
heap[idx] = heap_next;
56+
heap_next = idx;
57+
}
58+
59+
function takeObject(idx) {
60+
const ret = getObject(idx);
61+
dropObject(idx);
62+
return ret;
63+
}
64+
/**
65+
* @returns {bigint}
66+
*/
67+
export function throw_i128() {
68+
const ret = wasm.throw_i128();
69+
if (ret[3]) {
70+
throw takeObject(ret[2]);
71+
}
72+
return (BigInt.asUintN(64, ret[0]) | (ret[1] << BigInt(64)));
73+
}
74+

crates/cli/tests/reference/int128.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use wasm_bindgen::prelude::*;
2+
3+
#[wasm_bindgen]
4+
pub fn echo_i128(a: i128) -> i128 {
5+
a
6+
}
7+
#[wasm_bindgen]
8+
pub fn echo_u128(a: u128) -> u128 {
9+
a
10+
}
11+
12+
#[wasm_bindgen]
13+
pub fn echo_option_i128(a: Option<i128>) -> Option<i128> {
14+
a
15+
}
16+
#[wasm_bindgen]
17+
pub fn echo_option_u128(a: Option<u128>) -> Option<u128> {
18+
a
19+
}
20+
21+
#[wasm_bindgen]
22+
pub fn throw_i128() -> Result<i128, JsError> {
23+
Ok(0_i128)
24+
}
25+
// #[wasm_bindgen]
26+
// pub fn throw_option_i128() -> Result<Option<i128>, JsError> {
27+
// Ok(None)
28+
// }

crates/cli/tests/reference/int128.wat

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
(module $reference_test.wasm
2+
(type (;0;) (func (result i64 i64 i32 i32)))
3+
(type (;1;) (func (param i32 i64 i64) (result i32 i64 i64)))
4+
(type (;2;) (func (param i64 i64) (result i64 i64)))
5+
(func $"echo_option_i128 multivalue shim" (;0;) (type 1) (param i32 i64 i64) (result i32 i64 i64))
6+
(func $"echo_option_u128 multivalue shim" (;1;) (type 1) (param i32 i64 i64) (result i32 i64 i64))
7+
(func $"throw_i128 multivalue shim" (;2;) (type 0) (result i64 i64 i32 i32))
8+
(func $"echo_i128 multivalue shim" (;3;) (type 2) (param i64 i64) (result i64 i64))
9+
(func $"echo_u128 multivalue shim" (;4;) (type 2) (param i64 i64) (result i64 i64))
10+
(memory (;0;) 17)
11+
(export "memory" (memory 0))
12+
(export "echo_i128" (func $"echo_i128 multivalue shim"))
13+
(export "echo_u128" (func $"echo_u128 multivalue shim"))
14+
(export "echo_option_i128" (func $"echo_option_i128 multivalue shim"))
15+
(export "echo_option_u128" (func $"echo_option_u128 multivalue shim"))
16+
(export "throw_i128" (func $"throw_i128 multivalue shim"))
17+
(@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")
18+
)
19+

crates/macro/ui-tests/missing-catch.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ error[E0277]: the trait bound `Result<wasm_bindgen::JsValue, wasm_bindgen::JsVal
1515
i16
1616
i32
1717
i64
18-
usize
18+
i128
1919
and $N others
2020
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)

crates/macro/ui-tests/traits-not-implemented.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ error[E0277]: the trait bound `A: IntoWasmAbi` is not satisfied
1212
i16
1313
i32
1414
i64
15-
usize
15+
i128
1616
and $N others
1717
= note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)