Skip to content

Commit 7aa4f64

Browse files
authored
Add BigInt64Array polyfill for Safari 14 (#17103)
As discussed in #16693, -s WASM_BIGINT requires both BigInt (present since Safari 14.0) and BigInt64Array (present since Safari 15.0). Thus, users that want to target Safari 14.0 need a polyfill for BigInt64Array, which this adds.
1 parent 4b5c4f0 commit 7aa4f64

File tree

5 files changed

+268
-0
lines changed

5 files changed

+268
-0
lines changed

ChangeLog.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.15
2222
------
23+
- Added a shim for `BigInt64Array` so `-sWASM_BIGINT` can be used in Safari
24+
v14. (#17103)
2325

2426
3.1.14 - 06/20/2022
2527
-------------------

src/polyfill/bigint64array.js

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#if !POLYFILL
2+
assert(false, "this file should never be included unless POLYFILL is set");
3+
#endif
4+
5+
if (typeof globalThis.BigInt64Array === "undefined") {
6+
// BigInt64Array polyfill for Safari versions between v14.0 and v15.0.
7+
// All browsers other than Safari added BigInt and BigInt64Array at the same
8+
// time, but Safari introduced BigInt in v14.0 and introduced BigInt64Array in
9+
// v15.0
10+
11+
function partsToBigIntSigned(lower, upper) {
12+
return BigInt(lower) | (BigInt(upper + 2 * (upper & 0x80000000)) << 32n);
13+
}
14+
15+
function partsToBigIntUnsigned(lower, upper) {
16+
return BigInt(lower) | (BigInt(upper) << 32n);
17+
}
18+
19+
function bigIntToParts(value) {
20+
var lower = Number(BigInt(value) & BigInt(0xffffffff)) | 0;
21+
var upper = Number(BigInt(value) >> 32n) | 0;
22+
return [lower, upper];
23+
}
24+
25+
function createBigIntArrayShim(partsToBigInt) {
26+
function createBigInt64Array(array) {
27+
if (typeof array === "number") {
28+
array = new Uint32Array(2 * array);
29+
}
30+
var orig_array;
31+
if (!ArrayBuffer.isView(array)) {
32+
if (array.constructor && array.constructor.name === "ArrayBuffer") {
33+
array = new Uint32Array(array);
34+
} else {
35+
orig_array = array;
36+
array = new Uint32Array(array.length * 2);
37+
}
38+
}
39+
var proxy = new Proxy(
40+
{
41+
slice: function (min, max) {
42+
if (max === undefined) {
43+
max = array.length;
44+
}
45+
var new_buf = array.slice(min * 2, max * 2);
46+
return createBigInt64Array(new_buf);
47+
},
48+
subarray: function (min, max) {
49+
var new_buf = array.subarray(min * 2, max * 2);
50+
return createBigInt64Array(new_buf);
51+
},
52+
[Symbol.iterator]: function* () {
53+
for (var i = 0; i < array.length / 2; i++) {
54+
yield partsToBigInt(array[2 * i], array[2 * i + 1]);
55+
}
56+
},
57+
BYTES_PER_ELEMENT: 2 * array.BYTES_PER_ELEMENT,
58+
buffer: array.buffer,
59+
byteLength: array.byteLength,
60+
byteOffset: array.byteOffset,
61+
length: array.length / 2,
62+
copyWithin: function (target, start, end) {
63+
array.copyWithin(target * 2, start * 2, end * 2);
64+
return proxy;
65+
},
66+
set: function (source, targetOffset) {
67+
if (targetOffset === undefined) {
68+
targetOffset = 0;
69+
}
70+
if (2 * (source.length + targetOffset) > array.length) {
71+
// This is the Chrome error message
72+
// Firefox: "invalid or out-of-range index"
73+
throw new RangeError("offset is out of bounds");
74+
}
75+
for (var i = 0; i < source.length; i++) {
76+
var value = source[i];
77+
var pair = bigIntToParts(value);
78+
array.set(pair, 2 * (targetOffset + i));
79+
}
80+
},
81+
},
82+
{
83+
get: function (target, idx, receiver) {
84+
if (typeof idx !== "string" || !/^\d+$/.test(idx)) {
85+
return Reflect.get(target, idx, receiver);
86+
}
87+
var lower = array[idx * 2];
88+
var upper = array[idx * 2 + 1];
89+
return partsToBigInt(lower, upper);
90+
},
91+
set: function (target, idx, value, receiver) {
92+
if (typeof idx !== "string" || !/^\d+$/.test(idx)) {
93+
return Reflect.set(target, idx, value, receiver);
94+
}
95+
if (typeof value !== "bigint") {
96+
// Chrome error message, Firefox has no "a" in front if "BigInt".
97+
throw new TypeError(`Cannot convert ${value} to a BigInt`);
98+
}
99+
var pair = bigIntToParts(value);
100+
array.set(pair, 2 * idx);
101+
return true;
102+
},
103+
}
104+
);
105+
if (orig_array) {
106+
proxy.set(orig_array);
107+
}
108+
return proxy;
109+
}
110+
return createBigInt64Array;
111+
}
112+
113+
globalThis.BigUint64Array = createBigIntArrayShim(partsToBigIntUnsigned);
114+
globalThis.BigInt64Array = createBigIntArrayShim(partsToBigIntSigned);
115+
}

src/shell.js

+5
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ var Module = typeof {{{ EXPORT_NAME }}} != 'undefined' ? {{{ EXPORT_NAME }}} : {
5151
#if MIN_CHROME_VERSION < 45 || MIN_EDGE_VERSION < 12 || MIN_FIREFOX_VERSION < 34 || MIN_IE_VERSION != TARGET_NOT_SUPPORTED || MIN_SAFARI_VERSION < 90000
5252
#include "polyfill/objassign.js"
5353
#endif
54+
55+
// See https://caniuse.com/mdn-javascript_builtins_bigint64array
56+
#if WASM_BIGINT && MIN_SAFARI_VERSION < 150000
57+
#include "polyfill/bigint64array.js"
58+
#endif
5459
#endif // POLYFILL
5560

5661
#if MODULARIZE

tests/test_bigint64array_polyfill.js

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
let result = {};
2+
result.BigInt64Array_name = BigInt64Array.name;
3+
let arr2signed = new BigInt64Array(arr1signed.buffer);
4+
let arr2unsigned = new BigUint64Array(arr1unsigned.buffer);
5+
6+
result.arr1_to_arr1_signed = [];
7+
for(let x of bigint_list){
8+
arr1signed[0] = x;
9+
result.arr1_to_arr1_signed.push(arr1signed[0]);
10+
}
11+
12+
result.arr1_to_arr1_unsigned = [];
13+
for(let x of bigint_list){
14+
arr1unsigned[0] = x;
15+
result.arr1_to_arr1_unsigned.push(arr1unsigned[0]);
16+
}
17+
18+
result.arr1_to_arr2_signed = [];
19+
for(let x of bigint_list){
20+
arr1signed[0] = x;
21+
result.arr1_to_arr2_signed.push(arr2signed[0]);
22+
}
23+
24+
result.arr1_to_arr2_unsigned = [];
25+
for(let x of bigint_list){
26+
arr1unsigned[0] = x;
27+
result.arr1_to_arr2_unsigned.push(arr2unsigned[0]);
28+
}
29+
30+
result.arr2_to_arr1_signed = [];
31+
for(let x of bigint_list){
32+
arr2signed[0] = x;
33+
result.arr2_to_arr1_signed.push(arr1signed[0]);
34+
}
35+
36+
result.arr2_to_arr1_unsigned = [];
37+
for(let x of bigint_list){
38+
arr2unsigned[0] = x;
39+
result.arr2_to_arr1_unsigned.push(arr1unsigned[0]);
40+
}
41+
42+
43+
result.assertEquals = [];
44+
function assertEqual(cb){
45+
result.assertEquals.push([cb.toString(), cb()]);
46+
}
47+
48+
let arr1 = arr1unsigned;
49+
let arr2 = arr2unsigned;
50+
assertEqual(() => [arr2.BYTES_PER_ELEMENT, arr1.BYTES_PER_ELEMENT]);
51+
assertEqual(() => [arr2.byteLength, arr1.byteLength]);
52+
assertEqual(() => [arr2.length, arr1.length]);
53+
assertEqual(() => [arr2.slice().length, arr1.slice().length]);
54+
assertEqual(() => [arr2.slice(1, 5).length, arr1.slice(1, 5).length]);
55+
56+
arr1[0] = 1n; arr1[1] = 2n; arr1[2] = 3n; arr1[3] = 4n; arr1[4] = 5n;
57+
58+
result.arr2_slice = Array.from(arr2.slice(1, 5));
59+
result.arr2_subarray = Array.from(arr2.subarray(1, 5));
60+
let reducer = (k, v) => typeof v === 'bigint' ? v.toString() + 'n' : v;
61+
function arraytostring(arr){
62+
return JSON.stringify(Array.from(Array.from(arr)), reducer);
63+
}
64+
65+
let sub = arr2.subarray(1, 5);
66+
assertEqual(() => [sub.byteOffset, 8]);
67+
sub[0] = 7n; sub[1] = 77n; sub[2] = 3n; sub[3] = 66n;
68+
assertEqual(() => [arraytostring(arr1.slice(0, 5)), '["1n","7n","77n","3n","66n"]']);
69+
70+
arr1[2] = 62n;
71+
assertEqual(() => [sub[1], 62n]);
72+
73+
let slice = arr2.slice(1, 5);
74+
assertEqual(() => [slice.byteOffset, 0]);
75+
76+
slice[0] = 777n; slice[1] = 666n; slice[2] = 555n;
77+
assertEqual(() => [arraytostring(arr1.slice(0, 5)), '["1n","7n","62n","3n","66n"]']);
78+
79+
arr2.set([2n, 4n, 8n]);
80+
assertEqual(() => [arraytostring(arr1.slice(0, 3)), '["2n","4n","8n"]']);
81+
82+
arr2.set([1n, 3n, 7n], 6);
83+
assertEqual(() => [arraytostring(arr1.slice(6, 9)), '["1n","3n","7n"]']);
84+
85+
arr1[15] = 111n; arr1[18] = 171n; arr1[19] = 629n;
86+
87+
assertEqual(() => [arraytostring(arr2.slice(-1)), '["629n"]']);
88+
assertEqual(() => [arraytostring(arr2.slice(-5, -1)), '["111n","0n","0n","171n"]']);
89+
assertEqual(() => [arraytostring(arr2.slice(-5, -6)), '[]']);
90+
91+
arr3 = new BigUint64Array(Array.from({length:5}, (_, idx) => BigInt(idx)));
92+
assertEqual(() => [arraytostring(arr3), '["0n","1n","2n","3n","4n"]']);
93+
arr3.copyWithin(0, 2, 10);
94+
assertEqual(() => [arraytostring(arr3), '["2n","3n","4n","3n","4n"]']);
95+
96+
console.log(JSON.stringify(result, reducer));

tests/test_other.py

+50
Original file line numberDiff line numberDiff line change
@@ -12246,3 +12246,53 @@ def test_rust_gxx_personality_v0(self):
1224612246
}
1224712247
}
1224812248
''', assert_returncode=NON_ZERO)
12249+
12250+
def test_bigint64array_polyfill(self):
12251+
bigint64array = read_file(path_from_root('src/polyfill/bigint64array.js'))
12252+
test_code = read_file(test_file('test_bigint64array_polyfill.js'))
12253+
bigint_list = [
12254+
0,
12255+
1,
12256+
-1,
12257+
5,
12258+
(1 << 64),
12259+
(1 << 64) - 1,
12260+
(1 << 64) + 1,
12261+
(1 << 63),
12262+
(1 << 63) - 1,
12263+
(1 << 63) + 1,
12264+
]
12265+
bigint_list_strs = [str(x) for x in bigint_list]
12266+
12267+
bigint_list_unsigned = [x % (1 << 64) for x in bigint_list]
12268+
bigint_list_signed = [
12269+
x if x < 0 else (x % (1 << 64)) - 2 * (x & (1 << 63)) for x in bigint_list
12270+
]
12271+
bigint_list_unsigned_n = [f'{x}n' for x in bigint_list_unsigned]
12272+
bigint_list_signed_n = [f'{x}n' for x in bigint_list_signed]
12273+
12274+
bigint64array = '\n'.join(bigint64array.splitlines()[3:])
12275+
12276+
create_file(
12277+
'test.js',
12278+
f'''
12279+
let bigint_list = {bigint_list_strs}.map(x => BigInt(x));
12280+
let arr1signed = new BigInt64Array(20);
12281+
let arr1unsigned = new BigUint64Array(20);
12282+
delete globalThis.BigInt64Array;
12283+
''' + bigint64array + test_code
12284+
)
12285+
output = json.loads(self.run_js('test.js'))
12286+
self.assertEqual(output['BigInt64Array_name'], 'createBigInt64Array')
12287+
for key in ['arr1_to_arr1', 'arr1_to_arr2', 'arr2_to_arr1']:
12288+
print(key + '_unsigned')
12289+
self.assertEqual(output[key + '_unsigned'], bigint_list_unsigned_n)
12290+
for key in ['arr1_to_arr1', 'arr1_to_arr2', 'arr2_to_arr1']:
12291+
print(key + '_signed')
12292+
self.assertEqual(output[key + '_signed'], bigint_list_signed_n)
12293+
12294+
self.assertEqual(output['arr2_slice'], ['2n', '3n', '4n', '5n'])
12295+
self.assertEqual(output['arr2_subarray'], ['2n', '3n', '4n', '5n'])
12296+
12297+
for m, [v1, v2] in output['assertEquals']:
12298+
self.assertEqual(v1, v2, msg=m)

0 commit comments

Comments
 (0)