asterius
asterius copied to clipboard
Use a BigInt polyfill implementation for browsers lacking BigInt support
Is your feature request related to a problem? Please describe.
As illustrated in https://github.com/tweag/asterius/issues/401, Asterius currently fails on Safari, and in general browsers that do not support BigInt
s yet. To reproduce the problem, it suffices to create a simple test.hs
file with the following contents:
module Main where
main :: IO ()
main = putStr "Hello there!"
compile with --browser
ahc-link --input-hs test.hs --browser
and run the generated file in a browser without BigInt
support (e.g. safari
, or epiphany
).
Describe the solution you'd like
Instead of waiting for such browsers to add BigInt
support, it'd be nicer if Asterius could fallback to a BigInt
polyfill implementation such as jsbi, when support for BigInt
is not available.
Apart from the usual numeric operations on BigInt
s (e.g. <<
, &
, ...), Asterius' rts relies on DataView
methods getBigInt64
, setBigInt64
, getBigUint64
, setBigUint6
, as well as BigUint64Array
s, which are not provided by jsbi
. Hence, we can decompose the issue into smaller tasks:
- Add the
jsbi
sources to the runtime, and make sure that it is compatible with our webpack bundler (that is, test with--browser --bundle
). - Add a new top-level rts module
rts.bigint.mjs
which exports theBigInt
logic we use in the runtime.rts.bigint.mjs
should detect the environment first and only fall back tojsbi
if certain stuff is missing (e.g. by checking whetherDataView.prototype.getBigUint64
exists). - Replace all
BigInt
literals, operators, function calls, etc in the runtime with whatever's provided byrts.bigint.mjs
Relevant: https://github.com/GoogleChromeLabs/jsbi/issues/4 (and specifically https://gist.github.com/graup/815c9ac65c2bac8a56391f0ca23636fc).
at least safari now supports BigInts: https://caniuse.com/bigint
Since Safari supports BigInt
now, here are some polyfills for a few of the remaining missing bits, getBigUint64
/setBigUint64
, which might come in handy for people who stumble upon this.
No guarantees they're correct in all cases, but they at least work partially. Also no implementations for getBigInt64
/setBigInt64
; those might require some extra care to get right.
DataView.prototype.setBigUint64 ??= function(byteOffset, value, littleEndian) {
const wh = Number((value >> 32n) & 0xFFFFFFFFn);
const wl = Number((value ) & 0xFFFFFFFFn);
const [h, l] = littleEndian ? [4, 0] : [0, 4];
this.setUint32(byteOffset + h, wh, littleEndian);
this.setUint32(byteOffset + l, wl, littleEndian);
};
DataView.prototype.getBigUint64 ??= function(byteOffset, littleEndian) {
const [h, l] = littleEndian ? [4, 0] : [0, 4];
const wh = BigInt(this.getUint32(byteOffset + h, littleEndian));
const wl = BigInt(this.getUint32(byteOffset + l, littleEndian));
return (wh << 32n) + wl;
};