summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelogs/changelog_2_0_0.md10
-rw-r--r--compiler/commands.nim3
-rw-r--r--compiler/jsgen.nim209
-rw-r--r--compiler/options.nim4
-rw-r--r--doc/advopt.txt2
-rw-r--r--lib/js/jscore.nim11
-rw-r--r--lib/pure/json.nim2
-rw-r--r--lib/pure/random.nim19
-rw-r--r--lib/pure/strutils.nim10
-rw-r--r--lib/pure/times.nim2
-rw-r--r--lib/std/exitprocs.nim2
-rw-r--r--lib/std/jsbigints.nim4
-rw-r--r--lib/std/private/jsutils.nim17
-rw-r--r--lib/system/ctypes.nim12
-rw-r--r--lib/system/jssys.nim26
-rw-r--r--lib/system/reprjs.nim8
-rw-r--r--tests/js/ttypedarray.nim11
-rw-r--r--tests/lexer/tunary_minus.nim4
-rw-r--r--tests/misc/tints.nim20
-rw-r--r--tests/stdlib/thashes.nim2
-rw-r--r--tests/stdlib/tjson.nim7
-rw-r--r--tests/stdlib/tjsonmacro.nim6
-rw-r--r--tests/stdlib/trandom.nim19
-rw-r--r--tests/stdlib/tstrutils.nim18
-rw-r--r--tests/stdlib/ttimes.nim2
-rw-r--r--tests/system/tdollars.nim10
26 files changed, 329 insertions, 111 deletions
diff --git a/changelogs/changelog_2_0_0.md b/changelogs/changelog_2_0_0.md
index bef5f6073..02310eea3 100644
--- a/changelogs/changelog_2_0_0.md
+++ b/changelogs/changelog_2_0_0.md
@@ -175,6 +175,16 @@
 
 - - Added the `--legacy:verboseTypeMismatch` switch to get legacy type mismatch error messages.
 
+- The JavaScript backend now uses [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
+  for 64-bit integer types (`int64` and `uint64`) by default. As this affects
+  JS code generation, code using these types to interface with the JS backend
+  may need to be updated. Note that `int` and `uint` are not affected.
+  
+  For compatibility with [platforms that do not support BigInt](https://caniuse.com/bigint)
+  and in the case of potential bugs with the new implementation, the
+  old behavior is currently still supported with the command line option
+  `--jsbigint64:off`.
+
 - The `proc` and `iterator` type classes now respectively only match
   procs and iterators. Previously both type classes matched any of
   procs or iterators.
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 0c46a1bc0..4ed21d282 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -336,6 +336,7 @@ proc testCompileOption*(conf: ConfigRef; switch: string, info: TLineInfo): bool
   of "excessivestacktrace": result = contains(conf.globalOptions, optExcessiveStackTrace)
   of "nilseqs", "nilchecks", "taintmode": warningOptionNoop(switch)
   of "panics": result = contains(conf.globalOptions, optPanics)
+  of "jsbigint64": result = contains(conf.globalOptions, optJsBigInt64)
   else: invalidCmdLineOption(conf, passCmd1, switch, info)
 
 proc processPath(conf: ConfigRef; path: string, info: TLineInfo,
@@ -1078,6 +1079,8 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
     processOnOffSwitchG(conf, {optPanics}, arg, pass, info)
     if optPanics in conf.globalOptions:
       defineSymbol(conf.symbols, "nimPanics")
+  of "jsbigint64":
+    processOnOffSwitchG(conf, {optJsBigInt64}, arg, pass, info)
   of "sourcemap": # xxx document in --fullhelp
     conf.globalOptions.incl optSourcemap
     conf.options.incl optLineDir
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index a20492740..619555969 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -571,12 +571,20 @@ proc binaryUintExpr(p: PProc, n: PNode, r: var TCompRes, op: string,
   var x, y: TCompRes
   gen(p, n[1], x)
   gen(p, n[2], y)
-  let trimmer = unsignedTrimmer(n[1].typ.skipTypes(abstractRange).size)
+  let size = n[1].typ.skipTypes(abstractRange).size
   when reassign:
     let (a, tmp) = maybeMakeTempAssignable(p, n[1], x)
-    r.res = "$1 = (($5 $2 $3) $4)" % [a, rope op, y.rdLoc, trimmer, tmp]
+    if size == 8 and optJsBigInt64 in p.config.globalOptions:
+      r.res = "$1 = BigInt.asUintN(64, ($4 $2 $3))" % [a, rope op, y.rdLoc, tmp]
+    else:
+      let trimmer = unsignedTrimmer(size)
+      r.res = "$1 = (($5 $2 $3) $4)" % [a, rope op, y.rdLoc, trimmer, tmp]
   else:
-    r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer]
+    if size == 8 and optJsBigInt64 in p.config.globalOptions:
+      r.res = "BigInt.asUintN(64, ($1 $2 $3))" % [x.rdLoc, rope op, y.rdLoc]
+    else:
+      let trimmer = unsignedTrimmer(size)
+      r.res = "(($1 $2 $3) $4)" % [x.rdLoc, rope op, y.rdLoc, trimmer]
   r.kind = resExpr
 
 template ternaryExpr(p: PProc, n: PNode, r: var TCompRes, magic, frmt: string) =
@@ -618,11 +626,45 @@ proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
     if i == 0: applyFormat(frmtA) else: applyFormat(frmtB)
 
   case op
-  of mAddI: applyFormat("addInt($1, $2)", "($1 + $2)")
-  of mSubI: applyFormat("subInt($1, $2)", "($1 - $2)")
-  of mMulI: applyFormat("mulInt($1, $2)", "($1 * $2)")
-  of mDivI: applyFormat("divInt($1, $2)", "Math.trunc($1 / $2)")
-  of mModI: applyFormat("modInt($1, $2)", "Math.trunc($1 % $2)")
+  of mAddI:
+    if i == 0:
+      if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions:
+        useMagic(p, "addInt64")
+        applyFormat("addInt64($1, $2)")
+      else:
+        applyFormat("addInt($1, $2)")
+    else:
+      applyFormat("($1 + $2)")
+  of mSubI:
+    if i == 0:
+      if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions:
+        useMagic(p, "subInt64")
+        applyFormat("subInt64($1, $2)")
+      else:
+        applyFormat("subInt($1, $2)")
+    else:
+      applyFormat("($1 - $2)")
+  of mMulI:
+    if i == 0:
+      if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions:
+        useMagic(p, "mulInt64")
+        applyFormat("mulInt64($1, $2)")
+      else:
+        applyFormat("mulInt($1, $2)")
+    else:
+      applyFormat("($1 * $2)")
+  of mDivI:
+    if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions:
+      useMagic(p, "divInt64")
+      applyFormat("divInt64($1, $2)", "$1 / $2")
+    else:
+      applyFormat("divInt($1, $2)", "Math.trunc($1 / $2)")
+  of mModI:
+    if n[1].typ.size == 8 and optJsBigInt64 in p.config.globalOptions:
+      useMagic(p, "modInt64")
+      applyFormat("modInt64($1, $2)", "$1 % $2")
+    else:
+      applyFormat("modInt($1, $2)", "Math.trunc($1 % $2)")
   of mSucc: applyFormat("addInt($1, $2)", "($1 + $2)")
   of mPred: applyFormat("subInt($1, $2)", "($1 - $2)")
   of mAddF64: applyFormat("($1 + $2)", "($1 + $2)")
@@ -631,15 +673,27 @@ proc arithAux(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
   of mDivF64: applyFormat("($1 / $2)", "($1 / $2)")
   of mShrI: applyFormat("", "")
   of mShlI:
-    if n[1].typ.size <= 4:
-      applyFormat("($1 << $2)", "($1 << $2)")
+    let typ = n[1].typ.skipTypes(abstractVarRange)
+    if typ.size == 8:
+      if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
+        applyFormat("BigInt.asIntN(64, $1 << BigInt($2))")
+      elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
+        applyFormat("BigInt.asUintN(64, $1 << BigInt($2))")
+      else:
+        applyFormat("($1 * Math.pow(2, $2))")
     else:
-      applyFormat("($1 * Math.pow(2, $2))", "($1 * Math.pow(2, $2))")
+      applyFormat("($1 << $2)", "($1 << $2)")
   of mAshrI:
-    if n[1].typ.size <= 4:
-      applyFormat("($1 >> $2)", "($1 >> $2)")
+    let typ = n[1].typ.skipTypes(abstractVarRange)
+    if typ.size == 8:
+      if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
+        applyFormat("BigInt.asIntN(64, $1 >> BigInt($2))")
+      elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
+        applyFormat("BigInt.asUintN(64, $1 >> BigInt($2))")
+      else:
+        applyFormat("Math.floor($1 / Math.pow(2, $2))")
     else:
-      applyFormat("Math.floor($1 / Math.pow(2, $2))", "Math.floor($1 / Math.pow(2, $2))")
+      applyFormat("($1 >> $2)", "($1 >> $2)")
   of mBitandI: applyFormat("($1 & $2)", "($1 & $2)")
   of mBitorI: applyFormat("($1 | $2)", "($1 | $2)")
   of mBitxorI: applyFormat("($1 ^ $2)", "($1 ^ $2)")
@@ -697,7 +751,9 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
   of mMulU: binaryUintExpr(p, n, r, "*")
   of mDivU:
     binaryUintExpr(p, n, r, "/")
-    if n[1].typ.skipTypes(abstractRange).size == 8:
+    if optJsBigInt64 notin p.config.globalOptions and
+        n[1].typ.skipTypes(abstractRange).size == 8:
+      # bigint / already truncates
       r.res = "Math.trunc($1)" % [r.res]
   of mDivI:
     arithAux(p, n, r, op)
@@ -707,7 +763,13 @@ proc arith(p: PProc, n: PNode, r: var TCompRes, op: TMagic) =
     var x, y: TCompRes
     gen(p, n[1], x)
     gen(p, n[2], y)
-    r.res = "($1 >>> $2)" % [x.rdLoc, y.rdLoc]
+    let typ = n[1].typ.skipTypes(abstractVarRange)
+    if typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
+      r.res = "BigInt.asIntN(64, BigInt.asUintN(64, $1) >> BigInt($2))" % [x.rdLoc, y.rdLoc]
+    elif typ.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
+      r.res = "($1 >> BigInt($2))" % [x.rdLoc, y.rdLoc]
+    else:
+      r.res = "($1 >>> $2)" % [x.rdLoc, y.rdLoc]
   of mCharToStr, mBoolToStr, mIntToStr, mInt64ToStr, mCStrToStr, mStrToStr, mEnumToStr:
     arithAux(p, n, r, op)
   of mEqRef:
@@ -1764,15 +1826,25 @@ proc createObjInitList(p: PProc, typ: PType, excludedFieldIDs: IntSet, output: v
     createRecordVarAux(p, t.n, excludedFieldIDs, output)
     t = t[0]
 
-proc arrayTypeForElemType(typ: PType): string =
+proc arrayTypeForElemType(conf: ConfigRef; typ: PType): string =
   let typ = typ.skipTypes(abstractRange)
   case typ.kind
   of tyInt, tyInt32: "Int32Array"
   of tyInt16: "Int16Array"
   of tyInt8: "Int8Array"
+  of tyInt64:
+    if optJsBigInt64 in conf.globalOptions:
+      "BigInt64Array"
+    else:
+      ""
   of tyUInt, tyUInt32: "Uint32Array"
   of tyUInt16: "Uint16Array"
   of tyUInt8, tyChar, tyBool: "Uint8Array"
+  of tyUInt64:
+    if optJsBigInt64 in conf.globalOptions:
+      "BigUint64Array"
+    else:
+      ""
   of tyFloat32: "Float32Array"
   of tyFloat64, tyFloat: "Float64Array"
   of tyEnum:
@@ -1786,11 +1858,18 @@ proc arrayTypeForElemType(typ: PType): string =
 proc createVar(p: PProc, typ: PType, indirect: bool): Rope =
   var t = skipTypes(typ, abstractInst)
   case t.kind
-  of tyInt..tyInt64, tyUInt..tyUInt64, tyEnum, tyChar:
+  of tyInt8..tyInt32, tyUInt8..tyUInt32, tyEnum, tyChar:
+    result = putToSeq("0", indirect)
+  of tyInt, tyUInt:
     if $t.sym.loc.r == "bigint":
       result = putToSeq("0n", indirect)
     else:
       result = putToSeq("0", indirect)
+  of tyInt64, tyUInt64:
+    if optJsBigInt64 in p.config.globalOptions:
+      result = putToSeq("0n", indirect)
+    else:
+      result = putToSeq("0", indirect)
   of tyFloat..tyFloat128:
     result = putToSeq("0.0", indirect)
   of tyRange, tyGenericInst, tyAlias, tySink, tyOwned, tyLent:
@@ -1804,7 +1883,7 @@ proc createVar(p: PProc, typ: PType, indirect: bool): Rope =
   of tyArray:
     let length = toInt(lengthOrd(p.config, t))
     let e = elemType(t)
-    let jsTyp = arrayTypeForElemType(e)
+    let jsTyp = arrayTypeForElemType(p.config, e)
     if jsTyp.len > 0:
       result = "new $1($2)" % [rope(jsTyp), rope(length)]
     elif length > 32:
@@ -1979,7 +2058,11 @@ proc genNewSeq(p: PProc, n: PNode) =
 
 proc genOrd(p: PProc, n: PNode, r: var TCompRes) =
   case skipTypes(n[1].typ, abstractVar + abstractRange).kind
-  of tyEnum, tyInt..tyUInt64, tyChar: gen(p, n[1], r)
+  of tyEnum, tyInt..tyInt32, tyUInt..tyUInt32, tyChar: gen(p, n[1], r)
+  of tyInt64, tyUInt64:
+    if optJsBigInt64 in p.config.globalOptions:
+      unaryExpr(p, n, r, "", "Number($1)")
+    else: gen(p, n[1], r)
   of tyBool: unaryExpr(p, n, r, "", "($1 ? 1 : 0)")
   else: internalError(p.config, n.info, "genOrd")
 
@@ -2202,14 +2285,34 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
       r.res = "($1).length - 1" % [x.rdLoc]
     r.kind = resExpr
   of mInc:
-    if n[1].typ.skipTypes(abstractRange).kind in {tyUInt..tyUInt64}:
+    let typ = n[1].typ.skipTypes(abstractVarRange)
+    case typ.kind
+    of tyUInt..tyUInt32:
       binaryUintExpr(p, n, r, "+", true)
+    of tyUInt64:
+      if optJsBigInt64 in p.config.globalOptions:
+        binaryExpr(p, n, r, "", "$1 = BigInt.asUintN(64, $3 + BigInt($2))", true)
+      else: binaryUintExpr(p, n, r, "+", true)
+    elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
+      if optOverflowCheck notin p.options:
+        binaryExpr(p, n, r, "", "$1 = BigInt.asIntN(64, $3 + BigInt($2))", true)
+      else: binaryExpr(p, n, r, "addInt64", "$1 = addInt64($3, BigInt($2))", true)
     else:
       if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 += $2")
       else: binaryExpr(p, n, r, "addInt", "$1 = addInt($3, $2)", true)
   of ast.mDec:
-    if n[1].typ.skipTypes(abstractRange).kind in {tyUInt..tyUInt64}:
+    let typ = n[1].typ.skipTypes(abstractVarRange)
+    case typ.kind
+    of tyUInt..tyUInt32:
       binaryUintExpr(p, n, r, "-", true)
+    of tyUInt64:
+      if optJsBigInt64 in p.config.globalOptions:
+        binaryExpr(p, n, r, "", "$1 = BigInt.asUintN(64, $3 - BigInt($2))", true)
+      else: binaryUintExpr(p, n, r, "+", true)
+    elif typ.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
+      if optOverflowCheck notin p.options:
+        binaryExpr(p, n, r, "", "$1 = BigInt.asIntN(64, $3 - BigInt($2))", true)
+      else: binaryExpr(p, n, r, "subInt64", "$1 = subInt64($3, BigInt($2))", true)
     else:
       if optOverflowCheck notin p.options: binaryExpr(p, n, r, "", "$1 -= $2")
       else: binaryExpr(p, n, r, "subInt", "$1 = subInt($3, $2)", true)
@@ -2303,7 +2406,7 @@ proc genArrayConstr(p: PProc, n: PNode, r: var TCompRes) =
   ## Nim sequence maps to JS array.
   var t = skipTypes(n.typ, abstractInst)
   let e = elemType(t)
-  let jsTyp = arrayTypeForElemType(e)
+  let jsTyp = arrayTypeForElemType(p.config, e)
   if skipTypes(n.typ, abstractVarRange).kind != tySequence and jsTyp.len > 0:
     # generate typed array
     # for example Nim generates `new Uint8Array([1, 2, 3])` for `[byte(1), 2, 3]`
@@ -2384,7 +2487,27 @@ proc genConv(p: PProc, n: PNode, r: var TCompRes) =
     r.res = "(!!($1))" % [r.res]
     r.kind = resExpr
   elif toInt:
-    r.res = "(($1) | 0)" % [r.res]
+    if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
+      r.res = "Number($1)" % [r.res]
+    else:
+      r.res = "(($1) | 0)" % [r.res]
+  elif dest.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
+    if fromInt or fromUint or src.kind in {tyBool, tyChar, tyEnum}:
+      r.res = "BigInt($1)" % [r.res]
+    elif src.kind in {tyFloat..tyFloat64}:
+      r.res = "BigInt(Math.trunc($1))" % [r.res]
+    elif src.kind == tyUInt64:
+      r.res = "BigInt.asIntN(64, $1)" % [r.res]
+  elif dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
+    if fromInt or fromUint:
+      r.res = "BigInt($1)" % [r.res]
+    elif src.kind in {tyFloat..tyFloat64}:
+      r.res = "BigInt(Math.trunc($1))" % [r.res]
+    elif src.kind == tyInt64:
+      r.res = "BigInt.asUintN(64, $1)" % [r.res]
+  elif toUint or dest.kind in tyFloat..tyFloat64:
+    if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
+      r.res = "Number($1)" % [r.res]
   else:
     # TODO: What types must we handle here?
     discard
@@ -2395,7 +2518,11 @@ proc upConv(p: PProc, n: PNode, r: var TCompRes) =
 proc genRangeChck(p: PProc, n: PNode, r: var TCompRes, magic: string) =
   var a, b: TCompRes
   gen(p, n[0], r)
-  if optRangeCheck notin p.options or (skipTypes(n.typ, abstractVar).kind in {tyUInt..tyUInt64} and
+  let src = skipTypes(n[0].typ, abstractVarRange)
+  let dest = skipTypes(n.typ, abstractVarRange)
+  if src.kind in {tyInt64, tyUInt64} and dest.kind notin {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
+    r.res = "Number($1)" % [r.res]
+  if optRangeCheck notin p.options or (dest.kind in {tyUInt..tyUInt64} and
       checkUnsignedConversions notin p.config.legacyFeatures):
     discard "XXX maybe emit masking instructions here"
   else:
@@ -2587,6 +2714,8 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) =
   if toUint and (fromInt or fromUint):
     let trimmer = unsignedTrimmer(dest.size)
     r.res = "($1 $2)" % [r.res, trimmer]
+  elif toUint and src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
+    r.res = "Number(BigInt.asUintN($1, $2))" % [$(dest.size * 8), r.res]
   elif toInt:
     if fromInt:
       return
@@ -2602,6 +2731,25 @@ proc genCast(p: PProc, n: PNode, r: var TCompRes) =
           of 4: "0xfffffffe"
           else: ""
         r.res = "($1 - ($2 $3))" % [rope minuend, r.res, trimmer]
+    elif src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
+      r.res = "Number(BigInt.asIntN($1, $2))" % [$(dest.size * 8), r.res]
+  elif dest.kind == tyInt64 and optJsBigInt64 in p.config.globalOptions:
+    if fromInt or fromUint or src.kind in {tyBool, tyChar, tyEnum}:
+      r.res = "BigInt($1)" % [r.res]
+    elif src.kind in {tyFloat..tyFloat64}:
+      r.res = "BigInt(Math.trunc($1))" % [r.res]
+    elif src.kind == tyUInt64:
+      r.res = "BigInt.asIntN(64, $1)" % [r.res]
+  elif dest.kind == tyUInt64 and optJsBigInt64 in p.config.globalOptions:
+    if fromInt or fromUint:
+      r.res = "BigInt($1)" % [r.res]
+    elif src.kind in {tyFloat..tyFloat64}:
+      r.res = "BigInt(Math.trunc($1))" % [r.res]
+    elif src.kind == tyInt64:
+      r.res = "BigInt.asUintN(64, $1)" % [r.res]
+  elif dest.kind in tyFloat..tyFloat64:
+    if src.kind in {tyInt64, tyUInt64} and optJsBigInt64 in p.config.globalOptions:
+      r.res = "Number($1)" % [r.res]
   elif (src.kind == tyPtr and mapType(p, src) == etyObject) and dest.kind == tyPointer:
     r.address = r.res
     r.res = "null"
@@ -2620,8 +2768,17 @@ proc gen(p: PProc, n: PNode, r: var TCompRes) =
   of nkSym:
     genSym(p, n, r)
   of nkCharLit..nkUInt64Lit:
-    if n.typ.kind == tyBool:
+    case n.typ.skipTypes(abstractVarRange).kind
+    of tyBool:
       r.res = if n.intVal == 0: rope"false" else: rope"true"
+    of tyUInt64:
+      r.res = rope($cast[BiggestUInt](n.intVal))
+      if optJsBigInt64 in p.config.globalOptions:
+        r.res.add('n')
+    of tyInt64:
+      r.res = rope(n.intVal)
+      if optJsBigInt64 in p.config.globalOptions:
+        r.res.add('n')
     else:
       r.res = rope(n.intVal)
     r.kind = resExpr
diff --git a/compiler/options.nim b/compiler/options.nim
index 6df9723f2..da9c9cbbb 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -106,6 +106,7 @@ type                          # please make sure we have under 32 options
     optProfileVM              # enable VM profiler
     optEnableDeepCopy         # ORC specific: enable 'deepcopy' for all types.
     optShowNonExportedFields  # for documentation: show fields that are not exported
+    optJsBigInt64             # use bigints for 64-bit integers in JS
 
   TGlobalOptions* = set[TGlobalOption]
 
@@ -476,7 +477,8 @@ const
     optBoundsCheck, optOverflowCheck, optAssert, optWarns, optRefCheck,
     optHints, optStackTrace, optLineTrace, # consider adding `optStackTraceMsgs`
     optTrMacros, optStyleCheck, optCursorInference}
-  DefaultGlobalOptions* = {optThreadAnalysis, optExcessiveStackTrace}
+  DefaultGlobalOptions* = {optThreadAnalysis, optExcessiveStackTrace,
+    optJsBigInt64}
 
 proc getSrcTimestamp(): DateTime =
   try:
diff --git a/doc/advopt.txt b/doc/advopt.txt
index 03649ffdd..9a3912e35 100644
--- a/doc/advopt.txt
+++ b/doc/advopt.txt
@@ -174,3 +174,5 @@ Advanced options:
   --sinkInference:on|off    turn sink parameter inference on|off (default: off)
   --panics:on|off           turn panics into process terminations (default: off)
   --deepcopy:on|off         enable 'system.deepCopy' for ``--mm:arc|orc``
+  --jsbigint64:on|off       toggle the use of [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
+                            for 64-bit integers on the JavaScript backend (default: on)
diff --git a/lib/js/jscore.nim b/lib/js/jscore.nim
index 781e8fd57..5147b550d 100644
--- a/lib/js/jscore.nim
+++ b/lib/js/jscore.nim
@@ -13,7 +13,7 @@
 ## specific requirements and solely targets JavaScript, you should be using
 ## the relevant functions in the `math`, `json`, and `times` stdlib
 ## modules instead.
-import std/private/since
+import std/private/[since, jsutils]
 
 when not defined(js):
   {.error: "This module only works on the JavaScript platform".}
@@ -74,9 +74,16 @@ proc parse*(d: DateLib, s: cstring): int {.importcpp.}
 proc newDate*(): DateTime {.
   importcpp: "new Date()".}
 
-proc newDate*(date: int|int64|string): DateTime {.
+proc newDate*(date: int|string): DateTime {.
   importcpp: "new Date(#)".}
 
+whenJsNoBigInt64:
+  proc newDate*(date: int64): DateTime {.
+    importcpp: "new Date(#)".}
+do:
+  proc newDate*(date: int64): DateTime {.
+    importcpp: "new Date(Number(#))".}
+
 proc newDate*(year, month, day, hours, minutes,
              seconds, milliseconds: int): DateTime {.
   importcpp: "new Date(#,#,#,#,#,#,#)".}
diff --git a/lib/pure/json.nim b/lib/pure/json.nim
index d91da3545..45b22cea5 100644
--- a/lib/pure/json.nim
+++ b/lib/pure/json.nim
@@ -1110,7 +1110,7 @@ proc initFromJson(dst: var JsonNode; jsonNode: JsonNode; jsonPath: var string) =
   dst = jsonNode.copy
 
 proc initFromJson[T: SomeInteger](dst: var T; jsonNode: JsonNode, jsonPath: var string) =
-  when T is uint|uint64 or (not defined(js) and int.sizeof == 4):
+  when T is uint|uint64 or int.sizeof == 4:
     verifyJsonKind(jsonNode, {JInt, JString}, jsonPath)
     case jsonNode.kind
     of JString:
diff --git a/lib/pure/random.nim b/lib/pure/random.nim
index c36ab445b..422f42a8b 100644
--- a/lib/pure/random.nim
+++ b/lib/pure/random.nim
@@ -72,7 +72,7 @@ runnableExamples:
 ##   in the standard library
 
 import algorithm, math
-import std/private/since
+import std/private/[since, jsutils]
 
 when defined(nimPreviewSlimSystem):
   import std/[assertions]
@@ -231,11 +231,14 @@ proc rand[T: uint | uint64](r: var Rand; max: T): T =
     let max = uint64(max)
     when T.high.uint64 == uint64.high:
       if max == uint64.high: return T(next(r))
+    var iters = 0
     while true:
       let x = next(r)
       # avoid `mod` bias
-      if x <= randMax - (randMax mod max):
+      if x <= randMax - (randMax mod max) or iters > 20:
         return T(x mod (max + 1))
+      else:
+        inc iters
 
 proc rand*(r: var Rand; max: Natural): int {.benign.} =
   ## Returns a random integer in the range `0..max` using the given state.
@@ -337,9 +340,9 @@ proc rand*[T: Ordinal or SomeFloat](r: var Rand; x: HSlice[T, T]): T =
   when T is SomeFloat:
     result = rand(r, x.b - x.a) + x.a
   else: # Integers and Enum types
-    when defined(js):
+    whenJsNoBigInt64:
       result = cast[T](rand(r, cast[uint](x.b) - cast[uint](x.a)) + cast[uint](x.a))
-    else:
+    do:
       result = cast[T](rand(r, cast[uint64](x.b) - cast[uint64](x.a)) + cast[uint64](x.a))
 
 proc rand*[T: Ordinal or SomeFloat](x: HSlice[T, T]): T =
@@ -378,14 +381,14 @@ proc rand*[T: Ordinal](r: var Rand; t: typedesc[T]): T {.since: (1, 7, 1).} =
   when T is range or T is enum:
     result = rand(r, low(T)..high(T))
   elif T is bool:
-    when defined(js):
+    whenJsNoBigInt64:
       result = (r.next or 0) < 0
-    else:
+    do:
       result = cast[int64](r.next) < 0
   else:
-    when defined(js):
+    whenJsNoBigInt64:
       result = cast[T](r.next shr (sizeof(uint)*8 - sizeof(T)*8))
-    else:
+    do:
       result = cast[T](r.next shr (sizeof(uint64)*8 - sizeof(T)*8))
 
 proc rand*[T: Ordinal](t: typedesc[T]): T =
diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim
index c458d605e..0a77e8bf6 100644
--- a/lib/pure/strutils.nim
+++ b/lib/pure/strutils.nim
@@ -79,7 +79,7 @@ from unicode import toLower, toUpper
 export toLower, toUpper
 
 include "system/inclrtl"
-import std/private/since
+import std/private/[since, jsutils]
 from std/private/strimpl import cmpIgnoreStyleImpl, cmpIgnoreCaseImpl,
     startsWithImpl, endsWithImpl
 
@@ -944,9 +944,9 @@ func toHex*[T: SomeInteger](x: T, len: Positive): string =
     doAssert b.toHex(4) == "1001"
     doAssert toHex(62, 3) == "03E"
     doAssert toHex(-8, 6) == "FFFFF8"
-  when defined(js):
+  whenJsNoBigInt64:
     toHexImpl(cast[BiggestUInt](x), len, x < 0)
-  else:
+  do:
     when T is SomeSignedInt:
       toHexImpl(cast[BiggestUInt](BiggestInt(x)), len, x < 0)
     else:
@@ -957,9 +957,9 @@ func toHex*[T: SomeInteger](x: T): string =
   runnableExamples:
     doAssert toHex(1984'i64) == "00000000000007C0"
     doAssert toHex(1984'i16) == "07C0"
-  when defined(js):
+  whenJsNoBigInt64:
     toHexImpl(cast[BiggestUInt](x), 2*sizeof(T), x < 0)
-  else:
+  do:
     when T is SomeSignedInt:
       toHexImpl(cast[BiggestUInt](BiggestInt(x)), 2*sizeof(T), x < 0)
     else:
diff --git a/lib/pure/times.nim b/lib/pure/times.nim
index 138f2d9ec..3d644d361 100644
--- a/lib/pure/times.nim
+++ b/lib/pure/times.nim
@@ -537,7 +537,7 @@ proc getDayOfWeek*(monthday: MonthdayRange, month: Month, year: int): WeekDay
   assertValidDate monthday, month, year
   # 1970-01-01 is a Thursday, we adjust to the previous Monday
   let days = toEpochDay(monthday, month, year) - 3
-  let weeks = floorDiv(days, 7)
+  let weeks = floorDiv(days, 7'i64)
   let wd = days - weeks * 7
   # The value of d is 0 for a Sunday, 1 for a Monday, 2 for a Tuesday, etc.
   # so we must correct for the WeekDay type.
diff --git a/lib/std/exitprocs.nim b/lib/std/exitprocs.nim
index 48b4fca7f..36f22a5d1 100644
--- a/lib/std/exitprocs.nim
+++ b/lib/std/exitprocs.nim
@@ -10,6 +10,8 @@
 ## This module allows adding hooks to program exit.
 
 import locks
+when defined(js) and not defined(nodejs):
+  import std/assertions
 
 type
   FunKind = enum kClosure, kNoconv # extend as needed
diff --git a/lib/std/jsbigints.nim b/lib/std/jsbigints.nim
index 04578fc87..fda299e7b 100644
--- a/lib/std/jsbigints.nim
+++ b/lib/std/jsbigints.nim
@@ -64,10 +64,10 @@ func wrapToUint*(this: JsBigInt; bits: Natural): JsBigInt {.importjs:
   runnableExamples:
     doAssert (big("3") + big("2") ** big("66")).wrapToUint(66) == big("3")
 
-func toNumber*(this: JsBigInt): BiggestInt {.importjs: "Number(#)".} =
+func toNumber*(this: JsBigInt): int {.importjs: "Number(#)".} =
   ## Does not do any bounds check and may or may not return an inexact representation.
   runnableExamples:
-    doAssert toNumber(big"2147483647") == 2147483647.BiggestInt
+    doAssert toNumber(big"2147483647") == 2147483647.int
 
 func `+`*(x, y: JsBigInt): JsBigInt {.importjs: "(# $1 #)".} =
   runnableExamples:
diff --git a/lib/std/private/jsutils.nim b/lib/std/private/jsutils.nim
index 836b3512a..fd1f395f3 100644
--- a/lib/std/private/jsutils.nim
+++ b/lib/std/private/jsutils.nim
@@ -79,5 +79,18 @@ when defined(js):
       assert not "123".toJs.isSafeInteger
       assert 123.isSafeInteger
       assert 123.toJs.isSafeInteger
-      assert 9007199254740991.toJs.isSafeInteger
-      assert not 9007199254740992.toJs.isSafeInteger
+      when false:
+        assert 9007199254740991.toJs.isSafeInteger
+        assert not 9007199254740992.toJs.isSafeInteger
+
+template whenJsNoBigInt64*(no64, yes64): untyped =
+  when defined(js):
+    when compiles(compileOption("jsbigint64")):
+      when compileOption("jsbigint64"):
+        yes64
+      else:
+        no64
+    else:
+      no64
+  else:
+    no64
diff --git a/lib/system/ctypes.nim b/lib/system/ctypes.nim
index f6a341477..b788274bd 100644
--- a/lib/system/ctypes.nim
+++ b/lib/system/ctypes.nim
@@ -12,16 +12,10 @@ type
     ## compiler supports. Currently this is `float64`, but it is
     ## platform-dependent in general.
 
-when defined(js):
-  type BiggestUInt* = uint32
+  BiggestUInt* = uint64
     ## is an alias for the biggest unsigned integer type the Nim compiler
-    ## supports. Currently this is `uint32` for JS and `uint64` for other
-    ## targets.
-else:
-  type BiggestUInt* = uint64
-    ## is an alias for the biggest unsigned integer type the Nim compiler
-    ## supports. Currently this is `uint32` for JS and `uint64` for other
-    ## targets.
+    ## supports. Currently this is `uint64`, but it is platform-dependent
+    ## in general.
 
 when defined(windows):
   type
diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim
index 4f64403fe..3bf506e1e 100644
--- a/lib/system/jssys.nim
+++ b/lib/system/jssys.nim
@@ -451,44 +451,44 @@ proc modInt(a, b: int): int {.asmNoStackFrame, compilerproc.} =
     return Math.trunc(`a` % `b`);
   """
 
-proc checkOverflowInt64(a: int) {.asmNoStackFrame, compilerproc.} =
+proc checkOverflowInt64(a: int64) {.asmNoStackFrame, compilerproc.} =
   asm """
-    if (`a` > 9223372036854775807 || `a` < -9223372036854775808) `raiseOverflow`();
+    if (`a` > 9223372036854775807n || `a` < -9223372036854775808n) `raiseOverflow`();
   """
 
-proc addInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+proc addInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
   asm """
     var result = `a` + `b`;
     `checkOverflowInt64`(result);
     return result;
   """
 
-proc subInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+proc subInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
   asm """
     var result = `a` - `b`;
     `checkOverflowInt64`(result);
     return result;
   """
 
-proc mulInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+proc mulInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
   asm """
     var result = `a` * `b`;
     `checkOverflowInt64`(result);
     return result;
   """
 
-proc divInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+proc divInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
   asm """
-    if (`b` == 0) `raiseDivByZero`();
-    if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`();
-    return Math.trunc(`a` / `b`);
+    if (`b` == 0n) `raiseDivByZero`();
+    if (`b` == -1n && `a` == 9223372036854775807n) `raiseOverflow`();
+    return `a` / `b`;
   """
 
-proc modInt64(a, b: int): int {.asmNoStackFrame, compilerproc.} =
+proc modInt64(a, b: int64): int64 {.asmNoStackFrame, compilerproc.} =
   asm """
-    if (`b` == 0) `raiseDivByZero`();
-    if (`b` == -1 && `a` == 9223372036854775807) `raiseOverflow`();
-    return Math.trunc(`a` % `b`);
+    if (`b` == 0n) `raiseDivByZero`();
+    if (`b` == -1n && `a` == 9223372036854775807n) `raiseOverflow`();
+    return `a` % `b`;
   """
 
 proc negInt(a: int): int {.compilerproc.} =
diff --git a/lib/system/reprjs.nim b/lib/system/reprjs.nim
index 0818f9cc9..30e84ebee 100644
--- a/lib/system/reprjs.nim
+++ b/lib/system/reprjs.nim
@@ -12,6 +12,8 @@ when defined(nimPreviewSlimSystem):
   import std/formatfloat
 
 proc reprInt(x: int64): string {.compilerproc.} = $x
+proc reprInt(x: uint64): string {.compilerproc.} = $x
+proc reprInt(x: int): string {.compilerproc.} = $x
 proc reprFloat(x: float): string {.compilerproc.} = $x
 
 proc reprPointer(p: pointer): string {.compilerproc.} =
@@ -192,8 +194,12 @@ proc reprAux(result: var string, p: pointer, typ: PNimType,
     return
   dec(cl.recDepth)
   case typ.kind
-  of tyInt..tyInt64, tyUInt..tyUInt64:
+  of tyInt..tyInt32, tyUInt..tyUInt32:
     add(result, reprInt(cast[int](p)))
+  of tyInt64:
+    add(result, reprInt(cast[int64](p)))
+  of tyUInt64:
+    add(result, reprInt(cast[uint64](p)))
   of tyChar:
     add(result, reprChar(cast[char](p)))
   of tyBool:
diff --git a/tests/js/ttypedarray.nim b/tests/js/ttypedarray.nim
index 222f66569..08b5fcdde 100644
--- a/tests/js/ttypedarray.nim
+++ b/tests/js/ttypedarray.nim
@@ -1,3 +1,7 @@
+discard """
+  matrix: "--jsbigint64:off; --jsbigint64:on"
+"""
+
 import std/private/jsutils
 
 proc main()=
@@ -5,9 +9,10 @@ proc main()=
   doAssert fn(array[2, int8].default) == "Int8Array"
   doAssert fn(array[2, uint8].default) == "Uint8Array"
   doAssert fn(array[2, byte].default) == "Uint8Array"
-  # doAssert fn(array[2, char].default) == "Uint8Array" # xxx fails; bug?
-  doAssert fn(array[2, uint64].default) == "Array"
-    # pending https://github.com/nim-lang/RFCs/issues/187 maybe use `BigUint64Array`
+  doAssert fn(array[2, char].default) == "Uint8Array"
+  whenJsNoBigInt64: discard
+  do:
+    doAssert fn(array[2, uint64].default) == "BigUint64Array"
   doAssert fn([1'u8]) == "Uint8Array"
   doAssert fn([1'u16]) == "Uint16Array"
   doAssert fn([byte(1)]) == "Uint8Array"
diff --git a/tests/lexer/tunary_minus.nim b/tests/lexer/tunary_minus.nim
index 1641e918c..5ec2b5c70 100644
--- a/tests/lexer/tunary_minus.nim
+++ b/tests/lexer/tunary_minus.nim
@@ -5,6 +5,7 @@ discard """
 # Test numeric literals and handling of minus symbol
 
 import std/[macros, strutils]
+import std/private/jsutils
 
 import mlexerutils
 
@@ -60,7 +61,8 @@ template main =
     doAssert -2147483648'i32 == int32.low
     when int.sizeof > 4:
       doAssert -9223372036854775808 == int.low
-    when not defined(js):
+    whenJsNoBigInt64: discard
+    do:
       doAssert -9223372036854775808 == int64.low
 
   block: # check when a minus (-) is an unary op
diff --git a/tests/misc/tints.nim b/tests/misc/tints.nim
index d24cbd4ac..cb77d4d89 100644
--- a/tests/misc/tints.nim
+++ b/tests/misc/tints.nim
@@ -1,4 +1,5 @@
 discard """
+  matrix: "; --backend:js --jsbigint64:off; --backend:js --jsbigint64:on"
   output: '''
 0 0
 0 0
@@ -6,6 +7,8 @@ Success'''
 """
 # Test the different integer operations
 
+import std/private/jsutils
+
 var testNumber = 0
 
 template test(opr, a, b, c: untyped): untyped =
@@ -23,28 +26,37 @@ template test(opr, a, b, c: untyped): untyped =
 
 test(`+`, 12'i8, -13'i16, -1'i16)
 test(`shl`, 0b11, 0b100, 0b110000)
+whenJsNoBigInt64: discard
+do:
+  test(`shl`, 0b11'i64, 0b100'i64, 0b110000'i64)
 when not defined(js):
+  # mixed type shr needlessly complicates codegen with bigint
+  # and thus is not yet supported in JS for 64 bit ints
   test(`shl`, 0b11'i32, 0b100'i64, 0b110000'i64)
 test(`shl`, 0b11'i32, 0b100'i32, 0b110000'i32)
 
 test(`or`, 0xf0f0'i16, 0x0d0d'i16, 0xfdfd'i16)
 test(`and`, 0xf0f0'i16, 0xfdfd'i16, 0xf0f0'i16)
 
-when not defined(js):
+whenJsNoBigInt64: discard
+do:
   test(`shr`, 0xffffffffffffffff'i64, 0x4'i64, 0xffffffffffffffff'i64)
 test(`shr`, 0xffff'i16, 0x4'i16, 0xffff'i16)
 test(`shr`, 0xff'i8, 0x4'i8, 0xff'i8)
 
-when not defined(js):
+whenJsNoBigInt64: discard
+do:
   test(`shr`, 0xffffffff'i64, 0x4'i64, 0x0fffffff'i64)
 test(`shr`, 0xffffffff'i32, 0x4'i32, 0xffffffff'i32)
 
-when not defined(js):
+whenJsNoBigInt64: discard
+do:
   test(`shl`, 0xffffffffffffffff'i64, 0x4'i64, 0xfffffffffffffff0'i64)
 test(`shl`, 0xffff'i16, 0x4'i16, 0xfff0'i16)
 test(`shl`, 0xff'i8, 0x4'i8, 0xf0'i8)
 
-when not defined(js):
+whenJsNoBigInt64: discard
+do:
   test(`shl`, 0xffffffff'i64, 0x4'i64, 0xffffffff0'i64)
 test(`shl`, 0xffffffff'i32, 0x4'i32, 0xfffffff0'i32)
 
diff --git a/tests/stdlib/thashes.nim b/tests/stdlib/thashes.nim
index caae79213..526a2839f 100644
--- a/tests/stdlib/thashes.nim
+++ b/tests/stdlib/thashes.nim
@@ -1,5 +1,5 @@
 discard """
-  targets: "c cpp js"
+  matrix: "; --backend:cpp; --backend:js --jsbigint64:on; --backend:js --jsbigint64:off"
 """
 
 import std/hashes
diff --git a/tests/stdlib/tjson.nim b/tests/stdlib/tjson.nim
index a60d45aab..691bedeaa 100644
--- a/tests/stdlib/tjson.nim
+++ b/tests/stdlib/tjson.nim
@@ -1,6 +1,5 @@
 discard """
-  matrix: "--mm:refc"
-  targets: "c cpp js"
+  matrix: "--mm:refc; --backend:cpp --mm:refc; --backend:js --jsbigint64:off; --backend:js --jsbigint64:on"
 """
 
 
@@ -9,6 +8,7 @@ Note: Macro tests are in tests/stdlib/tjsonmacro.nim
 ]#
 
 import std/[json,parsejson,strutils]
+import std/private/jsutils
 from std/math import isNaN
 when not defined(js):
   import std/streams
@@ -314,7 +314,8 @@ block: # bug #17383
   else:
     testRoundtrip(int.high): "9223372036854775807"
     testRoundtrip(uint.high): "18446744073709551615"
-  when not defined(js):
+  whenJsNoBigInt64: discard
+  do:
     testRoundtrip(int64.high): "9223372036854775807"
     testRoundtrip(uint64.high): "18446744073709551615"
 
diff --git a/tests/stdlib/tjsonmacro.nim b/tests/stdlib/tjsonmacro.nim
index 9c1fa833d..f08c3946b 100644
--- a/tests/stdlib/tjsonmacro.nim
+++ b/tests/stdlib/tjsonmacro.nim
@@ -436,11 +436,7 @@ proc testJson() =
   block:
     let s = """{"a": 1, "b": 2}"""
     let t = parseJson(s).to(Table[string, int])
-    when not defined(js):
-      # For some reason on the JS backend `{"b": 2, "a": 0}` is
-      # sometimes the value of `t`. This needs investigation. I can't
-      # reproduce it right now in an isolated test.
-      doAssert t["a"] == 1
+    doAssert t["a"] == 1
     doAssert t["b"] == 2
 
   block:
diff --git a/tests/stdlib/trandom.nim b/tests/stdlib/trandom.nim
index ef71c3442..c35fc47da 100644
--- a/tests/stdlib/trandom.nim
+++ b/tests/stdlib/trandom.nim
@@ -1,9 +1,10 @@
 discard """
   joinable: false # to avoid messing with global rand state
-  targets: "c js"
+  matrix: "; --backend:js --jsbigint64:off; --backend:js --jsbigint64:on"
 """
 import std/[assertions, formatfloat]
 import std/[random, math, stats, sets, tables]
+import std/private/jsutils
 when not defined(js):
   import std/os
 
@@ -208,8 +209,8 @@ block: # bug #16360
   when withUint:
     test cast[uint](int.high)
     test cast[uint](int.high) + 1
-    when not defined(js):
-      # pending bug #16411
+    whenJsNoBigInt64: discard
+    do:
       test uint64.high
       test uint64.high - 1
     test uint.high - 2
@@ -239,16 +240,12 @@ block: # bug #16296
   test(int.low .. -1)
   test(int.low .. 1)
   test(int64.low .. 1'i64)
-  when not defined(js):
-    # pending bug #16411
-    test(10'u64 .. uint64.high)
+  test(10'u64 .. uint64.high)
 
 block: # bug #17670
-  when not defined(js):
-    # pending bug #16411
-    type UInt48 = range[0'u64..2'u64^48-1]
-    let x = rand(UInt48)
-    doAssert x is UInt48
+  type UInt48 = range[0'u64..2'u64^48-1]
+  let x = rand(UInt48)
+  doAssert x is UInt48
 
 block: # bug #17898
   # Checks whether `initRand()` generates unique states.
diff --git a/tests/stdlib/tstrutils.nim b/tests/stdlib/tstrutils.nim
index 4e88197ff..12b0c13b1 100644
--- a/tests/stdlib/tstrutils.nim
+++ b/tests/stdlib/tstrutils.nim
@@ -1,10 +1,11 @@
 discard """
-  targets: "c cpp js"
+  matrix: "; --backend:cpp; --backend:js --jsbigint64:off; --backend:js --jsbigint64:on"
 """
 
 import std/strutils
 from stdtest/testutils import disableVm
 import std/assertions
+import std/private/jsutils
 # xxx each instance of `disableVm` and `when not defined js:` should eventually be fixed
 
 template rejectParse(e) =
@@ -509,7 +510,8 @@ template main() =
   block: # toHex
     doAssert(toHex(100i16, 32) == "00000000000000000000000000000064")
     doAssert(toHex(-100i16, 32) == "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF9C")
-    when not defined js:
+    whenJsNoBigInt64: discard
+    do:
       doAssert(toHex(high(uint64)) == "FFFFFFFFFFFFFFFF")
       doAssert(toHex(high(uint64), 16) == "FFFFFFFFFFFFFFFF")
       doAssert(toHex(high(uint64), 32) == "0000000000000000FFFFFFFFFFFFFFFF")
@@ -530,11 +532,12 @@ template main() =
     doAssert(spaces(0) == "")
 
   block: # toBin, toOct
-    block:# bug #11369
+    whenJsNoBigInt64: # bug #11369
+      discard
+    do:
       var num: int64 = -1
-      when not defined js:
-        doAssert num.toBin(64) == "1111111111111111111111111111111111111111111111111111111111111111"
-        doAssert num.toOct(24) == "001777777777777777777777"
+      doAssert num.toBin(64) == "1111111111111111111111111111111111111111111111111111111111111111"
+      doAssert num.toOct(24) == "001777777777777777777777"
 
   block: # replace
     doAssert "oo".replace("", "abc") == "oo"
@@ -741,7 +744,8 @@ bar
 
   block: # formatSize
     disableVm:
-      when not defined(js):
+      whenJsNoBigInt64: discard
+      do:
         doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" # <=== bug #8231
       doAssert formatSize((2.234*1024*1024).int) == "2.234MiB"
       doAssert formatSize(4096) == "4KiB"
diff --git a/tests/stdlib/ttimes.nim b/tests/stdlib/ttimes.nim
index f6159d942..47d2efcf1 100644
--- a/tests/stdlib/ttimes.nim
+++ b/tests/stdlib/ttimes.nim
@@ -1,5 +1,5 @@
 discard """
-  targets: "c js"
+  matrix: "; --backend:js --jsbigint64:on; --backend:js --jsbigint64:off"
 """
 
 import times, strutils, unittest
diff --git a/tests/system/tdollars.nim b/tests/system/tdollars.nim
index 93fa5cb9e..7eb26cd6b 100644
--- a/tests/system/tdollars.nim
+++ b/tests/system/tdollars.nim
@@ -1,5 +1,5 @@
 discard """
-  targets: "c cpp js"
+  matrix: "; --backend:cpp; --backend:js --jsbigint64:off; --backend:js --jsbigint64:on"
 """
 
 #[
@@ -12,6 +12,7 @@ duplication (which always results in weaker test coverage in practice).
 ]#
 
 import std/unittest
+import std/private/jsutils
 template test[T](a: T, expected: string) =
   check $a == expected
   var b = a
@@ -66,7 +67,8 @@ block: # `$`(SomeInteger)
   testType int
   testType bool
 
-  when not defined(js): # requires BigInt support
+  whenJsNoBigInt64: discard
+  do:
     testType uint64
     testType int64
     testType BiggestInt
@@ -176,10 +178,10 @@ proc main()=
       res.addInt int64(i)
     doAssert res == "-9-8-7-6-5-4-3-2-10"
 
-    when not defined(js):
+    whenJsNoBigInt64: discard
+    do:
       test2 high(int64), "9223372036854775807"
       test2 low(int64), "-9223372036854775808"
-
     test2 high(int32), "2147483647"
     test2 low(int32), "-2147483648"
     test2 high(int16), "32767"