tweag/asterius

Use a BigInt polyfill implementation for browsers lacking BigInt support

gkaracha opened this issue · 3 comments

Is your feature request related to a problem? Please describe.

As illustrated in #401, Asterius currently fails on Safari, and in general browsers that do not support BigInts 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 BigInts (e.g. <<, &, ...), Asterius' rts relies on DataView methods getBigInt64, setBigInt64, getBigUint64, setBigUint6, as well as BigUint64Arrays, 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 the BigInt logic we use in the runtime. rts.bigint.mjs should detect the environment first and only fall back to jsbi if certain stuff is missing (e.g. by checking whether DataView.prototype.getBigUint64 exists).
  • Replace all BigInt literals, operators, function calls, etc in the runtime with whatever's provided by rts.bigint.mjs

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;
};