Browse Source

ES2020 BigInt (#1027)

Co-authored-by: Sébastien Ros <[email protected]>
Marko Lahma 3 years ago
parent
commit
9d8432606d
76 changed files with 2213 additions and 550 deletions
  1. 1 1
      Jint.Repl/Program.cs
  2. 15 0
      Jint.Tests.Test262/BuiltIns/BigIntTests.cs
  3. 2 5
      Jint.Tests.Test262/Test262Test.cs
  4. 9 0
      Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bigint-tobigint-errors.js
  5. 2 1
      Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bigint-tobigint-toprimitive.js
  6. 2 0
      Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bits-toindex-errors.js
  7. 2 0
      Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bits-toindex-toprimitive.js
  8. 26 0
      Jint.Tests.Test262/test/built-ins/BigInt/asIntN/not-a-constructor.js
  9. 9 0
      Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bigint-tobigint-errors.js
  10. 2 0
      Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bigint-tobigint-toprimitive.js
  11. 2 0
      Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bits-toindex-errors.js
  12. 2 0
      Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bits-toindex-toprimitive.js
  13. 26 0
      Jint.Tests.Test262/test/built-ins/BigInt/asUintN/not-a-constructor.js
  14. 33 0
      Jint.Tests.Test262/test/built-ins/BigInt/is-a-constructor.js
  15. 0 23
      Jint.Tests.Test262/test/built-ins/BigInt/new-target-throws.js
  16. 31 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/toLocaleString/not-a-constructor.js
  17. 21 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/a-z.js
  18. 31 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/not-a-constructor.js
  19. 1 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/prototype-call.js
  20. 2 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/thisbigintvalue-not-valid-throws.js
  21. 31 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/valueOf/not-a-constructor.js
  22. 1 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/valueOf/this-value-invalid-object-throws.js
  23. 2 0
      Jint.Tests.Test262/test/built-ins/BigInt/prototype/valueOf/this-value-invalid-primitive-throws.js
  24. 72 0
      Jint.Tests.Test262/test/built-ins/BigInt/wrapper-object-ordinary-toprimitive.js
  25. 9 1
      Jint.Tests.Test262/test/skipped.json
  26. 2 0
      Jint.Tests/Runtime/Domain/JsUuid.cs
  27. 1 1
      Jint.Tests/Runtime/OperatorOverloadingTests.cs
  28. 1 1
      Jint.Tests/Runtime/RegExpTests.cs
  29. 13 5
      Jint.Tests/Runtime/TypedArrayInteropTests.cs
  30. 2 2
      Jint.Tests/Runtime/UuidTests.cs
  31. 1 1
      Jint/Jint.csproj
  32. 31 5
      Jint/JsValueExtensions.cs
  33. 4 10
      Jint/Native/Array/ArrayPrototype.cs
  34. 36 19
      Jint/Native/ArrayBuffer/ArrayBufferInstance.cs
  35. 0 43
      Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs
  36. 134 0
      Jint/Native/BigInt/BigIntConstructor.cs
  37. 24 0
      Jint/Native/BigInt/BigIntInstance.cs
  38. 153 0
      Jint/Native/BigInt/BigIntPrototype.cs
  39. 1 1
      Jint/Native/Boolean/BooleanConstructor.cs
  40. 4 4
      Jint/Native/Boolean/BooleanInstance.cs
  41. 2 2
      Jint/Native/Boolean/BooleanPrototype.cs
  42. 3 5
      Jint/Native/DataView/DataViewPrototype.cs
  43. 2 1
      Jint/Native/Global/GlobalObject.cs
  44. 105 0
      Jint/Native/JsBigInt.cs
  45. 16 14
      Jint/Native/JsBoolean.cs
  46. 12 10
      Jint/Native/JsNull.cs
  47. 72 11
      Jint/Native/JsNumber.cs
  48. 47 27
      Jint/Native/JsString.cs
  49. 6 6
      Jint/Native/JsUndefined.cs
  50. 67 26
      Jint/Native/JsValue.cs
  51. 55 24
      Jint/Native/Json/JsonSerializer.cs
  52. 1 1
      Jint/Native/Map/MapInstance.cs
  53. 27 11
      Jint/Native/Number/NumberConstructor.cs
  54. 1 1
      Jint/Native/Number/NumberInstance.cs
  55. 1 1
      Jint/Native/Number/NumberPrototype.cs
  56. 5 16
      Jint/Native/Object/ObjectInstance.cs
  57. 25 0
      Jint/Native/SameValueZeroComparer.cs
  58. 1 1
      Jint/Native/Set/SetInstance.cs
  59. 1 1
      Jint/Native/String/StringConstructor.cs
  60. 5 5
      Jint/Native/String/StringInstance.cs
  61. 3 3
      Jint/Native/String/StringPrototype.cs
  62. 28 16
      Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs
  63. 18 2
      Jint/Native/TypedArray/TypedArrayConstructor.cs
  64. 51 27
      Jint/Native/TypedArray/TypedArrayInstance.cs
  65. 159 0
      Jint/Native/TypedArray/TypedArrayValue.cs
  66. 49 6
      Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
  67. 310 100
      Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
  68. 94 59
      Jint/Runtime/Interpreter/Expressions/JintExpression.cs
  69. 6 0
      Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs
  70. 22 10
      Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs
  71. 53 12
      Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs
  72. 1 1
      Jint/Runtime/Interpreter/Statements/JintSwitchBlock.cs
  73. 5 0
      Jint/Runtime/Intrinsics.cs
  74. 2 2
      Jint/Runtime/OrderedSet.cs
  75. 216 25
      Jint/Runtime/TypeConverter.cs
  76. 1 1
      README.md

+ 1 - 1
Jint.Repl/Program.cs

@@ -66,7 +66,7 @@ namespace Jint.Repl
                 try
                 {
                     var result = engine.Evaluate(input, parserOptions);
-                    if (!result.IsNull() && !result.IsUndefined())
+                    if (!result.IsPrimitive() && result is not IPrimitiveInstance)
                     {
                         var serializer = new JsonSerializer(engine);
                         var str = serializer.Serialize(result, Undefined.Instance, "  ");

+ 15 - 0
Jint.Tests.Test262/BuiltIns/BigIntTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.BuiltIns
+{
+    public class BigIntTests : Test262Test
+    {
+        [Theory(DisplayName = "built-ins\\BigInt")]
+        [MemberData(nameof(SourceFiles), "built-ins\\BigInt", false)]
+        [MemberData(nameof(SourceFiles), "built-ins\\BigInt", true, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 2 - 5
Jint.Tests.Test262/Test262Test.cs

@@ -73,6 +73,7 @@ namespace Jint.Tests.Test262
                 "detachArrayBuffer.js",
                 "byteConversionValues.js",
                 "hidden-constructors.js",
+                "testBigIntTypedArray.js"
             };
 
             Sources = new Dictionary<string, Script>(files.Length);
@@ -174,7 +175,7 @@ namespace Jint.Tests.Test262
 
             if (!negative && !string.IsNullOrWhiteSpace(lastError))
             {
-                throw new XunitException(lastError);
+                throw new XunitException($"{Environment.NewLine}{fileName}{Environment.NewLine}{Environment.NewLine}{lastError}");
             }
         }
 
@@ -242,10 +243,6 @@ namespace Jint.Tests.Test262
                                 skip = true;
                                 reason = "tail-calls not implemented";
                                 break;
-                            case "BigInt":
-                                skip = true;
-                                reason = "BigInt not implemented";
-                                break;
                             case "generators":
                                 skip = true;
                                 reason = "generators not implemented";

+ 9 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bigint-tobigint-errors.js

@@ -9,6 +9,15 @@ info: |
   2. Let bigint ? ToBigInt(bigint).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asIntN, 'function');
+
+assert.throws(TypeError, function () {
+  BigInt.asIntN();
+}, "ToBigInt: no argument => undefined => TypeError");
+assert.throws(TypeError, function () {
+  BigInt.asIntN(0);
+}, "ToBigInt: no argument => undefined => TypeError");
 
 assert.throws(TypeError, function() {
   BigInt.asIntN(0, undefined);

+ 2 - 1
Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bigint-tobigint-toprimitive.js

@@ -9,7 +9,8 @@ info: |
   2. Let bigint ? ToBigInt(bigint).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
-
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asIntN, 'function');
 function err() {
   throw new Test262Error();
 }

+ 2 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bits-toindex-errors.js

@@ -9,6 +9,8 @@ info: |
   1. Let bits be ? ToIndex(bits).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asIntN, 'function');
 
 assert.throws(RangeError, function() {
   BigInt.asIntN(-1, 0n);

+ 2 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asIntN/bits-toindex-toprimitive.js

@@ -9,6 +9,8 @@ info: |
   1. Let bits be ? ToIndex(bits).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asIntN, 'function');
 
 function err() {
   throw new Test262Error();

+ 26 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asIntN/not-a-constructor.js

@@ -0,0 +1,26 @@
+// Copyright (C) 2020 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+  BigInt.asIntN does not implement [[Construct]], is not new-able
+info: |
+  ECMAScript Function Objects
+
+  Built-in function objects that are not identified as constructors do not
+  implement the [[Construct]] internal method unless otherwise specified in
+  the description of a particular function.
+
+  sec-evaluatenew
+
+  ...
+  7. If IsConstructor(constructor) is false, throw a TypeError exception.
+  ...
+includes: [isConstructor.js]
+features: [Reflect.construct, BigInt, arrow-function]
+---*/
+assert.sameValue(isConstructor(BigInt.asIntN), false, 'isConstructor(BigInt.asIntN) must return false');
+
+assert.throws(TypeError, () => {
+  new BigInt.asIntN(64, 1n);
+}, '`new BigInt.asIntN(64, 1n)` throws TypeError');

+ 9 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bigint-tobigint-errors.js

@@ -9,6 +9,15 @@ info: |
   2. Let bigint ? ToBigInt(bigint).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asUintN, 'function');
+
+assert.throws(TypeError, function () {
+  BigInt.asUintN();
+}, "ToBigInt: no argument => undefined => TypeError");
+assert.throws(TypeError, function () {
+  BigInt.asUintN(0);
+}, "ToBigInt: no argument => undefined => TypeError");
 
 assert.throws(TypeError, function() {
   BigInt.asUintN(0, undefined);

+ 2 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bigint-tobigint-toprimitive.js

@@ -9,6 +9,8 @@ info: |
   2. Let bigint ? ToBigInt(bigint).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asUintN, 'function');
 
 function err() {
   throw new Test262Error();

+ 2 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bits-toindex-errors.js

@@ -9,6 +9,8 @@ info: |
   1. Let bits be ? ToIndex(bits).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asUintN, 'function');
 
 assert.throws(RangeError, function() {
   BigInt.asUintN(-1, 0n);

+ 2 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asUintN/bits-toindex-toprimitive.js

@@ -9,6 +9,8 @@ info: |
   1. Let bits be ? ToIndex(bits).
 features: [BigInt, computed-property-names, Symbol, Symbol.toPrimitive]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
+assert.sameValue(typeof BigInt.asUintN, 'function');
 
 function err() {
   throw new Test262Error();

+ 26 - 0
Jint.Tests.Test262/test/built-ins/BigInt/asUintN/not-a-constructor.js

@@ -0,0 +1,26 @@
+// Copyright (C) 2020 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+  BigInt.asUintN does not implement [[Construct]], is not new-able
+info: |
+  ECMAScript Function Objects
+
+  Built-in function objects that are not identified as constructors do not
+  implement the [[Construct]] internal method unless otherwise specified in
+  the description of a particular function.
+
+  sec-evaluatenew
+
+  ...
+  7. If IsConstructor(constructor) is false, throw a TypeError exception.
+  ...
+includes: [isConstructor.js]
+features: [Reflect.construct, BigInt, arrow-function]
+---*/
+assert.sameValue(isConstructor(BigInt.asUintN), false, 'isConstructor(BigInt.asUintN) must return false');
+
+assert.throws(TypeError, () => {
+  new BigInt.asUintN(64, 1n);
+}, '`new BigInt.asUintN(64, 1n)` throws TypeError');

+ 33 - 0
Jint.Tests.Test262/test/built-ins/BigInt/is-a-constructor.js

@@ -0,0 +1,33 @@
+// Copyright (C) 2020 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+  BigInt is a constructor, and does implement [[Construct]], but is not new target
+info: |
+  sec-bigint-constructor
+
+  - is not intended to be used with the new operator or to be subclassed. It may be used as the value of an extends clause of a class definition but a super call to the BigInt constructor will cause an exception.
+
+  sec-bigint-constructor-number-value
+
+  1. If NewTarget is not undefined, throw a TypeError exception.
+includes: [isConstructor.js]
+features: [BigInt, Reflect.construct, arrow-function]
+---*/
+
+assert.sameValue(
+  isConstructor(BigInt),
+  true,
+  'isConstructor(BigInt) must return true'
+);
+
+assert.throws(TypeError, () => {
+  new BigInt({
+    valueOf() {
+      new Test262Error();
+    }
+  });
+}, '`new BigInt({ valueOf() {new Test262Error();}})` throws TypeError');
+

+ 0 - 23
Jint.Tests.Test262/test/built-ins/BigInt/new-target-throws.js

@@ -1,23 +0,0 @@
-// Copyright (C) 2017 Robin Templeton. All rights reserved.
-// This code is governed by the BSD license found in the LICENSE file.
-
-/*---
-description: Throws a TypeError if BigInt is called with a new target
-esid: sec-bigint-constructor
-info: |
-  1. If NewTarget is not undefined, throw a TypeError exception.
-  ...
-features: [BigInt]
----*/
-
-assert.throws(TypeError, function() {
-  new BigInt();
-});
-
-assert.throws(TypeError, function() {
-  new BigInt({
-    valueOf: function() {
-      throw new Test262Error("unreachable");
-    }
-  });
-});

+ 31 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/toLocaleString/not-a-constructor.js

@@ -0,0 +1,31 @@
+// Copyright (C) 2020 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+  BigInt.prototype.toLocaleString does not implement [[Construct]], is not new-able
+info: |
+  ECMAScript Function Objects
+
+  Built-in function objects that are not identified as constructors do not
+  implement the [[Construct]] internal method unless otherwise specified in
+  the description of a particular function.
+
+  sec-evaluatenew
+
+  ...
+  7. If IsConstructor(constructor) is false, throw a TypeError exception.
+  ...
+includes: [isConstructor.js]
+features: [Reflect.construct, BigInt, arrow-function]
+---*/
+assert.sameValue(
+  isConstructor(BigInt.prototype.toLocaleString),
+  false,
+  'isConstructor(BigInt.prototype.toLocaleString) must return false'
+);
+
+assert.throws(TypeError, () => {
+  let n = 1n;
+  new n.toLocaleString();
+}, '`let n = 1n; new n.toLocaleString()` throws TypeError');

+ 21 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/a-z.js

@@ -0,0 +1,21 @@
+// Copyright (C) 2020 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+esid: sec-bigint.prototype.tostring
+description: >
+  Letters a-z are used for digits with values 10 through 35
+info: |
+  6. Return the String representation of this Number value using
+  the radix specified by radixNumber. Letters a-z are used for
+  digits with values 10 through 35. The precise algorithm is
+  implementation-dependent, however the algorithm should be a
+  generalization of that specified in 6.1.6.2.23.
+features: [BigInt]
+---*/
+
+for (let radix = 11; radix <= 36; radix++) {
+  for (let i = 10n; i < radix; i++) {
+    assert.sameValue(i.toString(radix), String.fromCharCode(Number(i + 87n)));
+  }
+}

+ 31 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/not-a-constructor.js

@@ -0,0 +1,31 @@
+// Copyright (C) 2020 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+  BigInt.prototype.toString does not implement [[Construct]], is not new-able
+info: |
+  ECMAScript Function Objects
+
+  Built-in function objects that are not identified as constructors do not
+  implement the [[Construct]] internal method unless otherwise specified in
+  the description of a particular function.
+
+  sec-evaluatenew
+
+  ...
+  7. If IsConstructor(constructor) is false, throw a TypeError exception.
+  ...
+includes: [isConstructor.js]
+features: [Reflect.construct, BigInt, arrow-function]
+---*/
+assert.sameValue(
+  isConstructor(BigInt.prototype.toString),
+  false,
+  'isConstructor(BigInt.prototype.toString) must return false'
+);
+
+assert.throws(TypeError, () => {
+  let n = 1n;
+  new n.toString();
+}, '`let n = 1n; new n.toString()` throws TypeError');

+ 1 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/prototype-call.js

@@ -15,6 +15,7 @@ info: |
   [[BigIntData]] internal slot.
 features: [BigInt]
 ---*/
+assert.sameValue(typeof BigInt, 'function');
 
 assert.throws(TypeError, function() {
   BigInt.prototype.toString(1);

+ 2 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/toString/thisbigintvalue-not-valid-throws.js

@@ -21,6 +21,8 @@ features: [BigInt, Symbol, Symbol.toPrimitive]
 
 var toString = BigInt.prototype.toString;
 
+assert.sameValue(typeof toString, 'function');
+
 assert.throws(TypeError, function() {
   toString.call({
     x: 1n

+ 31 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/valueOf/not-a-constructor.js

@@ -0,0 +1,31 @@
+// Copyright (C) 2020 Rick Waldron. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-ecmascript-standard-built-in-objects
+description: >
+  BigInt.prototype.valueOf does not implement [[Construct]], is not new-able
+info: |
+  ECMAScript Function Objects
+
+  Built-in function objects that are not identified as constructors do not
+  implement the [[Construct]] internal method unless otherwise specified in
+  the description of a particular function.
+
+  sec-evaluatenew
+
+  ...
+  7. If IsConstructor(constructor) is false, throw a TypeError exception.
+  ...
+includes: [isConstructor.js]
+features: [Reflect.construct, BigInt, arrow-function]
+---*/
+assert.sameValue(
+  isConstructor(BigInt.prototype.valueOf),
+  false,
+  'isConstructor(BigInt.prototype.valueOf) must return false'
+);
+
+assert.throws(TypeError, () => {
+  let n = 1n;
+  new n.valueOf();
+}, '`let n = 1n; new n.valueOf()` throws TypeError');

+ 1 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/valueOf/this-value-invalid-object-throws.js

@@ -20,6 +20,7 @@ features: [BigInt]
 ---*/
 
 var valueOf = BigInt.prototype.valueOf;
+assert.sameValue(typeof valueOf, 'function');
 
 assert.throws(TypeError, function() {
   valueOf.call({});

+ 2 - 0
Jint.Tests.Test262/test/built-ins/BigInt/prototype/valueOf/this-value-invalid-primitive-throws.js

@@ -21,6 +21,8 @@ features: [BigInt, Symbol]
 
 var valueOf = BigInt.prototype.valueOf;
 
+assert.sameValue(typeof valueOf, 'function');
+
 assert.throws(TypeError, function() {
   valueOf.call(undefined);
 }, "undefined");

+ 72 - 0
Jint.Tests.Test262/test/built-ins/BigInt/wrapper-object-ordinary-toprimitive.js

@@ -0,0 +1,72 @@
+// Copyright (C) 2021 Alexey Shvayka. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+/*---
+esid: sec-toprimitive
+description: >
+    BigInt wrapper object is converted to primitive via OrdinaryToPrimitive.
+info: |
+    ToPrimitive ( input [ , preferredType ] )
+
+    [...]
+    2. If Type(input) is Object, then
+        a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
+        b. If exoticToPrim is not undefined, then
+            [...]
+        c. If preferredType is not present, let preferredType be number.
+        d. Return ? OrdinaryToPrimitive(input, preferredType).
+features: [BigInt]
+---*/
+
+const BigIntToString = BigInt.prototype.toString;
+let toStringGets = 0;
+let toStringCalls = 0;
+let toStringFunction = function() { ++toStringCalls; return `${BigIntToString.call(this)}foo`; };
+Object.defineProperty(BigInt.prototype, "toString", {
+    get: () => { ++toStringGets; return toStringFunction; },
+});
+
+assert.sameValue("" + Object(1n), "1", "hint: default");
+assert.throws(TypeError, () => { +Object(1n); }, "hint: number");
+assert.sameValue(`${Object(1n)}`, "1foo", "hint: string");
+
+assert.sameValue(toStringGets, 1);
+assert.sameValue(toStringCalls, 1);
+
+const BigIntValueOf = BigInt.prototype.valueOf;
+let valueOfGets = 0;
+let valueOfCalls = 0;
+let valueOfFunction = function() { ++valueOfCalls; return BigIntValueOf.call(this) * 2n; };
+Object.defineProperty(BigInt.prototype, "valueOf", {
+    get: () => { ++valueOfGets; return valueOfFunction; },
+});
+
+assert(Object(1n) == 2n, "hint: default");
+assert.sameValue(Object(1n) + 1n, 3n, "hint: number");
+assert.sameValue({ "1foo": 1, "2": 2 }[Object(1n)], 1, "hint: string");
+
+assert.sameValue(toStringGets, 2);
+assert.sameValue(toStringCalls, 2);
+assert.sameValue(valueOfGets, 2);
+assert.sameValue(valueOfCalls, 2);
+
+toStringFunction = undefined;
+
+assert.throws(TypeError, () => { 1 + Object(1n); }, "hint: default");
+assert.sameValue(Object(1n) * 1n, 2n, "hint: number");
+assert.sameValue("".concat(Object(1n)), "2", "hint: string");
+
+assert.sameValue(toStringGets, 3);
+assert.sameValue(toStringCalls, 2);
+assert.sameValue(valueOfGets, 5);
+assert.sameValue(valueOfCalls, 5);
+
+valueOfFunction = null;
+
+assert.throws(TypeError, () => { new Date(Object(1n)); }, "hint: default");
+assert.throws(TypeError, () => { Number(Object(1n)); }, "hint: number");
+assert.throws(TypeError, () => { String(Object(1n)); }, "hint: string");
+
+assert.sameValue(toStringGets, 6);
+assert.sameValue(toStringCalls, 2);
+assert.sameValue(valueOfGets, 8);
+assert.sameValue(valueOfCalls, 5);

+ 9 - 1
Jint.Tests.Test262/test/skipped.json

@@ -174,6 +174,14 @@
     "reason": "inner binding rejects modification (from parameters) Expected a Error to be thrown but no exception was thrown at all"
   },
 
+  {
+    "source": "built-ins/TypedArrayConstructors/ctors-bigint/object-arg/as-generator-iterable-returns.js",
+    "reason": "yield not implemented"
+  },
+  {
+    "source": "built-ins/TypedArrayConstructors/ctors-bigint/object-arg/iterating-throws.js",
+    "reason": "yield not implemented"
+  },
   {
     "source": "language/expressions/object/accessor-name-computed-yield-id.js",
     "reason": "accessor / yield not implemented"
@@ -363,5 +371,5 @@
   {
     "source": "language/statements/for-of/dstr-obj-id-identifier-yield-ident-valid.js",
     "reason": "Esprima problem"
-  },
+  }
 ]

+ 2 - 0
Jint.Tests/Runtime/Domain/JsUuid.cs

@@ -21,5 +21,7 @@ namespace Jint.Tests.Runtime.Domain
         public override object ToObject() => _value;
 
         public override string ToString() => _value.ToString();
+
+        public override bool Equals(object obj) => Equals(obj as JsUuid);
     }
 }

+ 1 - 1
Jint.Tests/Runtime/OperatorOverloadingTests.cs

@@ -367,7 +367,7 @@ namespace Jint.Tests.Runtime
             engine.SetValue("log", new Action<object>(Console.WriteLine));
 
             engine.Evaluate("let v1 = new Vector2D(1, 2);");
-            Assert.Equal("(1, 2)", engine.Evaluate("new String(v1)").As<StringInstance>().PrimitiveValue.ToString());
+            Assert.Equal("(1, 2)", engine.Evaluate("new String(v1)").As<StringInstance>().StringData.ToString());
             Assert.Equal("### (1, 2) ###", engine.Evaluate("'### ' + v1 + ' ###'"));
         }
     }

+ 1 - 1
Jint.Tests/Runtime/RegExpTests.cs

@@ -87,7 +87,7 @@ namespace Jint.Tests.Runtime
         public void ShouldNotThrowErrorOnRegExNumericNegation()
         {
             var engine = new Engine();
-            Assert.Equal(JsNumber.DoubleNaN, engine.Evaluate("-/[]/"));
+            Assert.True(ReferenceEquals(JsNumber.DoubleNaN, engine.Evaluate("-/[]/")));
         }
     }
 }

+ 13 - 5
Jint.Tests/Runtime/TypedArrayInteropTests.cs

@@ -73,7 +73,7 @@ namespace Jint.Tests.Runtime
         public void CanInteropWithInt32()
         {
             var engine = new Engine();
-            var source = new int[] { 42, 12 };
+            var source = new[] { 42, 12 };
             engine.SetValue("testSubject", engine.Realm.Intrinsics.Int32Array.Construct(source));
             ValidateCreatedTypeArray(engine, "Int32Array");
 
@@ -96,26 +96,26 @@ namespace Jint.Tests.Runtime
             Assert.Equal(source, fromEngine.AsUint32Array());
         }
 
-        [Fact(Skip = "BigInt not implemented")]
+        [Fact]
         public void CanInteropWithBigInt64()
         {
             var engine = new Engine();
             var source = new long[] { 42, 12 };
             engine.SetValue("testSubject", engine.Realm.Intrinsics.BigInt64Array.Construct(source));
-            ValidateCreatedTypeArray(engine, "BigInt64Array");
+            ValidateCreatedBigIntegerTypeArray(engine, "BigInt64Array");
 
             var fromEngine = engine.GetValue("testSubject");
             Assert.True(fromEngine.IsBigInt64Array());
             Assert.Equal(source, fromEngine.AsBigInt64Array());
         }
 
-        [Fact(Skip = "BigInt not implemented")]
+        [Fact]
         public void CanInteropWithBigUint64()
         {
             var engine = new Engine();
             var source = new ulong[] { 42, 12 };
             engine.SetValue("testSubject", engine.Realm.Intrinsics.BigUint64Array.Construct(source));
-            ValidateCreatedTypeArray(engine, "BigUint64Array");
+            ValidateCreatedBigIntegerTypeArray(engine, "BigUint64Array");
 
             var fromEngine = engine.GetValue("testSubject");
             Assert.True(fromEngine.IsBigUint64Array());
@@ -129,5 +129,13 @@ namespace Jint.Tests.Runtime
             Assert.Equal(42, engine.Evaluate("testSubject[0]").AsNumber());
             Assert.Equal(12, engine.Evaluate("testSubject[1]").AsNumber());
         }
+
+        private static void ValidateCreatedBigIntegerTypeArray(Engine engine, string arrayName)
+        {
+            Assert.Equal(arrayName, engine.Evaluate("testSubject.constructor.name").AsString());
+            Assert.Equal(2, engine.Evaluate("testSubject.length").AsNumber());
+            Assert.Equal(42, engine.Evaluate("testSubject[0]").AsBigInt());
+            Assert.Equal(12, engine.Evaluate("testSubject[1]").AsBigInt());
+        }
     }
 }

+ 2 - 2
Jint.Tests/Runtime/UuidTests.cs

@@ -43,8 +43,8 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void Copy()
         {
-            var actual = (bool)RunTest($"const g = new Uuid(); copy(g).toString() === g.toString()");
-            Assert.True(actual);
+            _engine.Evaluate("const g = new Uuid();");
+            Assert.Equal(_engine.Evaluate("copy(g).toString()").AsString(), _engine.Evaluate("g.toString()").AsString());
         }
     }
 }

+ 1 - 1
Jint/Jint.csproj

@@ -8,7 +8,7 @@
     <IsPackable>true</IsPackable>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Esprima" Version="2.0.4" />
+    <PackageReference Include="Esprima" Version="2.1.0" />
     <PackageReference Include="IsExternalInit" Version="1.0.1" PrivateAssets="all" />
     <PackageReference Include="Nullable" Version="1.3.0" PrivateAssets="all" />
   </ItemGroup>

+ 31 - 5
Jint/JsValueExtensions.cs

@@ -1,6 +1,8 @@
 using System;
 using System.Diagnostics.Contracts;
+using System.Numerics;
 using System.Runtime.CompilerServices;
+using Esprima;
 using Jint.Native;
 using Jint.Native.Array;
 using Jint.Native.Date;
@@ -89,6 +91,13 @@ namespace Jint
             return (value._type & (InternalTypes.Number | InternalTypes.Integer)) != 0;
         }
 
+        [Pure]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool IsBigInt(this JsValue value)
+        {
+            return (value._type & InternalTypes.BigInt) != 0;
+        }
+
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static bool IsInteger(this JsValue value)
@@ -204,6 +213,12 @@ namespace Jint
             return (int) ((JsNumber) value)._value;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static BigInteger AsBigInt(this JsValue value)
+        {
+            return ((JsBigInt) value)._value;
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static string AsString(this JsValue value)
         {
@@ -343,13 +358,12 @@ namespace Jint
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static long[] AsBigInt64Array(this JsValue value)
         {
-            if (!value.IsUint32Array())
+            if (!value.IsBigInt64Array())
             {
                 ThrowWrongTypeException(value, "BigInt64Array");
             }
 
-            ExceptionHelper.ThrowNotImplementedException();
-            return null;
+            return ((TypedArrayInstance) value).ToNativeArray<long>();
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -366,8 +380,7 @@ namespace Jint
                 ThrowWrongTypeException(value, "BigUint64Array");
             }
 
-            ExceptionHelper.ThrowNotImplementedException();
-            return null;
+            return ((TypedArrayInstance) value).ToNativeArray<ulong>();
         }
 
         [Pure]
@@ -467,5 +480,18 @@ namespace Jint
         {
             ExceptionHelper.ThrowArgumentException($"Expected {expectedType} but got {value._type}");
         }
+
+        internal static BigInteger ToBigInteger(this JsValue value, Engine engine)
+        {
+            try
+            {
+                return TypeConverter.ToBigInt(value);
+            }
+            catch (ParserException ex)
+            {
+                ExceptionHelper.ThrowSyntaxError(engine.Realm, ex.Message);
+                return default;
+            }
+        }
     }
 }

+ 4 - 10
Jint/Native/Array/ArrayPrototype.cs

@@ -10,9 +10,6 @@ using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
-using Jint.Runtime.Interpreter.Expressions;
-
-using static System.String;
 
 namespace Jint.Native.Array
 {
@@ -299,8 +296,7 @@ namespace Jint.Native.Array
                 if (kPresent)
                 {
                     var elementK = o.Get(i);
-                    var same = JintBinaryExpression.StrictlyEqual(elementK, searchElement);
-                    if (same)
+                    if (elementK == searchElement)
                     {
                         return i;
                     }
@@ -620,7 +616,7 @@ namespace Jint.Native.Array
             while (k < len)
             {
                 var value = o.Get(k);
-                if (JintBinaryExpression.SameValueZero(value, searchElement))
+                if (SameValueZeroComparer.Equals(value, searchElement))
                 {
                     return true;
                 }
@@ -722,8 +718,7 @@ namespace Jint.Native.Array
                 if (kPresent)
                 {
                     var elementK = o.Get(k);
-                    var same = JintBinaryExpression.StrictlyEqual(elementK, searchElement);
-                    if (same)
+                    if (elementK == searchElement)
                     {
                         return k;
                     }
@@ -1484,8 +1479,7 @@ namespace Jint.Native.Array
                 var xString = TypeConverter.ToString(x);
                 var yString = TypeConverter.ToString(y);
 
-                var r = CompareOrdinal(xString, yString);
-                return r;
+                return string.CompareOrdinal(xString, yString);
             }
         }
     }

+ 36 - 19
Jint/Native/ArrayBuffer/ArrayBufferInstance.cs

@@ -80,7 +80,7 @@ namespace Jint.Native.ArrayBuffer
         /// <summary>
         /// https://tc39.es/ecma262/#sec-getvaluefrombuffer
         /// </summary>
-        internal double GetValueFromBuffer(
+        internal TypedArrayValue GetValueFromBuffer(
             int byteIndex,
             TypedArrayElementType type,
             bool isTypedArray,
@@ -104,19 +104,22 @@ namespace Jint.Native.ArrayBuffer
                 h. Append Chosen Value EsprimaExtensions.Record { [[Event]]: readEvent, [[ChosenValue]]: rawValue } to execution.[[ChosenValues]].
             */
             ExceptionHelper.ThrowNotImplementedException("SharedArrayBuffer not implemented");
-            return 0;
+            return default;
         }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-rawbytestonumeric
         /// </summary>
-        internal double RawBytesToNumeric(TypedArrayElementType type, int byteIndex, bool isLittleEndian)
+        internal TypedArrayValue RawBytesToNumeric(TypedArrayElementType type, int byteIndex, bool isLittleEndian)
         {
             var elementSize = type.GetElementSize();
             var rawBytes = _arrayBufferData;
 
-            // floats require a little more at the moment
-            var needsReverse = !isLittleEndian && elementSize > 1 && (type == TypedArrayElementType.Float32 || type == TypedArrayElementType.Float64);
+            // 8 byte values require a little more at the moment
+            var needsReverse = !isLittleEndian
+                               && elementSize > 1
+                               && type is TypedArrayElementType.Float32 or TypedArrayElementType.Float64 or TypedArrayElementType.BigInt64 or TypedArrayElementType.BigUint64;
+
             if (needsReverse)
             {
                 System.Array.Copy(rawBytes, byteIndex, _workBuffer, 0, elementSize);
@@ -146,13 +149,19 @@ namespace Jint.Native.ArrayBuffer
                 return value;
             }
 
-            if (type.IsBigIntElementType())
+            if (type == TypedArrayElementType.BigUint64)
+            {
+                var value = BitConverter.ToUInt64(rawBytes, byteIndex);
+                return value;
+            }
+
+            if (type == TypedArrayElementType.BigInt64)
             {
-                // return the BigInt value that corresponds to intValue
-                ExceptionHelper.ThrowNotImplementedException("BigInt not implemented");
+                var value = BitConverter.ToInt64(rawBytes, byteIndex);
+                return value;
             }
 
-            long? intValue = type switch
+            TypedArrayValue? arrayValue = type switch
             {
                 TypedArrayElementType.Int8 => ((sbyte) rawBytes[byteIndex]),
                 TypedArrayElementType.Uint8 => (rawBytes[byteIndex]),
@@ -176,12 +185,12 @@ namespace Jint.Native.ArrayBuffer
                 _ => null
             };
 
-            if (intValue is null)
+            if (arrayValue is null)
             {
                 ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(type), type.ToString());
             }
 
-            return (double) intValue;
+            return arrayValue.Value;
         }
 
         /// <summary>
@@ -190,7 +199,7 @@ namespace Jint.Native.ArrayBuffer
         internal void SetValueInBuffer(
             int byteIndex,
             TypedArrayElementType type,
-            double value,
+            TypedArrayValue value,
             bool isTypedArray,
             ArrayBufferOrder order,
             bool? isLittleEndian = null)
@@ -214,23 +223,31 @@ namespace Jint.Native.ArrayBuffer
             }
         }
 
-        private byte[] NumericToRawBytes(TypedArrayElementType type, double value, bool isLittleEndian)
+        private byte[] NumericToRawBytes(TypedArrayElementType type, TypedArrayValue value, bool isLittleEndian)
         {
             byte[] rawBytes;
             if (type == TypedArrayElementType.Float32)
             {
                 // Let rawBytes be a List whose elements are the 4 bytes that are the result of converting value to IEEE 754-2019 binary32 format using roundTiesToEven mode. If isLittleEndian is false, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If value is NaN, rawBytes may be set to any implementation chosen IEEE 754-2019 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable NaN value.
-                rawBytes = BitConverter.GetBytes((float) value);
+                rawBytes = BitConverter.GetBytes((float) value.DoubleValue);
             }
             else if (type == TypedArrayElementType.Float64)
             {
                 // Let rawBytes be a List whose elements are the 8 bytes that are the IEEE 754-2019 binary64 format encoding of value. If isLittleEndian is false, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If value is NaN, rawBytes may be set to any implementation chosen IEEE 754-2019 binary64 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable NaN value.
-                rawBytes = BitConverter.GetBytes(value);
+                rawBytes = BitConverter.GetBytes(value.DoubleValue);
+            }
+            else if (type == TypedArrayElementType.BigInt64)
+            {
+                rawBytes = BitConverter.GetBytes(TypeConverter.ToBigInt64(value.BigInteger));
+            }
+            else if (type == TypedArrayElementType.BigUint64)
+            {
+                rawBytes = BitConverter.GetBytes(TypeConverter.ToBigUint64(value.BigInteger));
             }
             else
             {
                 // inlined conversion for faster speed instead of getting the method in spec
-                var intValue = (long) value;
+                var intValue = (long) value.DoubleValue;
                 rawBytes = _workBuffer;
                 switch (type)
                 {
@@ -241,7 +258,7 @@ namespace Jint.Native.ArrayBuffer
                         rawBytes[0] = (byte) intValue;
                         break;
                     case TypedArrayElementType.Uint8C:
-                        rawBytes[0] = (byte) TypeConverter.ToUint8Clamp(value);
+                        rawBytes[0] = (byte) TypeConverter.ToUint8Clamp(value.DoubleValue);
                         break;
                     case TypedArrayElementType.Int16:
 #if !NETSTANDARD2_1
@@ -259,9 +276,9 @@ namespace Jint.Native.ArrayBuffer
                         break;
                     case TypedArrayElementType.Int32:
 #if !NETSTANDARD2_1
-                        rawBytes = BitConverter.GetBytes((int) intValue);
+                        rawBytes = BitConverter.GetBytes((uint) intValue);
 #else
-                        BitConverter.TryWriteBytes(rawBytes, (int) intValue);
+                        BitConverter.TryWriteBytes(rawBytes, (uint) intValue);
 #endif
                         break;
                     case TypedArrayElementType.Uint32:

+ 0 - 43
Jint/Native/ArrayBuffer/ArrayBufferPrototype.cs

@@ -154,48 +154,5 @@ namespace Jint.Native.ArrayBuffer
             System.Array.Copy(fromBuf, first, toBuf, 0, newLen);
             return bufferInstance;
         }
-
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-copydatablockbytes
-        /// </summary>
-        /// <remarks>
-        /// Here only to support algorithm of shared view buffer.
-        /// </remarks>
-        private void CopyDataBlockBytes(byte[] toBlock, int toIndex, byte[] fromBlock, int fromIndex, int count)
-        {
-            var fromSize = Length;
-            var toSize = Length;
-
-            bool isSharedDataBlock = false;
-
-            while (count > 0)
-            {
-                if (isSharedDataBlock)
-                {
-                    /*
-                    i. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
-                        ii. Let eventList be the [[EventList]] field of the element in execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
-                        iii. Let bytes be a List whose sole element is a nondeterministically chosen byte value.
-                        iv. NOTE: In implementations, bytes is the result of a non-atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency.
-                        v. Let readEvent be ReadSharedMemory { [[Order]]: Unordered, [[NoTear]]: true, [[Block]]: fromBlock, [[ByteIndex]]: fromIndex, [[ElementSize]]: 1 }.
-                    vi. Append readEvent to eventList.
-                        vii. Append Chosen Value Record { [[Event]]: readEvent, [[ChosenValue]]: bytes } to execution.[[ChosenValues]].
-                    viii. If toBlock is a Shared Data Block, then
-                    1. Append WriteSharedMemory { [[Order]]: Unordered, [[NoTear]]: true, [[Block]]: toBlock, [[ByteIndex]]: toIndex, [[ElementSize]]: 1, [[Payload]]: bytes } to eventList.
-                        ix. Else,
-                    1. Set toBlock[toIndex] to bytes[0].
-                    */
-                    ExceptionHelper.ThrowNotImplementedException();
-                }
-                else
-                {
-                    toBlock[toIndex] = fromBlock[fromIndex];
-                }
-
-                toIndex++;
-                fromIndex++;
-                count--;
-            }
-        }
     }
 }

+ 134 - 0
Jint/Native/BigInt/BigIntConstructor.cs

@@ -0,0 +1,134 @@
+using System.Numerics;
+using Jint.Collections;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.BigInt;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-properties-of-the-bigint-constructor
+/// </summary>
+public sealed class BigIntConstructor : FunctionInstance, IConstructor
+{
+    private static readonly JsString _functionName = new("BigInt");
+
+    public BigIntConstructor(
+        Engine engine,
+        Realm realm,
+        FunctionPrototype functionPrototype,
+        ObjectPrototype objectPrototype)
+        : base(engine, realm, _functionName)
+    {
+        _prototype = functionPrototype;
+        PrototypeObject = new BigIntPrototype(engine, realm, this, objectPrototype);
+        _length = new PropertyDescriptor(JsNumber.PositiveOne, PropertyFlag.Configurable);
+        _prototypeDescriptor = new PropertyDescriptor(PrototypeObject, PropertyFlag.AllForbidden);
+    }
+
+    protected override void Initialize()
+    {
+        var properties = new PropertyDictionary(2, checkExistingKeys: false)
+        {
+            ["asIntN"] = new(new ClrFunctionInstance(Engine, "asIntN", AsIntN, 2, PropertyFlag.Configurable), true, false, true),
+            ["asUintN"] = new(new ClrFunctionInstance(Engine, "asUintN", AsUintN, 2, PropertyFlag.Configurable), true, false, true),
+        };
+        SetProperties(properties);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-bigint.asintn
+    /// </summary>
+    private JsValue AsIntN(JsValue thisObj, JsValue[] arguments)
+    {
+        var bits = (int) TypeConverter.ToIndex(_realm, arguments.At(0));
+        var bigint = arguments.At(1).ToBigInteger(_engine);
+
+        var mod = TypeConverter.BigIntegerModulo(bigint, BigInteger.Pow(2, bits));
+        if (bits > 0 && mod >= BigInteger.Pow(2, bits - 1))
+        {
+            return (mod - BigInteger.Pow(2, bits));
+        }
+
+        return mod;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-bigint.asuintn
+    /// </summary>
+    private JsValue AsUintN(JsValue thisObj, JsValue[] arguments)
+    {
+        var bits = (int) TypeConverter.ToIndex(_realm, arguments.At(0));
+        var bigint = arguments.At(1).ToBigInteger(_engine);
+
+        var result = TypeConverter.BigIntegerModulo(bigint, BigInteger.Pow(2, bits));
+
+        return result;
+    }
+
+    public override JsValue Call(JsValue thisObject, JsValue[] arguments)
+    {
+        if (arguments.Length == 0)
+        {
+            return JsBigInt.Zero;
+        }
+
+        var prim = TypeConverter.ToPrimitive(arguments.At(0), Types.Number);
+        if (prim.IsNumber())
+        {
+            return NumberToBigInt((JsNumber) prim);
+        }
+
+        return prim.ToBigInteger(_engine);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-numbertobigint
+    /// </summary>
+    private JsBigInt NumberToBigInt(JsNumber value)
+    {
+        if (TypeConverter.IsIntegralNumber(value._value))
+        {
+            return JsBigInt.Create((long) value._value);
+        }
+
+        ExceptionHelper.ThrowRangeError(_realm, "The number " + value + " cannot be converted to a BigInt because it is not an integer");
+        return null;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-bigint-constructor-number-value
+    /// </summary>
+    public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
+    {
+        var value = arguments.Length > 0
+            ? JsBigInt.Create(arguments[0].ToBigInteger(_engine))
+            : JsBigInt.Zero;
+
+        if (newTarget.IsUndefined())
+        {
+            return Construct(value);
+        }
+
+        var o = OrdinaryCreateFromConstructor(
+            newTarget,
+            static intrinsics => intrinsics.BigInt.PrototypeObject,
+            static (engine, realm, state) => new BigIntInstance(engine, (JsBigInt) state), value);
+        return o;
+    }
+
+    public BigIntPrototype PrototypeObject { get; }
+
+    public BigIntInstance Construct(JsBigInt value)
+    {
+        var instance = new BigIntInstance(Engine)
+        {
+            _prototype = PrototypeObject,
+            BigIntData = value
+        };
+
+        return instance;
+    }
+}

+ 24 - 0
Jint/Native/BigInt/BigIntInstance.cs

@@ -0,0 +1,24 @@
+using Jint.Native.Object;
+using Jint.Runtime;
+
+namespace Jint.Native.BigInt;
+
+public sealed class BigIntInstance : ObjectInstance, IPrimitiveInstance
+{
+    public BigIntInstance(Engine engine)
+        : base(engine, ObjectClass.Object)
+    {
+    }
+
+    public BigIntInstance(Engine engine, JsBigInt value)
+        : this(engine)
+    {
+        BigIntData = value;
+    }
+
+    Types IPrimitiveInstance.Type => Types.BigInt;
+
+    JsValue IPrimitiveInstance.PrimitiveValue => BigIntData;
+
+    public JsBigInt BigIntData { get; internal init; }
+}

+ 153 - 0
Jint/Native/BigInt/BigIntPrototype.cs

@@ -0,0 +1,153 @@
+using System.Numerics;
+using Jint.Collections;
+using Jint.Native.Object;
+using Jint.Native.Symbol;
+using Jint.Pooling;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Native.BigInt;
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-properties-of-the-bigint-prototype-object
+/// </summary>
+public sealed class BigIntPrototype : ObjectInstance
+{
+    private readonly Realm _realm;
+    private readonly BigIntConstructor _constructor;
+
+    internal BigIntPrototype(
+        Engine engine,
+        Realm realm,
+        BigIntConstructor constructor,
+        ObjectPrototype objectPrototype)
+        : base(engine)
+    {
+        _prototype = objectPrototype;
+        _realm = realm;
+        _constructor = constructor;
+    }
+
+    protected override void Initialize()
+    {
+        var properties = new PropertyDictionary(4, checkExistingKeys: false)
+        {
+            ["constructor"] = new(_constructor, true, false, true),
+            ["toString"] = new(new ClrFunctionInstance(Engine, "toString", ToBigIntString, 0, PropertyFlag.Configurable), true, false, true),
+            ["toLocaleString"] = new(new ClrFunctionInstance(Engine, "toLocaleString", ToLocaleString, 0, PropertyFlag.Configurable), true, false, true),
+            ["valueOf"] = new(new ClrFunctionInstance(Engine, "valueOf", ValueOf, 0, PropertyFlag.Configurable), true, false, true),
+        };
+        SetProperties(properties);
+
+        var symbols = new SymbolDictionary(1)
+        {
+            [GlobalSymbolRegistry.ToStringTag] = new("BigInt", false, false, true)
+        };
+        SetSymbols(symbols);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-bigint.prototype.tolocalestring
+    /// </summary>
+    private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments)
+    {
+        if (!thisObject.IsBigInt() && thisObject is not BigIntInstance)
+        {
+            ExceptionHelper.ThrowTypeError(_realm);
+        }
+
+        var m = TypeConverter.ToBigInt(thisObject);
+        return m.ToString("R");
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-bigint.prototype.valueof
+    /// </summary>
+    private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
+    {
+        if (thisObj is BigIntInstance ni)
+        {
+            return ni.BigIntData;
+        }
+
+        if (thisObj is JsBigInt)
+        {
+            return thisObj;
+        }
+
+        ExceptionHelper.ThrowTypeError(_realm);
+        return null;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-bigint.prototype.tostring
+    /// </summary>
+    private JsValue ToBigIntString(JsValue thisObject, JsValue[] arguments)
+    {
+        var x = ThisBigIntValue(thisObject);
+
+        var radix = arguments.At(0);
+
+        var radixMV = radix.IsUndefined()
+            ? 10
+            : (int) TypeConverter.ToIntegerOrInfinity(radix);
+
+        if (radixMV is < 2 or > 36)
+        {
+            ExceptionHelper.ThrowRangeError(_realm, "radix must be between 2 and 36");
+        }
+
+        var value = x._value;
+        if (value == BigInteger.Zero)
+        {
+            return JsString.NumberZeroString;
+        }
+
+        if (radixMV == 10)
+        {
+            return value.ToString("R");
+        }
+
+        var negative = value < 0;
+
+        if (negative)
+        {
+            value = -value;
+        }
+
+        const string digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+        using var builder = StringBuilderPool.Rent();
+        var sb = builder.Builder;
+
+        for (; value > 0; value /= radixMV)
+        {
+            var d = (int) (value % radixMV);
+            sb.Append(digits[d]);
+        }
+
+        var charArray = new char[sb.Length];
+        sb.CopyTo(0, charArray, 0, charArray.Length);
+        System.Array.Reverse(charArray);
+
+        return (negative ? "-" : "") + new string(charArray);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#thisbigintvalue
+    /// </summary>
+    private JsBigInt ThisBigIntValue(JsValue value)
+    {
+        switch (value)
+        {
+            case JsBigInt bigInt:
+                return bigInt;
+            case BigIntInstance bigIntInstance:
+                return bigIntInstance.BigIntData;
+            default:
+                ExceptionHelper.ThrowTypeError(_realm);
+                return JsBigInt.One;
+        }
+    }
+}

+ 1 - 1
Jint/Native/Boolean/BooleanConstructor.cs

@@ -60,7 +60,7 @@ namespace Jint.Native.Boolean
             var instance = new BooleanInstance(Engine)
             {
                 _prototype = PrototypeObject,
-                PrimitiveValue = value,
+                BooleanData = value,
             };
 
             return instance;

+ 4 - 4
Jint/Native/Boolean/BooleanInstance.cs

@@ -9,17 +9,17 @@ namespace Jint.Native.Boolean
             : base(engine, ObjectClass.Boolean)
         {
         }
-        
+
         public BooleanInstance(Engine engine, JsBoolean value)
             : base(engine, ObjectClass.Boolean)
         {
-            PrimitiveValue = value;
+            BooleanData = value;
         }
 
         Types IPrimitiveInstance.Type => Types.Boolean;
 
-        JsValue IPrimitiveInstance.PrimitiveValue => PrimitiveValue;
+        JsValue IPrimitiveInstance.PrimitiveValue => BooleanData;
 
-        public JsValue PrimitiveValue { get; set; }
+        public JsValue BooleanData { get; internal init; }
     }
 }

+ 2 - 2
Jint/Native/Boolean/BooleanPrototype.cs

@@ -21,7 +21,7 @@ namespace Jint.Native.Boolean
             ObjectPrototype objectPrototype) : base(engine)
         {
             _prototype = objectPrototype;
-            PrimitiveValue = JsBoolean.False;
+            BooleanData = JsBoolean.False;
             _realm = realm;
             _constructor = constructor;
         }
@@ -46,7 +46,7 @@ namespace Jint.Native.Boolean
 
             if (thisObj is BooleanInstance bi)
             {
-                return bi.PrimitiveValue;
+                return bi.BooleanData;
             }
 
             ExceptionHelper.ThrowTypeError(_realm);

+ 3 - 5
Jint/Native/DataView/DataViewPrototype.cs

@@ -246,7 +246,7 @@ namespace Jint.Native.DataView
             }
 
             var bufferIndex = (int) (getIndex + viewOffset);
-            return buffer.GetValueFromBuffer(bufferIndex, type, false, ArrayBufferOrder.Unordered, isLittleEndianBoolean);
+            return buffer.GetValueFromBuffer(bufferIndex, type, false, ArrayBufferOrder.Unordered, isLittleEndianBoolean).ToJsValue();
         }
 
         /// <summary>
@@ -267,12 +267,10 @@ namespace Jint.Native.DataView
 
             var getIndex = TypeConverter.ToIndex(_realm, requestIndex);
 
-            double numberValue;
+            TypedArrayValue numberValue;
             if (type.IsBigIntElementType())
             {
-                // let numberValue be ? ToBigInt(value).
-                ExceptionHelper.ThrowNotImplementedException("BigInt not implemented");
-                return Undefined;
+                numberValue = TypeConverter.ToBigInt(value);
             }
             else
             {

+ 2 - 1
Jint/Native/Global/GlobalObject.cs

@@ -33,7 +33,7 @@ namespace Jint.Native.Global
             const PropertyFlag lengthFlags = PropertyFlag.Configurable;
             const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
 
-            var properties = new PropertyDictionary(54, checkExistingKeys: false)
+            var properties = new PropertyDictionary(55, checkExistingKeys: false)
             {
                 ["Object"] = new PropertyDescriptor(_realm.Intrinsics.Object, propertyFlags),
                 ["Function"] = new PropertyDescriptor(_realm.Intrinsics.Function, propertyFlags),
@@ -61,6 +61,7 @@ namespace Jint.Native.Global
                 ["String"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.String, propertyFlags),
                 ["RegExp"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.RegExp, propertyFlags),
                 ["Number"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Number, propertyFlags),
+                ["BigInt"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.BigInt, propertyFlags),
                 ["Boolean"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Boolean, propertyFlags),
                 ["Date"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Date, propertyFlags),
                 ["Math"] = new LazyPropertyDescriptor(this, static state => ((GlobalObject) state)._realm.Intrinsics.Math, propertyFlags),

+ 105 - 0
Jint/Native/JsBigInt.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Numerics;
+using Jint.Runtime;
+
+namespace Jint.Native;
+
+public sealed class JsBigInt : JsValue, IEquatable<JsBigInt>
+{
+    internal readonly BigInteger _value;
+
+    public static readonly JsBigInt Zero = new(0);
+    public static readonly JsBigInt One = new(1);
+
+    private static readonly JsBigInt[] _bigIntegerToJsValue;
+
+    static JsBigInt()
+    {
+        var bigIntegers = new JsBigInt[1024];
+        for (uint i = 0; i < bigIntegers.Length; i++)
+        {
+            bigIntegers[i] = new JsBigInt(i);
+        }
+        _bigIntegerToJsValue = bigIntegers;
+    }
+
+    public JsBigInt(BigInteger value) : base(Types.BigInt)
+    {
+        _value = value;
+    }
+
+    internal static JsBigInt Create(BigInteger bigInt)
+    {
+        var temp = _bigIntegerToJsValue;
+        if (bigInt >= 0 && bigInt < (uint) temp.Length)
+        {
+            return temp[(int) bigInt];
+        }
+
+        return new JsBigInt(bigInt);
+    }
+
+    internal static JsBigInt Create(JsValue value)
+    {
+        return value is JsBigInt jsBigInt
+            ? jsBigInt
+            : Create(TypeConverter.ToBigInt(value));
+    }
+
+
+    public override object ToObject()
+    {
+        return _value;
+    }
+
+    public static bool operator ==(JsBigInt a, double b)
+    {
+        return a is not null && TypeConverter.IsIntegralNumber(b) && a._value == (long) b;
+    }
+
+    public static bool operator !=(JsBigInt a, double b)
+    {
+        return !(a == b);
+    }
+
+    public override string ToString()
+    {
+        return TypeConverter.ToString(_value);
+    }
+
+    public override bool IsLooselyEqual(JsValue value)
+    {
+        if (value is JsBigInt bigInt)
+        {
+            return Equals(bigInt);
+        }
+
+        return value is JsNumber jsNumber && TypeConverter.IsIntegralNumber(jsNumber._value) && _value == new BigInteger(jsNumber._value)
+               || value is JsString jsString && TypeConverter.TryStringToBigInt(jsString.ToString(), out var temp) && temp == _value;
+    }
+
+    public override bool Equals(object other)
+    {
+        return Equals(other as JsBigInt);
+    }
+
+    public override bool Equals(JsValue other)
+    {
+        return Equals(other as JsBigInt);
+    }
+
+    public bool Equals(JsBigInt other)
+    {
+        if (ReferenceEquals(null, other))
+        {
+            return false;
+        }
+
+        return ReferenceEquals(this, other) || _value.Equals(other._value);
+    }
+
+    public override int GetHashCode()
+    {
+        return _value.GetHashCode();
+    }
+}

+ 16 - 14
Jint/Native/JsBoolean.cs

@@ -1,4 +1,6 @@
-using System;
+#nullable enable
+
+using System;
 using Jint.Runtime;
 
 namespace Jint.Native
@@ -28,31 +30,31 @@ namespace Jint.Native
             return _value ? "true" : "false";
         }
 
-        public override bool Equals(JsValue obj)
+        public override bool IsLooselyEqual(JsValue value)
         {
-            if (ReferenceEquals(null, obj))
+            if (value is JsBoolean jsBoolean)
             {
-                return false;
+                return Equals(jsBoolean);
             }
 
-            if (!(obj is JsBoolean number))
-            {
-                return false;
-            }
+            return !value.IsNullOrUndefined() && base.IsLooselyEqual(value);
+        }
 
-            return Equals(number);
+        public override bool Equals(JsValue obj)
+        {
+            return Equals(obj as JsBoolean);
         }
 
-        public bool Equals(JsBoolean other)
+        public bool Equals(JsBoolean? other)
         {
-            if (ReferenceEquals(null, other))
+            if (ReferenceEquals(this, other))
             {
-                return false;
+                return true;
             }
 
-            if (ReferenceEquals(this, other))
+            if (other is null)
             {
-                return true;
+                return false;
             }
 
             return _value == other._value;

+ 12 - 10
Jint/Native/JsNull.cs

@@ -1,4 +1,6 @@
-using System;
+#nullable enable
+
+using System;
 using Jint.Runtime;
 
 namespace Jint.Native
@@ -9,7 +11,7 @@ namespace Jint.Native
         {
         }
 
-        public override object ToObject()
+        public override object? ToObject()
         {
             return null;
         }
@@ -19,19 +21,19 @@ namespace Jint.Native
             return "null";
         }
 
-        public override bool Equals(JsValue obj)
+        public override bool IsLooselyEqual(JsValue value)
         {
-            if (ReferenceEquals(this, obj))
-            {
-                return true;
-            }
+            return ReferenceEquals(Null, value) || ReferenceEquals(Undefined, value);
+        }
 
-            return obj is JsNull s && Equals(s);
+        public override bool Equals(JsValue obj)
+        {
+            return Equals(obj as JsNull);
         }
 
-        public bool Equals(JsNull other)
+        public bool Equals(JsNull? other)
         {
-            return !ReferenceEquals(null, other);
+            return other is not null;
         }
     }
 }

+ 72 - 11
Jint/Native/JsNumber.cs

@@ -1,5 +1,9 @@
-using System;
+#nullable enable
+
+using System;
+using System.Numerics;
 using System.Runtime.CompilerServices;
+using Jint.Native.Number;
 using Jint.Runtime;
 
 namespace Jint.Native
@@ -96,8 +100,7 @@ namespace Jint.Native
         {
             // we expect zero to be on the fast path of integer mostly
             var temp = _intToJsValue;
-            if (value >= 1 && value < temp.Length
-                && System.Math.Abs(value % 1) <= DoubleIsIntegerTolerance)
+            if (value >= 1 && value < temp.Length && TypeConverter.IsIntegralNumber(value))
             {
                 return temp[(uint) value];
             }
@@ -198,29 +201,87 @@ namespace Jint.Native
             return new JsNumber(value);
         }
 
+        public static JsNumber Create(JsValue jsValue)
+        {
+            if (jsValue is JsNumber number)
+            {
+                return number;
+            }
+
+            return Create(TypeConverter.ToNumber(jsValue));
+        }
+
         public override string ToString()
         {
             return TypeConverter.ToString(_value);
         }
 
-        public override bool Equals(JsValue obj)
+        internal bool IsNaN()
+        {
+            return double.IsNaN(_value);
+        }
+
+        /// <summary>
+        /// Either positive or negative zero.
+        /// </summary>
+        internal bool IsZero()
+        {
+            return IsNegativeZero() || IsPositiveZero();
+        }
+
+        internal bool IsNegativeZero()
+        {
+            return NumberInstance.IsNegativeZero(_value);
+        }
+
+        internal bool IsPositiveZero()
+        {
+            return NumberInstance.IsPositiveZero(_value);
+        }
+
+        internal bool IsPositiveInfinity()
         {
-            if (ReferenceEquals(null, obj))
+            return double.IsPositiveInfinity(_value);
+        }
+
+        internal bool IsNegativeInfinity()
+        {
+            return double.IsNegativeInfinity(_value);
+        }
+
+        public override bool IsLooselyEqual(JsValue value)
+        {
+            if (value is JsNumber jsNumber)
             {
-                return false;
+                return Equals(jsNumber);
             }
 
-            if (!(obj is JsNumber number))
+            if (value.IsBigInt())
             {
-                return false;
+                return TypeConverter.IsIntegralNumber(_value) && new BigInteger(_value) == value.AsBigInt();
             }
 
-            return Equals(number);
+            return base.IsLooselyEqual(value);
         }
 
-        public bool Equals(JsNumber other)
+        public override bool Equals(JsValue obj)
         {
-            if (ReferenceEquals(null, other))
+            return Equals(obj as JsNumber);
+        }
+
+        public override bool Equals(object? obj)
+        {
+            return Equals(obj as JsNumber);
+        }
+
+        public bool Equals(JsNumber? other)
+        {
+            if (other is null)
+            {
+                return false;
+            }
+
+            if (double.IsNaN(_value) || double.IsNaN(other._value))
             {
                 return false;
             }

+ 47 - 27
Jint/Native/JsString.cs

@@ -1,4 +1,6 @@
-using System;
+#nullable enable
+
+using System;
 using System.Text;
 using Jint.Native.Array;
 using Jint.Runtime;
@@ -20,6 +22,7 @@ namespace Jint.Native
         internal static readonly JsString BooleanString = new JsString("boolean");
         internal static readonly JsString StringString = new JsString("string");
         internal static readonly JsString NumberString = new JsString("number");
+        internal static readonly JsString BigIntString = new JsString("bigint");
         internal static readonly JsString SymbolString = new JsString("symbol");
         internal static readonly JsString DefaultString = new JsString("default");
         internal static readonly JsString NumberZeroString = new JsString("0");
@@ -68,34 +71,34 @@ namespace Jint.Native
             _value = value.ToString();
         }
 
-        public static bool operator ==(JsValue a, JsString b)
+        public static bool operator ==(JsValue? a, JsString? b)
         {
-            if (a is JsString s && b is object)
+            if (a is JsString s && b is not null)
             {
                 return s.ToString() == b.ToString();
             }
 
-            if ((object) a == null)
+            if (a is null)
             {
-                return (object) b == null;
+                return b is null;
             }
 
-            return (object) b != null && a.Equals(b);
+            return b is not null && a.Equals(b);
         }
 
-        public static bool operator ==(JsString a, JsValue b)
+        public static bool operator ==(JsString? a, JsValue? b)
         {
-            if (a is object && b is JsString s)
+            if (a is not null && b is JsString s)
             {
                 return s.ToString() == b.ToString();
             }
 
-            if ((object) a == null)
+            if (a is null)
             {
-                return (object) b == null;
+                return b is null;
             }
 
-            return (object) b != null && a.Equals(b);
+            return b is not null && a.Equals(b);
         }
 
         public static bool operator !=(JsString a, JsValue b)
@@ -225,37 +228,42 @@ namespace Jint.Native
 
         public override bool Equals(JsValue obj)
         {
-            if (ReferenceEquals(null, obj))
+            return Equals(obj as JsString);
+        }
+
+        public bool Equals(JsString? other)
+        {
+            if (other is null)
             {
                 return false;
             }
 
-            if (!(obj is JsString s))
+            if (ReferenceEquals(this, other))
             {
-                return false;
+                return true;
             }
 
-            return Equals(s);
+            return _value == other.ToString();
         }
 
-        public bool Equals(JsString other)
+        public override bool IsLooselyEqual(JsValue value)
         {
-            if (ReferenceEquals(null, other))
+            if (value is JsString jsString)
             {
-                return false;
+                return Equals(jsString);
             }
 
-            if (ReferenceEquals(this, other))
+            if (value.IsBigInt())
             {
-                return true;
+                return value.IsBigInt() && TypeConverter.TryStringToBigInt(ToString(), out var temp) && temp == value.AsBigInt();
             }
 
-            return _value == other.ToString();
+            return base.IsLooselyEqual(value);
         }
 
         public override bool Equals(object obj)
         {
-            return ReferenceEquals(this, obj) || obj is JsString other && Equals(other);
+            return Equals(obj as JsString);
         }
 
         public override int GetHashCode()
@@ -265,7 +273,7 @@ namespace Jint.Native
 
         internal sealed class ConcatenatedString : JsString
         {
-            private StringBuilder _stringBuilder;
+            private StringBuilder? _stringBuilder;
             private bool _dirty;
 
             internal ConcatenatedString(string value, int capacity = 0)
@@ -285,7 +293,7 @@ namespace Jint.Native
             {
                 if (_dirty)
                 {
-                    _value = _stringBuilder.ToString();
+                    _value = _stringBuilder!.ToString();
                     _dirty = false;
                 }
 
@@ -310,7 +318,7 @@ namespace Jint.Native
 
             internal override JsString EnsureCapacity(int capacity)
             {
-                _stringBuilder.EnsureCapacity(capacity);
+                _stringBuilder!.EnsureCapacity(capacity);
                 return this;
             }
 
@@ -328,9 +336,21 @@ namespace Jint.Native
             {
                 if (other is ConcatenatedString cs)
                 {
-                    if (_stringBuilder != null && cs._stringBuilder != null)
+                    var stringBuilder = _stringBuilder;
+                    var csStringBuilder = cs._stringBuilder;
+
+                    // we cannot use StringBuilder.Equals as it also checks Capacity on full framework / pre .NET Core 3
+                    if (stringBuilder != null && csStringBuilder != null && stringBuilder.Length == csStringBuilder.Length)
                     {
-                        return _stringBuilder.Equals(cs._stringBuilder);
+                        for (var i = 0; i < stringBuilder.Length; ++i)
+                        {
+                            if (stringBuilder[i] != csStringBuilder[i])
+                            {
+                                return false;
+                            }
+                        }
+
+                        return true;
                     }
 
                     return ToString() == cs.ToString();

+ 6 - 6
Jint/Native/JsUndefined.cs

@@ -19,14 +19,14 @@ namespace Jint.Native
             return "undefined";
         }
 
-        public override bool Equals(JsValue obj)
+        public override bool IsLooselyEqual(JsValue value)
         {
-            if (ReferenceEquals(this, obj))
-            {
-                return true;
-            }
+            return ReferenceEquals(Undefined, value) || ReferenceEquals(Null, value);
+        }
 
-            return obj is JsUndefined s && Equals(s);
+        public override bool Equals(JsValue obj)
+        {
+            return Equals(obj as JsUndefined);
         }
 
         public bool Equals(JsUndefined other)

+ 67 - 26
Jint/Native/JsValue.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Diagnostics;
 using System.Diagnostics.Contracts;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 using Jint.Native.Generator;
 using Jint.Native.Iterator;
@@ -213,32 +214,17 @@ namespace Jint.Native
 
         public static bool operator ==(JsValue a, JsValue b)
         {
-            if ((object) a == null)
+            if (a is null)
             {
-                return (object) b == null;
+                return b is null;
             }
 
-            return (object) b != null && a.Equals(b);
+            return b is not null && a.Equals(b);
         }
 
         public static bool operator !=(JsValue a, JsValue b)
         {
-            if ((object) a == null)
-            {
-                if ((object) b == null)
-                {
-                    return false;
-                }
-
-                return true;
-            }
-
-            if ((object) b == null)
-            {
-                return true;
-            }
-
-            return !a.Equals(b);
+            return !(a == b);
         }
 
         public static implicit operator JsValue(char value)
@@ -271,6 +257,11 @@ namespace Jint.Native
             return JsNumber.Create(value);
         }
 
+        public static implicit operator JsValue(BigInteger value)
+        {
+            return JsBigInt.Create(value);
+        }
+
         public static implicit operator JsValue(bool value)
         {
             return value ? JsBoolean.True : JsBoolean.False;
@@ -287,22 +278,69 @@ namespace Jint.Native
             return JsString.Create(value);
         }
 
-        public override bool Equals(object obj)
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-islooselyequal
+        /// </summary>
+        public virtual bool IsLooselyEqual(JsValue value)
         {
-            if (ReferenceEquals(null, obj))
+            if (ReferenceEquals(this, value))
             {
-                return false;
+                return true;
             }
 
-            if (ReferenceEquals(this, obj))
+            var x = this;
+            var y = value;
+
+            if (x.IsNumber() && y.IsString())
             {
-                return true;
+                return x.IsLooselyEqual(TypeConverter.ToNumber(y));
+            }
+
+            if (x.IsString() && y.IsNumber())
+            {
+                return y.IsLooselyEqual(TypeConverter.ToNumber(x));
             }
 
-            return obj is JsValue value && Equals(value);
+            if (x.IsBoolean())
+            {
+                return y.IsLooselyEqual(TypeConverter.ToNumber(x));
+            }
+
+            if (y.IsBoolean())
+            {
+                return x.IsLooselyEqual(TypeConverter.ToNumber(y));
+            }
+
+            const InternalTypes stringOrNumber = InternalTypes.String | InternalTypes.Integer | InternalTypes.Number | InternalTypes.BigInt;
+
+            if (y.IsObject() && (x._type & stringOrNumber) != 0)
+            {
+                return x.IsLooselyEqual(TypeConverter.ToPrimitive(y));
+            }
+
+            if (x.IsObject() && (y._type & stringOrNumber) != 0)
+            {
+                return y.IsLooselyEqual(TypeConverter.ToPrimitive(x));
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Strict equality.
+        /// </summary>
+        public override bool Equals(object obj)
+        {
+            return Equals(obj as JsValue);
         }
 
-        public abstract bool Equals(JsValue other);
+        /// <summary>
+        /// Strict equality.
+        /// </summary>
+        public virtual bool Equals(JsValue other)
+        {
+            return ReferenceEquals(this, other);
+        }
 
         public override int GetHashCode()
         {
@@ -335,6 +373,9 @@ namespace Jint.Native
                     case Types.Number:
                         Value = ((JsNumber) value)._value + " (number)";
                         break;
+                    case Types.BigInt:
+                        Value = ((JsBigInt) value)._value + " (bigint)";
+                        break;
                     case Types.Object:
                         Value = value.AsObject().GetType().Name;
                         break;

+ 55 - 24
Jint/Native/Json/JsonSerializer.cs

@@ -1,8 +1,12 @@
 using System.Collections.Generic;
 using System.Linq;
 using Jint.Collections;
+using Jint.Native.BigInt;
+using Jint.Native.Boolean;
 using Jint.Native.Global;
+using Jint.Native.Number;
 using Jint.Native.Object;
+using Jint.Native.String;
 using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
@@ -18,6 +22,8 @@ namespace Jint.Native.Json
         private List<JsValue> _propertyList;
         private JsValue _replacerFunction = Undefined.Instance;
 
+        private static readonly JsString toJsonProperty = new("toJSON");
+
         public JsonSerializer(Engine engine)
         {
             _engine = engine;
@@ -117,15 +123,23 @@ namespace Jint.Native.Json
             var wrapper = _engine.Realm.Intrinsics.Object.Construct(Arguments.Empty);
             wrapper.DefineOwnProperty(JsString.Empty, new PropertyDescriptor(value, PropertyFlag.ConfigurableEnumerableWritable));
 
-            return Str(JsString.Empty, wrapper);
+            return SerializeJSONProperty(JsString.Empty, wrapper);
         }
 
-        private JsValue Str(JsValue key, JsValue holder)
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-serializejsonproperty
+        /// </summary>
+        private JsValue SerializeJSONProperty(JsValue key, JsValue holder)
         {
             var value = holder.Get(key, holder);
-            if (value.IsObject())
+            var isBigInt = value is BigIntInstance || value.IsBigInt();
+            if (value.IsObject() || isBigInt)
             {
-                var toJson = value.AsObject().Get("toJSON", value);
+                var toJson = value.Get(toJsonProperty, value);
+                if (toJson.IsUndefined() && isBigInt)
+                {
+                    toJson = _engine.Realm.Intrinsics.BigInt.PrototypeObject.Get(toJsonProperty);
+                }
                 if (toJson.IsObject())
                 {
                     if (toJson.AsObject() is ICallable callableToJson)
@@ -135,30 +149,33 @@ namespace Jint.Native.Json
                 }
             }
 
-            if (!ReferenceEquals(_replacerFunction, Undefined.Instance))
+            if (!_replacerFunction.IsUndefined())
             {
-                var replacerFunctionCallable = (ICallable)_replacerFunction.AsObject();
+                var replacerFunctionCallable = (ICallable) _replacerFunction.AsObject();
                 value = replacerFunctionCallable.Call(holder, Arguments.From(key, value));
             }
 
             if (value.IsObject())
             {
                 var valueObj = value.AsObject();
-                switch (valueObj.Class)
+                switch (valueObj)
                 {
-                    case ObjectClass.Number:
-                        value = TypeConverter.ToNumber(value);
+                    case NumberInstance numberInstance:
+                        value = numberInstance.NumberData;
                         break;
-                    case ObjectClass.String:
-                        value = TypeConverter.ToString(value);
+                    case StringInstance stringInstance:
+                        value = stringInstance.StringData;
                         break;
-                    case ObjectClass.Boolean:
-                        value = TypeConverter.ToPrimitive(value);
+                    case BooleanInstance booleanInstance:
+                        value = booleanInstance.BooleanData;
+                        break;
+                    case BigIntInstance bigIntInstance:
+                        value = bigIntInstance.BigIntData;
                         break;
                     default:
                         value = SerializesAsArray(value)
-                            ? SerializeArray(value)
-                            : SerializeObject(value.AsObject());
+                            ? SerializeJSONArray(value)
+                            : SerializeJSONObject(value.AsObject());
                         return value;
                 }
             }
@@ -175,7 +192,7 @@ namespace Jint.Native.Json
 
             if (value.IsString())
             {
-                return Quote(value.ToString());
+                return QuoteJSONString(value.ToString());
             }
 
             if (value.IsNumber())
@@ -189,13 +206,18 @@ namespace Jint.Native.Json
                 return JsString.NullString;
             }
 
+            if (value.IsBigInt())
+            {
+                ExceptionHelper.ThrowTypeError(_engine.Realm, "Do not know how to serialize a BigInt");
+            }
+
             var isCallable = value.IsObject() && value.AsObject() is ICallable;
 
             if (value.IsObject() && isCallable == false)
             {
                 return SerializesAsArray(value)
-                    ? SerializeArray(value)
-                    : SerializeObject(value.AsObject());
+                    ? SerializeJSONArray(value)
+                    : SerializeJSONObject(value.AsObject());
             }
 
             return JsValue.Undefined;
@@ -206,7 +228,10 @@ namespace Jint.Native.Json
             return value.AsObject().Class == ObjectClass.Array || value is ObjectWrapper { IsArrayLike: true };
         }
 
-        private static string Quote(string value)
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-quotejsonstring
+        /// </summary>
+        private static string QuoteJSONString(string value)
         {
             using var stringBuilder = StringBuilderPool.Rent();
             var sb = stringBuilder.Builder;
@@ -253,7 +278,10 @@ namespace Jint.Native.Json
             return sb.ToString();
         }
 
-        private string SerializeArray(JsValue value)
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-serializejsonarray
+        /// </summary>
+        private string SerializeJSONArray(JsValue value)
         {
             _stack.Enter(value);
             var stepback = _indent;
@@ -262,7 +290,7 @@ namespace Jint.Native.Json
             var len = TypeConverter.ToUint32(value.Get(CommonProperties.Length, value));
             for (int i = 0; i < len; i++)
             {
-                var strP = Str(i, value);
+                var strP = SerializeJSONProperty(i, value);
                 if (strP.IsUndefined())
                 {
                     strP = JsString.NullString;
@@ -295,7 +323,10 @@ namespace Jint.Native.Json
             return final;
         }
 
-        private string SerializeObject(ObjectInstance value)
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-serializejsonobject
+        /// </summary>
+        private string SerializeJSONObject(ObjectInstance value)
         {
             string final;
 
@@ -310,10 +341,10 @@ namespace Jint.Native.Json
             var partial = new List<string>();
             foreach (var p in k)
             {
-                var strP = Str(p, value);
+                var strP = SerializeJSONProperty(p, value);
                 if (!strP.IsUndefined())
                 {
-                    var member = Quote(p.ToString()) + ":";
+                    var member = QuoteJSONString(p.ToString()) + ":";
                     if (_gap != "")
                     {
                         member += " ";

+ 1 - 1
Jint/Native/Map/MapInstance.cs

@@ -14,7 +14,7 @@ namespace Jint.Native.Map
             : base(engine, objectClass: ObjectClass.Map)
         {
             _realm = realm;
-            _map = new OrderedDictionary<JsValue, JsValue>();
+            _map = new OrderedDictionary<JsValue, JsValue>(SameValueZeroComparer.Instance);
         }
 
         public override PropertyDescriptor GetOwnProperty(JsValue property)

+ 27 - 11
Jint/Native/Number/NumberConstructor.cs

@@ -8,6 +8,9 @@ using Jint.Runtime.Interop;
 
 namespace Jint.Native.Number
 {
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-number-constructor
+    /// </summary>
     public sealed class NumberConstructor : FunctionInstance, IConstructor
     {
         private static readonly JsString _functionName = new JsString("Number");
@@ -111,12 +114,8 @@ namespace Jint.Native.Number
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
-            if (arguments.Length == 0)
-            {
-                return 0d;
-            }
-
-            return TypeConverter.ToNumber(arguments[0]);
+            var n = ProcessFirstParameter(arguments);
+            return n;
         }
 
         /// <summary>
@@ -124,22 +123,39 @@ namespace Jint.Native.Number
         /// </summary>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            var value = arguments.Length > 0
-                ? JsNumber.Create(TypeConverter.ToNumber(arguments[0]))
-                : JsNumber.PositiveZero;
+            var n = ProcessFirstParameter(arguments);
 
             if (newTarget.IsUndefined())
             {
-                return Construct(value);
+                return Construct(n);
             }
 
             var o = OrdinaryCreateFromConstructor(
                 newTarget,
                 static intrinsics => intrinsics.Number.PrototypeObject,
-                static (engine, realm, state) => new NumberInstance(engine, (JsNumber) state), value);
+                static (engine, realm, state) => new NumberInstance(engine, (JsNumber) state), n);
             return o;
         }
 
+        private static JsNumber ProcessFirstParameter(JsValue[] arguments)
+        {
+            var n = JsNumber.PositiveZero;
+            if (arguments.Length > 0)
+            {
+                var prim = TypeConverter.ToNumeric(arguments[0]);
+                if (prim.IsBigInt())
+                {
+                    n = JsNumber.Create((long) ((JsBigInt) prim)._value);
+                }
+                else
+                {
+                    n = (JsNumber) prim;
+                }
+            }
+
+            return n;
+        }
+
         public NumberPrototype PrototypeObject { get; private set; }
 
         public NumberInstance Construct(JsNumber value)

+ 1 - 1
Jint/Native/Number/NumberInstance.cs

@@ -24,7 +24,7 @@ namespace Jint.Native.Number
 
         JsValue IPrimitiveInstance.PrimitiveValue => NumberData;
 
-        public JsNumber NumberData { get; set; }
+        public JsNumber NumberData { get; internal init; }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool IsNegativeZero(double x)

+ 1 - 1
Jint/Native/Number/NumberPrototype.cs

@@ -12,7 +12,7 @@ using Jint.Runtime.Interop;
 namespace Jint.Native.Number
 {
     /// <summary>
-    /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4
+    /// https://tc39.es/ecma262/#sec-properties-of-the-number-prototype-object
     /// </summary>
     public sealed class NumberPrototype : NumberInstance
     {

+ 5 - 16
Jint/Native/Object/ObjectInstance.cs

@@ -16,7 +16,6 @@ using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
-using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Native.Object
 {
@@ -724,7 +723,7 @@ namespace Jint.Native.Object
                 current.Enumerable == desc.Enumerable && current.EnumerableSet == desc.EnumerableSet &&
                 ((ReferenceEquals(currentGet, null) && ReferenceEquals(descGet, null)) || (!ReferenceEquals(currentGet, null) && !ReferenceEquals(descGet, null) && SameValue(currentGet, descGet))) &&
                 ((ReferenceEquals(currentSet, null) && ReferenceEquals(descSet, null)) || (!ReferenceEquals(currentSet, null) && !ReferenceEquals(descSet, null) && SameValue(currentSet, descSet))) &&
-                ((ReferenceEquals(currentValue, null) && ReferenceEquals(descValue, null)) || (!ReferenceEquals(currentValue, null) && !ReferenceEquals(descValue, null) && JintBinaryExpression.StrictlyEqual(currentValue, descValue)))
+                ((ReferenceEquals(currentValue, null) && ReferenceEquals(descValue, null)) || (!ReferenceEquals(currentValue, null) && !ReferenceEquals(descValue, null) && currentValue == descValue))
             )
             {
                 return true;
@@ -924,7 +923,7 @@ namespace Jint.Native.Object
                 case ObjectClass.String:
                     if (this is StringInstance stringInstance)
                     {
-                        converted = stringInstance.PrimitiveValue.ToString();
+                        converted = stringInstance.StringData.ToString();
                     }
                     break;
 
@@ -938,7 +937,7 @@ namespace Jint.Native.Object
                 case ObjectClass.Boolean:
                     if (this is BooleanInstance booleanInstance)
                     {
-                        converted = ((JsBoolean) booleanInstance.PrimitiveValue)._value
+                        converted = ((JsBoolean) booleanInstance.BooleanData)._value
                             ? JsBoolean.BoxedTrue
                             : JsBoolean.BoxedFalse;
                     }
@@ -1317,22 +1316,12 @@ namespace Jint.Native.Object
 
         public override bool Equals(JsValue obj)
         {
-            if (ReferenceEquals(null, obj))
-            {
-                return false;
-            }
-
-            if (!(obj is ObjectInstance s))
-            {
-                return false;
-            }
-
-            return Equals(s);
+            return Equals(obj as ObjectInstance);
         }
 
         public bool Equals(ObjectInstance other)
         {
-            if (ReferenceEquals(null, other))
+            if (other is null)
             {
                 return false;
             }

+ 25 - 0
Jint/Native/SameValueZeroComparer.cs

@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+
+namespace Jint.Native;
+
+internal sealed class SameValueZeroComparer : IEqualityComparer<JsValue>
+{
+    public static readonly SameValueZeroComparer Instance = new();
+
+    bool IEqualityComparer<JsValue>.Equals(JsValue x, JsValue y)
+    {
+        return Equals(x, y);
+    }
+
+    public int GetHashCode(JsValue obj)
+    {
+        return obj.GetHashCode();
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    internal static bool Equals(JsValue x, JsValue y)
+    {
+        return x == y || x is JsNumber xNum && y is JsNumber yNum && double.IsNaN(xNum._value) && double.IsNaN(yNum._value);
+    }
+}

+ 1 - 1
Jint/Native/Set/SetInstance.cs

@@ -11,7 +11,7 @@ namespace Jint.Native.Set
         public SetInstance(Engine engine)
             : base(engine, ObjectClass.Map)
         {
-            _set = new OrderedSet<JsValue>();
+            _set = new OrderedSet<JsValue>(SameValueZeroComparer.Instance);
         }
 
         public override PropertyDescriptor GetOwnProperty(JsValue property)

+ 1 - 1
Jint/Native/String/StringConstructor.cs

@@ -187,7 +187,7 @@ namespace Jint.Native.String
             var instance = new StringInstance(Engine)
             {
                 _prototype = prototype,
-                PrimitiveValue = value,
+                StringData = value,
                 _length = PropertyDescriptor.AllForbiddenDescriptor.ForNumber(value.Length)
             };
 

+ 5 - 5
Jint/Native/String/StringInstance.cs

@@ -16,9 +16,9 @@ namespace Jint.Native.String
 
         Types IPrimitiveInstance.Type => Types.String;
 
-        JsValue IPrimitiveInstance.PrimitiveValue => PrimitiveValue;
+        JsValue IPrimitiveInstance.PrimitiveValue => StringData;
 
-        public JsString PrimitiveValue { get; set; }
+        public JsString StringData { get; internal init; }
 
         private static bool IsInt32(double d, out int intValue)
         {
@@ -55,7 +55,7 @@ namespace Jint.Native.String
                 return PropertyDescriptor.Undefined;
             }
 
-            var str = PrimitiveValue.ToString();
+            var str = StringData.ToString();
             var number = TypeConverter.ToNumber(property);
             if (!IsInt32(number, out var index) || index < 0 || index >= str.Length)
             {
@@ -80,8 +80,8 @@ namespace Jint.Native.String
 
         public override List<JsValue> GetOwnPropertyKeys(Types types)
         {
-            var keys = new List<JsValue>(PrimitiveValue.Length + 1);
-            for (uint i = 0; i < PrimitiveValue.Length; ++i)
+            var keys = new List<JsValue>(StringData.Length + 1);
+            for (uint i = 0; i < StringData.Length; ++i)
             {
                 keys.Add(JsString.Create(i));
             }

+ 3 - 3
Jint/Native/String/StringPrototype.cs

@@ -29,7 +29,7 @@ namespace Jint.Native.String
             : base(engine)
         {
             _prototype = objectPrototype;
-            PrimitiveValue = JsString.Empty;
+            StringData = JsString.Empty;
             _length = PropertyDescriptor.AllForbiddenDescriptor.NumberZero;
             _realm = realm;
             _constructor = constructor;
@@ -109,7 +109,7 @@ namespace Jint.Native.String
                 ExceptionHelper.ThrowTypeError(_realm);
             }
 
-            return s.PrimitiveValue;
+            return s.StringData;
         }
 
         // http://msdn.microsoft.com/en-us/library/system.char.iswhitespace(v=vs.110).aspx
@@ -765,7 +765,7 @@ namespace Jint.Native.String
         {
             if (thisObj is StringInstance si)
             {
-                return si.PrimitiveValue;
+                return si.StringData;
             }
 
             if (thisObj is JsString)

+ 28 - 16
Jint/Native/TypedArray/IntrinsicTypedArrayPrototype.cs

@@ -12,7 +12,6 @@ using Jint.Pooling;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
-using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Native.TypedArray
 {
@@ -310,12 +309,19 @@ namespace Jint.Native.TypedArray
         {
             var o = thisObj.ValidateTypedArray(_realm);
 
+            var jsValue = arguments.At(0);
             var start = arguments.At(1);
             var end = arguments.At(2);
 
-            var value = o._contentType == TypedArrayContentType.BigInt
-                ? TypeConverter.ToBigInt(arguments.At(0))
-                : TypeConverter.ToNumber(arguments.At(0));
+            JsValue value;
+            if (o._contentType == TypedArrayContentType.BigInt)
+            {
+                value = JsBigInt.Create(jsValue.ToBigInteger(_engine));
+            }
+            else
+            {
+                value = JsNumber.Create(jsValue);
+            }
 
             var len = o.Length;
 
@@ -509,7 +515,7 @@ namespace Jint.Native.TypedArray
             while (k < len)
             {
                 var value = o[(int) k];
-                if (JintBinaryExpression.SameValueZero(value, searchElement))
+                if (SameValueZeroComparer.Equals(value, searchElement))
                 {
                     return JsBoolean.True;
                 }
@@ -565,8 +571,7 @@ namespace Jint.Native.TypedArray
                 if (kPresent)
                 {
                     var elementK = o[(int) k];
-                    var same = JintBinaryExpression.StrictlyEqual(elementK, searchElement);
-                    if (same)
+                    if (elementK == searchElement)
                     {
                         return k;
                     }
@@ -664,8 +669,7 @@ namespace Jint.Native.TypedArray
                 if (kPresent)
                 {
                     var elementK = o[(int) k];
-                    var same = JintBinaryExpression.StrictlyEqual(elementK, searchElement);
-                    if (same)
+                    if (elementK == searchElement)
                     {
                         return k;
                     }
@@ -963,18 +967,19 @@ namespace Jint.Native.TypedArray
 
             while (targetByteIndex < limit)
             {
-                double value;
                 if (target._contentType == TypedArrayContentType.BigInt)
                 {
-                    value = TypeConverter.ToBigInt(src.Get(k));
+                    var value = src.Get(k).ToBigInteger(_engine);
+                    targetBuffer.AssertNotDetached();
+                    targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered);
                 }
                 else
                 {
-                    value = TypeConverter.ToNumber(src.Get(k));
+                    var value = TypeConverter.ToNumber(src.Get(k));
+                    targetBuffer.AssertNotDetached();
+                    targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered);
                 }
 
-                targetBuffer.AssertNotDetached();
-                targetBuffer.SetValueInBuffer((int) targetByteIndex, targetType, value, true, ArrayBufferOrder.Unordered);
                 k++;
                 targetByteIndex += targetElementSize;
             }
@@ -1084,8 +1089,8 @@ namespace Jint.Native.TypedArray
                     var limit = targetByteIndex + count * elementSize;
                     while (targetByteIndex < limit)
                     {
-                        var value = (JsNumber) srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered);
-                        targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value._value, true, ArrayBufferOrder.Unordered);
+                        var value = srcBuffer.GetValueFromBuffer(srcByteIndex, TypedArrayElementType.Uint8, true, ArrayBufferOrder.Unordered);
+                        targetBuffer.SetValueInBuffer(targetByteIndex, TypedArrayElementType.Uint8, value, true, ArrayBufferOrder.Unordered);
                         srcByteIndex++;
                         targetByteIndex++;
                     }
@@ -1348,6 +1353,13 @@ namespace Jint.Native.TypedArray
                     return (int) v;
                 }
 
+                if (x.Type == Types.BigInt || y.Type == Types.BigInt)
+                {
+                    var xBigInt = TypeConverter.ToBigInt(x);
+                    var yBigInt = TypeConverter.ToBigInt(y);
+                    return xBigInt.CompareTo(yBigInt);
+                }
+
                 var xValue = x.AsNumber();
                 var yValue = y.AsNumber();
 

+ 18 - 2
Jint/Native/TypedArray/TypedArrayConstructor.cs

@@ -279,11 +279,27 @@ namespace Jint.Native.TypedArray
             return obj;
         }
 
-        internal static void FillTypedArrayInstance(TypedArrayInstance target, System.Array values)
+        internal static void FillTypedArrayInstance<T>(TypedArrayInstance target, T[] values)
         {
             for (var i = 0; i < values.Length; ++i)
             {
-                target.DoIntegerIndexedElementSet(i, Convert.ToDouble(values.GetValue(i)));
+                target.DoIntegerIndexedElementSet(i, Convert.ToDouble(values[i]));
+            }
+        }
+
+        internal static void FillTypedArrayInstance(TypedArrayInstance target, ulong[] values)
+        {
+            for (var i = 0; i < values.Length; ++i)
+            {
+                target.DoIntegerIndexedElementSet(i, values[i]);
+            }
+        }
+
+        internal static void FillTypedArrayInstance(TypedArrayInstance target, long[] values)
+        {
+            for (var i = 0; i < values.Length; ++i)
+            {
+                target.DoIntegerIndexedElementSet(i, values[i]);
             }
         }
     }

+ 51 - 27
Jint/Native/TypedArray/TypedArrayInstance.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Runtime.CompilerServices;
+using Esprima;
 using Jint.Native.ArrayBuffer;
 using Jint.Native.Number;
 using Jint.Native.Object;
@@ -11,7 +12,7 @@ namespace Jint.Native.TypedArray
 {
     public sealed class TypedArrayInstance : ObjectInstance
     {
-        internal TypedArrayContentType _contentType;
+        internal readonly TypedArrayContentType _contentType;
         internal readonly TypedArrayElementType _arrayElementType;
         internal ArrayBufferInstance _viewedArrayBuffer;
         internal uint _byteLength;
@@ -35,6 +36,10 @@ namespace Jint.Native.TypedArray
         {
             _arrayElementType = type;
             _arrayLength = length;
+
+            _contentType = type != TypedArrayElementType.BigInt64 && type != TypedArrayElementType.BigUint64
+                ? TypedArrayContentType.Number
+                : TypedArrayContentType.BigInt;
         }
 
         internal JsValue this[int index]
@@ -258,20 +263,35 @@ namespace Jint.Native.TypedArray
             var elementSize = elementType.GetElementSize();
             var indexedPosition = index * elementSize + offset;
             var value = _viewedArrayBuffer.GetValueFromBuffer(indexedPosition, elementType, true, ArrayBufferOrder.Unordered);
-            return _arrayElementType.FitsInt32() ? JsNumber.Create((int) value) : JsNumber.Create(value);
+            if (value.Type == Types.Number)
+            {
+                return _arrayElementType.FitsInt32()
+                    ? JsNumber.Create((int) value.DoubleValue)
+                    : JsNumber.Create(value.DoubleValue);
+            }
+
+            return JsBigInt.Create(value.BigInteger);
         }
 
         // helper tot prevent floating point
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void IntegerIndexedElementSet(int index, JsValue value)
         {
-            var numValue = _contentType == TypedArrayContentType.BigInt
-                ? TypeConverter.ToBigInt(value)
-                : TypeConverter.ToNumber(value);
-
-            if (IsValidIntegerIndex(index))
+            if (_contentType != TypedArrayContentType.BigInt)
+            {
+                var numValue = TypeConverter.ToNumber(value);
+                if (IsValidIntegerIndex(index))
+                {
+                    DoIntegerIndexedElementSet(index, numValue);
+                }
+            }
+            else
             {
-                DoIntegerIndexedElementSet(index, numValue);
+                var numValue = value.ToBigInteger(_engine);
+                if (IsValidIntegerIndex(index))
+                {
+                    DoIntegerIndexedElementSet(index, numValue);
+                }
             }
         }
 
@@ -281,17 +301,32 @@ namespace Jint.Native.TypedArray
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void IntegerIndexedElementSet(double index, JsValue value)
         {
-            var numValue = _contentType == TypedArrayContentType.BigInt
-                ? TypeConverter.ToBigInt(value)
-                : TypeConverter.ToNumber(value);
-
-            if (IsValidIntegerIndex(index))
+            if (_contentType != TypedArrayContentType.BigInt)
+            {
+                var numValue = TypeConverter.ToNumber(value);
+                if (IsValidIntegerIndex(index))
+                {
+                    DoIntegerIndexedElementSet((int) index, numValue);
+                }
+            }
+            else
             {
-                DoIntegerIndexedElementSet((int) index, numValue);
+                try
+                {
+                    var numValue = TypeConverter.ToBigInt(value);
+                    if (IsValidIntegerIndex(index))
+                    {
+                        DoIntegerIndexedElementSet((int) index, numValue);
+                    }
+                }
+                catch (ParserException ex)
+                {
+                    ExceptionHelper.ThrowSyntaxError(_engine.Realm, ex.Message);
+                }
             }
         }
 
-        internal void DoIntegerIndexedElementSet(int index, double numValue)
+        internal void DoIntegerIndexedElementSet(int index, TypedArrayValue numValue)
         {
             var offset = _byteOffset;
             var elementType = _arrayElementType;
@@ -307,7 +342,7 @@ namespace Jint.Native.TypedArray
         private bool IsValidIntegerIndex(double index)
         {
             return !_viewedArrayBuffer.IsDetachedBuffer
-                   && IsIntegralNumber(index)
+                   && TypeConverter.IsIntegralNumber(index)
                    && !NumberInstance.IsNegativeZero(index)
                    && (uint) index < _arrayLength;
         }
@@ -321,17 +356,6 @@ namespace Jint.Native.TypedArray
             return !_viewedArrayBuffer.IsDetachedBuffer && (uint) index < _arrayLength;
         }
 
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-isintegralnumber
-        /// </summary>
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static bool IsIntegralNumber(double value)
-        {
-            return !double.IsNaN(value)
-                   && !double.IsInfinity(value)
-                   && System.Math.Floor(System.Math.Abs(value)) == System.Math.Abs(value);
-        }
-
         internal T[] ToNativeArray<T>()
         {
             var conversionType = typeof(T);

+ 159 - 0
Jint/Native/TypedArray/TypedArrayValue.cs

@@ -0,0 +1,159 @@
+using System;
+using System.Numerics;
+using Jint.Runtime;
+
+namespace Jint.Native.TypedArray;
+
+/// <summary>
+/// Container for either double or BigInteger.
+/// </summary>
+internal readonly record struct TypedArrayValue(Types Type, double DoubleValue, BigInteger BigInteger) : IConvertible
+{
+    public static implicit operator TypedArrayValue(double value)
+    {
+        return new TypedArrayValue(Types.Number, value, default);
+    }
+
+    public static implicit operator TypedArrayValue(byte value)
+    {
+        return new TypedArrayValue(Types.Number, value, default);
+    }
+
+    public static implicit operator TypedArrayValue(int value)
+    {
+        return new TypedArrayValue(Types.Number, value, default);
+    }
+
+    public static implicit operator TypedArrayValue(ushort value)
+    {
+        return new TypedArrayValue(Types.Number, value, default);
+    }
+
+    public static implicit operator TypedArrayValue(short value)
+    {
+        return new TypedArrayValue(Types.Number, value, default);
+    }
+
+    public static implicit operator TypedArrayValue(uint value)
+    {
+        return new TypedArrayValue(Types.Number, value, default);
+    }
+
+    public static implicit operator TypedArrayValue(BigInteger value)
+    {
+        return new TypedArrayValue(Types.BigInt, default, value);
+    }
+
+    public static implicit operator TypedArrayValue(ulong value)
+    {
+        return new TypedArrayValue(Types.BigInt, default, value);
+    }
+
+    public static implicit operator TypedArrayValue(long value)
+    {
+        return new TypedArrayValue(Types.BigInt, default, value);
+    }
+
+    public JsValue ToJsValue()
+    {
+        return Type == Types.Number
+            ? JsNumber.Create(DoubleValue)
+            : JsBigInt.Create(BigInteger);
+    }
+
+    public TypeCode GetTypeCode()
+    {
+        ExceptionHelper.ThrowNotImplementedException();
+        return default;
+    }
+
+    public bool ToBoolean(IFormatProvider provider)
+    {
+        ExceptionHelper.ThrowNotImplementedException();
+        return default;
+    }
+
+    public char ToChar(IFormatProvider provider)
+    {
+        ExceptionHelper.ThrowNotImplementedException();
+        return default;
+    }
+
+    public sbyte ToSByte(IFormatProvider provider)
+    {
+        return (sbyte) DoubleValue;
+    }
+
+    public byte ToByte(IFormatProvider provider)
+    {
+        return (byte) DoubleValue;
+    }
+
+    public short ToInt16(IFormatProvider provider)
+    {
+        return (short) DoubleValue;
+    }
+
+    public ushort ToUInt16(IFormatProvider provider)
+    {
+        return (ushort) DoubleValue;
+    }
+
+    public int ToInt32(IFormatProvider provider)
+    {
+        return (int) DoubleValue;
+    }
+
+    public uint ToUInt32(IFormatProvider provider)
+    {
+        return (uint) DoubleValue;
+    }
+
+    public long ToInt64(IFormatProvider provider)
+    {
+        return (long) BigInteger;
+    }
+
+    public ulong ToUInt64(IFormatProvider provider)
+    {
+        return (ulong) BigInteger;
+    }
+
+    public float ToSingle(IFormatProvider provider)
+    {
+        return (float) DoubleValue;
+    }
+
+    public double ToDouble(IFormatProvider provider)
+    {
+        return DoubleValue;
+    }
+
+    public decimal ToDecimal(IFormatProvider provider)
+    {
+        return (decimal) DoubleValue;
+    }
+
+    public DateTime ToDateTime(IFormatProvider provider)
+    {
+        ExceptionHelper.ThrowNotImplementedException();
+        return default;
+    }
+
+    public string ToString(IFormatProvider provider)
+    {
+        ExceptionHelper.ThrowNotImplementedException();
+        return default;
+    }
+
+    public object ToType(Type conversionType, IFormatProvider provider)
+    {
+        if (conversionType == typeof(BigInteger) && Type == Types.BigInt)
+        {
+            return BigInteger;
+        }
+
+        ExceptionHelper.ThrowNotImplementedException();
+        return default;
+    }
+}

+ 49 - 6
Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs

@@ -1,3 +1,4 @@
+using System.Numerics;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native.Function;
@@ -126,10 +127,14 @@ namespace Jint.Runtime.Interpreter.Expressions
 
                                 lval = jsString.Append(rprim);
                             }
-                            else
+                            else if (!AreIntegerOperands(lval, rval))
                             {
                                 lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
                             }
+                            else
+                            {
+                                lval = TypeConverter.ToBigInt(lprim) + TypeConverter.ToBigInt(rprim);
+                            }
                         }
 
                         break;
@@ -138,9 +143,19 @@ namespace Jint.Runtime.Interpreter.Expressions
                     case AssignmentOperator.MinusAssign:
                     {
                         var rval = _right.GetValue(context).Value;
-                        lval = AreIntegerOperands(lval, rval)
-                            ? JsNumber.Create(lval.AsInteger() - rval.AsInteger())
-                            : JsNumber.Create(TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval));
+                        if (AreIntegerOperands(lval, rval))
+                        {
+                            lval = JsNumber.Create(lval.AsInteger() - rval.AsInteger());
+                        }
+                        else if (!AreIntegerOperands(lval, rval))
+                        {
+                            lval = JsNumber.Create(TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval));
+                        }
+                        else
+                        {
+                            lval = JsNumber.Create(TypeConverter.ToBigInt(lval) - TypeConverter.ToBigInt(rval));
+                        }
+
                         break;
                     }
 
@@ -155,10 +170,14 @@ namespace Jint.Runtime.Interpreter.Expressions
                         {
                             lval = Undefined.Instance;
                         }
-                        else
+                        else if (!AreIntegerOperands(lval, rval))
                         {
                             lval = TypeConverter.ToNumber(lval) * TypeConverter.ToNumber(rval);
                         }
+                        else
+                        {
+                            lval = TypeConverter.ToBigInt(lval) * TypeConverter.ToBigInt(rval);
+                        }
 
                         break;
                     }
@@ -166,7 +185,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                     case AssignmentOperator.DivideAssign:
                     {
                         var rval = _right.GetValue(context).Value;
-                        lval = Divide(lval, rval);
+                        lval = Divide(context, lval, rval);
                         break;
                     }
 
@@ -177,6 +196,10 @@ namespace Jint.Runtime.Interpreter.Expressions
                         {
                             lval = Undefined.Instance;
                         }
+                        else if (!AreIntegerOperands(lval, rval))
+                        {
+                            lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
+                        }
                         else
                         {
                             lval = TypeConverter.ToNumber(lval) % TypeConverter.ToNumber(rval);
@@ -263,6 +286,26 @@ namespace Jint.Runtime.Interpreter.Expressions
                         break;
                     }
 
+                    case AssignmentOperator.ExponentiationAssign:
+                    {
+                        var rval = _right.GetValue(context).Value;
+                        if (!lval.IsBigInt() && !rval.IsBigInt())
+                        {
+                            lval = JsNumber.Create(System.Math.Pow(TypeConverter.ToNumber(lval), TypeConverter.ToNumber(rval)));
+                        }
+                        else
+                        {
+                            var exponent = TypeConverter.ToBigInt(rval);
+                            if (exponent > int.MaxValue || exponent < int.MinValue)
+                            {
+                                ExceptionHelper.ThrowTypeError(context.Engine.Realm, "Cannot do exponentation with exponent not fitting int32");
+                            }
+                            lval = JsBigInt.Create(BigInteger.Pow(TypeConverter.ToBigInt(lval), (int) exponent));
+                        }
+
+                        break;
+                    }
+
                     default:
                         ExceptionHelper.ThrowNotImplementedException();
                         return default;

+ 310 - 100
Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs

@@ -1,9 +1,12 @@
 using System;
 using System.Collections.Concurrent;
 using System.Linq;
+using System.Numerics;
+using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Extensions;
 using Jint.Native;
+using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Runtime.Interop;
 
@@ -19,6 +22,8 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         private JintBinaryExpression(Engine engine, BinaryExpression expression) : base(expression)
         {
+            // TODO check https://tc39.es/ecma262/#sec-applystringornumericbinaryoperator
+
             _left = Build(engine, expression.Left);
             _right = Build(engine, expression.Right);
         }
@@ -155,70 +160,18 @@ namespace Jint.Runtime.Interpreter.Expressions
             return result;
         }
 
-        public static bool SameValueZero(JsValue x, JsValue y)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool AreNonBigIntOperands(JsValue left, JsValue right)
         {
-            return x == y || (x is JsNumber xNum && y is JsNumber yNum && double.IsNaN(xNum._value) && double.IsNaN(yNum._value));
+            return left._type != InternalTypes.BigInt && right._type != InternalTypes.BigInt;
         }
 
-        public static bool StrictlyEqual(JsValue x, JsValue y)
+        internal static void AssertValidBigIntArithmeticOperands(EvaluationContext context, JsValue left, JsValue right)
         {
-            var typeX = x._type & ~InternalTypes.InternalFlags;
-            var typeY = y._type & ~InternalTypes.InternalFlags;
-
-            if (typeX != typeY)
-            {
-                if (typeX == InternalTypes.Integer)
-                {
-                    typeX = InternalTypes.Number;
-                }
-
-                if (typeY == InternalTypes.Integer)
-                {
-                    typeY = InternalTypes.Number;
-                }
-
-                if (typeX != typeY)
-                {
-                    return false;
-                }
-            }
-
-            if (typeX == InternalTypes.Undefined || typeX == InternalTypes.Null)
-            {
-                return true;
-            }
-
-            if (typeX == InternalTypes.Integer)
-            {
-                return x.AsInteger() == y.AsInteger();
-            }
-
-            if (typeX == InternalTypes.Number)
+            if (left.Type != right.Type)
             {
-                var nx = ((JsNumber) x)._value;
-                var ny = ((JsNumber) y)._value;
-                return !double.IsNaN(nx) && !double.IsNaN(ny) && nx == ny;
+                ExceptionHelper.ThrowTypeError(context.Engine.Realm, "Cannot mix BigInt and other types, use explicit conversions");
             }
-
-            if ((typeX & InternalTypes.String) != 0)
-            {
-                return x.ToString() == y.ToString();
-            }
-
-            if (typeX == InternalTypes.Boolean)
-            {
-                return ((JsBoolean) x)._value == ((JsBoolean) y)._value;
-            }
-
-            if ((typeX & InternalTypes.Object) != 0 && x.AsObject() is IObjectWrapper xw)
-            {
-                var yw = y.AsObject() as IObjectWrapper;
-                if (yw == null)
-                    return false;
-                return Equals(xw.Target, yw.Target);
-            }
-
-            return x == y;
         }
 
         private sealed class StrictlyEqualBinaryExpression : JintBinaryExpression
@@ -231,7 +184,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue(context).Value;
                 var right = _right.GetValue(context).Value;
-                var equal = StrictlyEqual(left, right);
+                var equal = left == right;
                 return NormalCompletion(equal ? JsBoolean.True : JsBoolean.False);
             }
         }
@@ -246,7 +199,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue(context).Value;
                 var right = _right.GetValue(context).Value;
-                return NormalCompletion(StrictlyEqual(left, right) ? JsBoolean.False : JsBoolean.True);
+                return NormalCompletion(left == right ? JsBoolean.False : JsBoolean.True);
             }
         }
 
@@ -320,9 +273,20 @@ namespace Jint.Runtime.Interpreter.Expressions
 
                 var lprim = TypeConverter.ToPrimitive(left);
                 var rprim = TypeConverter.ToPrimitive(right);
-                JsValue result = lprim.IsString() || rprim.IsString()
-                    ? JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim))
-                    : JsNumber.Create(TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim));
+                JsValue result;
+                if (lprim.IsString() || rprim.IsString())
+                {
+                    result = JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim));
+                }
+                else if (AreNonBigIntOperands(left,right))
+                {
+                    result = JsNumber.Create(TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim));
+                }
+                else
+                {
+                    AssertValidBigIntArithmeticOperands(context, lprim, rprim);
+                    result = JsBigInt.Create(TypeConverter.ToBigInt(lprim) + TypeConverter.ToBigInt(rprim));
+                }
 
                 return NormalCompletion(result);
             }
@@ -345,9 +309,22 @@ namespace Jint.Runtime.Interpreter.Expressions
                     return NormalCompletion(JsValue.FromObject(context.Engine, opResult));
                 }
 
-                var number = AreIntegerOperands(left, right)
-                    ? JsNumber.Create(left.AsInteger() - right.AsInteger())
-                    : JsNumber.Create(TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right));
+                JsValue number;
+                left = TypeConverter.ToNumeric(left);
+                right = TypeConverter.ToNumeric(right);
+
+                if (AreIntegerOperands(left, right))
+                {
+                    number = JsNumber.Create(left.AsInteger() - right.AsInteger());
+                }
+                else if (AreNonBigIntOperands(left, right))
+                {
+                    number = JsNumber.Create(TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right));
+                }
+                else
+                {
+                    number = JsBigInt.Create(TypeConverter.ToBigInt(left) - TypeConverter.ToBigInt(right));
+                }
 
                 return NormalCompletion(number);
             }
@@ -364,23 +341,33 @@ namespace Jint.Runtime.Interpreter.Expressions
                 var left = _left.GetValue(context).Value;
                 var right = _right.GetValue(context).Value;
 
+                JsValue result;
                 if (context.OperatorOverloadingAllowed
                     && TryOperatorOverloading(context, left, right, "op_Multiply", out var opResult))
                 {
-                    return NormalCompletion(JsValue.FromObject(context.Engine, opResult));
+                    result = JsValue.FromObject(context.Engine, opResult);
                 }
-
-                if (AreIntegerOperands(left, right))
+                else if (AreIntegerOperands(left, right))
                 {
-                    return NormalCompletion(JsNumber.Create((long) left.AsInteger() * right.AsInteger()));
+                    result = JsNumber.Create((long) left.AsInteger() * right.AsInteger());
                 }
-
-                if (left.IsUndefined() || right.IsUndefined())
+                else
                 {
-                    return NormalCompletion(Undefined.Instance);
+                    var leftNumeric = TypeConverter.ToNumeric(left);
+                    var rightNumeric = TypeConverter.ToNumeric(right);
+
+                    if (leftNumeric.IsNumber())
+                    {
+                        result = JsNumber.Create(TypeConverter.ToNumber(left) * TypeConverter.ToNumber(right));
+                    }
+                    else
+                    {
+                        AssertValidBigIntArithmeticOperands(context, leftNumeric, rightNumeric);
+                        result = JsBigInt.Create(leftNumeric.AsBigInt() * rightNumeric.AsBigInt());
+                    }
                 }
 
-                return NormalCompletion(JsNumber.Create(TypeConverter.ToNumber(left) * TypeConverter.ToNumber(right)));
+                return NormalCompletion(result);
             }
         }
 
@@ -401,7 +388,9 @@ namespace Jint.Runtime.Interpreter.Expressions
                     return NormalCompletion(JsValue.FromObject(context.Engine, opResult));
                 }
 
-                return NormalCompletion(Divide(left, right));
+                left = TypeConverter.ToNumeric(left);
+                right = TypeConverter.ToNumeric(right);
+                return NormalCompletion(Divide(context, left, right));
             }
         }
 
@@ -425,7 +414,12 @@ namespace Jint.Runtime.Interpreter.Expressions
                     return NormalCompletion(JsValue.FromObject(context.Engine, opResult));
                 }
 
-                return NormalCompletion(Equal(left, right) == !_invert ? JsBoolean.True : JsBoolean.False);
+                // if types match, we can take faster strict equality
+                var equality = left.Type == right.Type
+                    ? left.Equals(right)
+                    : left.IsLooselyEqual(right);
+
+                return NormalCompletion(equality == !_invert ? JsBoolean.True : JsBoolean.False);
             }
         }
 
@@ -479,10 +473,120 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             protected override ExpressionResult EvaluateInternal(EvaluationContext context)
             {
-                var left = _left.GetValue(context).Value;
-                var right = _right.GetValue(context).Value;
+                var leftReference = _left.GetValue(context).Value;
+                var rightReference = _right.GetValue(context).Value;
+
+                var left = TypeConverter.ToNumeric(leftReference);
+                var right = TypeConverter.ToNumeric(rightReference);
+
+                JsValue result;
+                if (AreNonBigIntOperands(left,right))
+                {
+                    // validation
+                    var baseNumber = (JsNumber) left;
+                    var exponentNumber = (JsNumber) right;
+
+                    if (exponentNumber.IsNaN())
+                    {
+                        return NormalCompletion(JsNumber.DoubleNaN);
+                    }
+
+                    if (exponentNumber.IsZero())
+                    {
+                        return NormalCompletion(JsNumber.PositiveOne);
+                    }
+
+                    if (baseNumber.IsNaN())
+                    {
+                        return NormalCompletion(JsNumber.DoubleNaN);
+                    }
+
+                    var exponentValue = exponentNumber._value;
+                    if (baseNumber.IsPositiveInfinity())
+                    {
+                        return NormalCompletion(exponentValue > 0 ? JsNumber.DoublePositiveInfinity : JsNumber.PositiveZero);
+                    }
+
+                    static bool IsOddIntegral(double value) => TypeConverter.IsIntegralNumber(value) && value % 2 != 0;
+
+                    if (baseNumber.IsNegativeInfinity())
+                    {
+                        if (exponentValue > 0)
+                        {
+                            return NormalCompletion(IsOddIntegral(exponentValue) ? JsNumber.DoubleNegativeInfinity : JsNumber.DoublePositiveInfinity);
+                        }
+
+                        return NormalCompletion(IsOddIntegral(exponentValue) ? JsNumber.NegativeZero : JsNumber.PositiveZero);
+                    }
 
-                return NormalCompletion(JsNumber.Create(Math.Pow(TypeConverter.ToNumber(left), TypeConverter.ToNumber(right))));
+                    if (baseNumber.IsPositiveZero())
+                    {
+                        return NormalCompletion(exponentValue > 0 ? JsNumber.PositiveZero : JsNumber.DoublePositiveInfinity);
+                    }
+
+                    if (baseNumber.IsNegativeZero())
+                    {
+                        if (exponentValue > 0)
+                        {
+                            return NormalCompletion(IsOddIntegral(exponentValue) ? JsNumber.NegativeZero : JsNumber.PositiveZero);
+                        }
+                        return NormalCompletion(IsOddIntegral(exponentValue) ? JsNumber.DoubleNegativeInfinity : JsNumber.DoublePositiveInfinity);
+                    }
+
+                    var baseValue = baseNumber._value;
+                    if (exponentNumber.IsPositiveInfinity())
+                    {
+                        if (Math.Abs(baseValue) > 1)
+                        {
+                            return NormalCompletion(JsNumber.DoublePositiveInfinity);
+                        }
+                        if (Math.Abs(baseValue) == 1)
+                        {
+                            return NormalCompletion(JsNumber.DoubleNaN);
+                        }
+
+                        return NormalCompletion(JsNumber.PositiveZero);
+                    }
+
+                    if (exponentNumber.IsNegativeInfinity())
+                    {
+                        if (Math.Abs(baseValue) > 1)
+                        {
+                            return NormalCompletion(JsNumber.PositiveZero);
+                        }
+                        if (Math.Abs(baseValue) == 1)
+                        {
+                            return NormalCompletion(JsNumber.DoubleNaN);
+                        }
+
+                        return NormalCompletion(JsNumber.DoublePositiveInfinity);
+                    }
+
+                    if (baseValue < 0 && !TypeConverter.IsIntegralNumber(exponentValue))
+                    {
+                        return NormalCompletion(JsNumber.DoubleNaN);
+                    }
+
+                    result = JsNumber.Create(Math.Pow(baseNumber._value, exponentValue));
+                }
+                else
+                {
+                    AssertValidBigIntArithmeticOperands(context, left, right);
+
+                    var exponent = right.AsBigInt();
+                    if (exponent < 0)
+                    {
+                        ExceptionHelper.ThrowRangeError(context.Engine.Realm, "Exponent must be positive");
+                    }
+
+                    if (exponent > int.MaxValue || exponent < int.MinValue)
+                    {
+                        ExceptionHelper.ThrowTypeError(context.Engine.Realm, "Exponent does not fit 32bit range");
+                    }
+                    result = JsBigInt.Create(BigInteger.Pow(left.AsBigInt(), (int) exponent));
+                }
+
+                return NormalCompletion(result);
             }
         }
 
@@ -524,22 +628,80 @@ namespace Jint.Runtime.Interpreter.Expressions
                     return NormalCompletion(JsValue.FromObject(context.Engine, opResult));
                 }
 
+                var result = Undefined.Instance;
+                left = TypeConverter.ToNumeric(left);
+                right = TypeConverter.ToNumeric(right);
+
                 if (AreIntegerOperands(left, right))
                 {
                     var leftInteger = left.AsInteger();
                     var rightInteger = right.AsInteger();
-                    if (leftInteger > 0 && rightInteger != 0)
+
+                    if (rightInteger == 0)
                     {
-                        return NormalCompletion(JsNumber.Create(leftInteger % rightInteger));
+                        result = JsNumber.DoubleNaN;
+                    }
+                    else
+                    {
+                        var modulo = leftInteger % rightInteger;
+                        if (modulo == 0 && leftInteger < 0)
+                        {
+                            result = JsNumber.NegativeZero;
+                        }
+                        else
+                        {
+                            result = JsNumber.Create(modulo);
+                        }
                     }
                 }
+                else if (AreNonBigIntOperands(left, right))
+                {
+                    var n = TypeConverter.ToNumber(left);
+                    var d = TypeConverter.ToNumber(right);
 
-                if (left.IsUndefined() || right.IsUndefined())
+                    if (double.IsNaN(n) || double.IsNaN(d) || double.IsInfinity(n))
+                    {
+                        result = JsNumber.DoubleNaN;
+                    }
+                    else if (double.IsInfinity(d))
+                    {
+                        result = n;
+                    }
+                    else if (NumberInstance.IsPositiveZero(d) || NumberInstance.IsNegativeZero(d))
+                    {
+                        result = JsNumber.DoubleNaN;
+                    }
+                    else if (NumberInstance.IsPositiveZero(n) || NumberInstance.IsNegativeZero(n))
+                    {
+                        result = n;
+                    }
+                    else
+                    {
+                        result = JsNumber.Create(n % d);
+                    }
+                }
+                else
                 {
-                    return NormalCompletion(Undefined.Instance);
+                    AssertValidBigIntArithmeticOperands(context, left, right);
+
+                    var n = TypeConverter.ToBigInt(left);
+                    var d = TypeConverter.ToBigInt(right);
+
+                    if (d == 0)
+                    {
+                        ExceptionHelper.ThrowRangeError(context.Engine.Realm, "Division by zero");
+                    }
+                    else if (n == 0)
+                    {
+                        result = JsBigInt.Zero;
+                    }
+                    else
+                    {
+                        result = JsBigInt.Create(n % d);
+                    }
                 }
 
-                return NormalCompletion(JsNumber.Create(TypeConverter.ToNumber(left) % TypeConverter.ToNumber(right)));
+                return NormalCompletion(result);
             }
         }
 
@@ -571,21 +733,29 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             protected override ExpressionResult EvaluateInternal(EvaluationContext context)
             {
-                var left = _left.GetValue(context).Value;
-                var right = _right.GetValue(context).Value;
+                var lval = _left.GetValue(context).Value;
+                var rval = _right.GetValue(context).Value;
 
                 if (context.OperatorOverloadingAllowed
-                    && TryOperatorOverloading(context, left, right, OperatorClrName, out var opResult))
+                    && TryOperatorOverloading(context, lval, rval, OperatorClrName, out var opResult))
                 {
                     return NormalCompletion(JsValue.FromObject(context.Engine, opResult));
                 }
 
-                if (AreIntegerOperands(left, right))
+                var lnum = TypeConverter.ToNumeric(lval);
+                var rnum = TypeConverter.ToNumeric(rval);
+
+                if (lnum.Type != rnum.Type)
                 {
-                    int leftValue = left.AsInteger();
-                    int rightValue = right.AsInteger();
+                    ExceptionHelper.ThrowTypeError(context.Engine.Realm);
+                }
 
-                    JsValue result;
+                if (AreIntegerOperands(lnum, rnum))
+                {
+                    int leftValue = lnum.AsInteger();
+                    int rightValue = rnum.AsInteger();
+
+                    JsValue result = null;
                     switch (_operator)
                     {
                         case BinaryOperator.BitwiseAnd:
@@ -608,40 +778,80 @@ namespace Jint.Runtime.Interpreter.Expressions
                             break;
                         default:
                             ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(_operator), "unknown shift operator");
-                            result = null;
                             break;
                     }
 
                     return NormalCompletion(result);
                 }
 
-                return NormalCompletion(EvaluateNonInteger(left, right));
+                return NormalCompletion(EvaluateNonInteger(context.Engine.Realm, lnum, rnum));
             }
 
-            private JsNumber EvaluateNonInteger(JsValue left, JsValue right)
+            private JsValue EvaluateNonInteger(Realm realm, JsValue left, JsValue right)
             {
                 switch (_operator)
                 {
                     case BinaryOperator.BitwiseAnd:
-                        return JsNumber.Create(TypeConverter.ToInt32(left) & TypeConverter.ToInt32(right));
+                    {
+                        if (!left.IsBigInt())
+                        {
+                            return JsNumber.Create(TypeConverter.ToInt32(left) & TypeConverter.ToInt32(right));
+                        }
+
+                        return JsBigInt.Create(TypeConverter.ToBigInt(left) & TypeConverter.ToBigInt(right));
+                    }
 
                     case BinaryOperator.BitwiseOr:
-                        return JsNumber.Create(TypeConverter.ToInt32(left) | TypeConverter.ToInt32(right));
+                    {
+                        if (!left.IsBigInt())
+                        {
+                            return JsNumber.Create(TypeConverter.ToInt32(left) | TypeConverter.ToInt32(right));
+                        }
+                        return JsBigInt.Create(TypeConverter.ToBigInt(left) | TypeConverter.ToBigInt(right));
+                    }
 
                     case BinaryOperator.BitwiseXOr:
-                        return JsNumber.Create(TypeConverter.ToInt32(left) ^ TypeConverter.ToInt32(right));
+                    {
+                        if (!left.IsBigInt())
+                        {
+                            return JsNumber.Create(TypeConverter.ToInt32(left) ^ TypeConverter.ToInt32(right));
+                        }
+                        return JsBigInt.Create(TypeConverter.ToBigInt(left) ^ TypeConverter.ToBigInt(right));
+                    }
 
                     case BinaryOperator.LeftShift:
-                        return JsNumber.Create(TypeConverter.ToInt32(left) << (int) (TypeConverter.ToUint32(right) & 0x1F));
+                    {
+                        if (!left.IsBigInt())
+                        {
+                            return JsNumber.Create(TypeConverter.ToInt32(left) << (int) (TypeConverter.ToUint32(right) & 0x1F));
+                        }
+                        return JsBigInt.Create(TypeConverter.ToBigInt(left) << (int) TypeConverter.ToBigInt(right));
+                    }
 
                     case BinaryOperator.RightShift:
-                        return JsNumber.Create(TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
+                    {
+                        if (!left.IsBigInt())
+                        {
+                            return JsNumber.Create(TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
+                        }
+                        return JsBigInt.Create(TypeConverter.ToBigInt(left) >> (int) TypeConverter.ToBigInt(right));
+                    }
 
                     case BinaryOperator.UnsignedRightShift:
-                        return JsNumber.Create((uint) TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
+                    {
+                        if (!left.IsBigInt())
+                        {
+                            return JsNumber.Create((uint) TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
+                        }
+                        ExceptionHelper.ThrowTypeError(realm);
+                        return null;
+                    }
+
                     default:
+                    {
                         ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(_operator), "unknown shift operator");
                         return null;
+                    }
                 }
             }
         }

+ 94 - 59
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -1,6 +1,7 @@
 #nullable enable
 
 using System.Diagnostics;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 using Esprima;
 using Esprima.Ast;
@@ -209,11 +210,32 @@ namespace Jint.Runtime.Interpreter.Expressions
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        protected static JsValue Divide(JsValue lval, JsValue rval)
+        protected static JsValue Divide(EvaluationContext context, JsValue left, JsValue right)
         {
-            return AreIntegerOperands(lval, rval)
-                ? DivideInteger(lval, rval)
-                : DivideComplex(lval, rval);
+            JsValue result;
+            if (AreIntegerOperands(left, right))
+            {
+                result = DivideInteger(left, right);
+            }
+            else if (JintBinaryExpression.AreNonBigIntOperands(left, right))
+            {
+                result = DivideComplex(left, right);
+            }
+            else
+            {
+                JintBinaryExpression.AssertValidBigIntArithmeticOperands(context, left, right);
+                var x = TypeConverter.ToBigInt(left);
+                var y = TypeConverter.ToBigInt(right);
+
+                if (y == 0)
+                {
+                    ExceptionHelper.ThrowRangeError(context.Engine.Realm, "Division by zero");
+                }
+
+                result = JsBigInt.Create(x / y);
+            }
+
+            return result;
         }
 
         private static JsValue DivideInteger(JsValue lval, JsValue rval)
@@ -290,60 +312,6 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         }
 
-        protected static bool Equal(JsValue x, JsValue y)
-        {
-            return x.Type == y.Type
-                ? JintBinaryExpression.StrictlyEqual(x, y)
-                : EqualUnlikely(x, y);
-        }
-
-        private static bool EqualUnlikely(JsValue x, JsValue y)
-        {
-            if (x._type == InternalTypes.Null && y._type == InternalTypes.Undefined)
-            {
-                return true;
-            }
-
-            if (x._type == InternalTypes.Undefined && y._type == InternalTypes.Null)
-            {
-                return true;
-            }
-
-            if (x.IsNumber() && y.IsString())
-            {
-                return Equal(x, TypeConverter.ToNumber(y));
-            }
-
-            if (x.IsString() && y.IsNumber())
-            {
-                return Equal(TypeConverter.ToNumber(x), y);
-            }
-
-            if (x.IsBoolean())
-            {
-                return Equal(TypeConverter.ToNumber(x), y);
-            }
-
-            if (y.IsBoolean())
-            {
-                return Equal(x, TypeConverter.ToNumber(y));
-            }
-
-            const InternalTypes stringOrNumber = InternalTypes.String | InternalTypes.Integer | InternalTypes.Number;
-
-            if (y.IsObject() && (x._type & stringOrNumber) != 0)
-            {
-                return Equal(x, TypeConverter.ToPrimitive(y));
-            }
-
-            if (x.IsObject() && ((y._type & stringOrNumber) != 0))
-            {
-                return Equal(TypeConverter.ToPrimitive(x), y);
-            }
-
-            return false;
-        }
-
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true) =>
             x._type == y._type && x._type == InternalTypes.Integer
@@ -386,6 +354,73 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             if (typea != Types.String || typeb != Types.String)
             {
+                if (typea == Types.BigInt || typeb == Types.BigInt)
+                {
+                    if (typea == typeb)
+                    {
+                        return TypeConverter.ToBigInt(px) < TypeConverter.ToBigInt(py);
+                    }
+
+                    if (typea == Types.BigInt)
+                    {
+                        if (py is JsString jsStringY)
+                        {
+                            if (!TypeConverter.TryStringToBigInt(jsStringY.ToString(), out var temp))
+                            {
+                                return JsValue.Undefined;
+                            }
+                            return TypeConverter.ToBigInt(px) < temp;
+                        }
+
+                        var numberB = TypeConverter.ToNumber(py);
+                        if (double.IsNaN(numberB))
+                        {
+                            return JsValue.Undefined;
+                        }
+
+                        if (double.IsPositiveInfinity(numberB))
+                        {
+                            return true;
+                        }
+
+                        if (double.IsNegativeInfinity(numberB))
+                        {
+                            return false;
+                        }
+
+                        var normalized = new BigInteger(System.Math.Ceiling(numberB));
+                        return TypeConverter.ToBigInt(px) < normalized;
+                    }
+
+                    if (px is JsString jsStringX)
+                    {
+                        if (!TypeConverter.TryStringToBigInt(jsStringX.ToString(), out var temp))
+                        {
+                            return JsValue.Undefined;
+                        }
+                        return temp < TypeConverter.ToBigInt(py);
+                    }
+
+                    var numberA = TypeConverter.ToNumber(px);
+                    if (double.IsNaN(numberA))
+                    {
+                        return JsValue.Undefined;
+                    }
+
+                    if (double.IsPositiveInfinity(numberA))
+                    {
+                        return false;
+                    }
+
+                    if (double.IsNegativeInfinity(numberA))
+                    {
+                        return true;
+                    }
+
+                    var normalizedA = new BigInteger(System.Math.Floor(numberA));
+                    return normalizedA < TypeConverter.ToBigInt(py);
+                }
+
                 var nx = TypeConverter.ToNumber(px);
                 var ny = TypeConverter.ToNumber(py);
 
@@ -435,7 +470,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
         }
 
-        protected JsValue[] BuildArgumentsWithSpreads(EvaluationContext context, JintExpression[] jintExpressions)
+        protected static JsValue[] BuildArgumentsWithSpreads(EvaluationContext context, JintExpression[] jintExpressions)
         {
             var args = new System.Collections.Generic.List<JsValue>(jintExpressions.Length);
             for (var i = 0; i < jintExpressions.Length; i++)

+ 6 - 0
Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Numerics;
 using Esprima;
 using Esprima.Ast;
 using Jint.Native;
@@ -48,6 +49,11 @@ namespace Jint.Runtime.Interpreter.Expressions
                 return JsString.Create((string) literal.Value);
             }
 
+            if (literal.TokenType == TokenType.BigIntLiteral)
+            {
+                return JsBigInt.Create((BigInteger) literal.Value);
+            }
+
             return null;
         }
 

+ 22 - 10
Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs

@@ -6,6 +6,7 @@ using Jint.Runtime.Environments;
 using Jint.Runtime.Interop;
 using Jint.Runtime.References;
 using System.Collections.Concurrent;
+using System.Numerics;
 using System.Reflection;
 
 namespace Jint.Runtime.Interpreter.Expressions
@@ -67,9 +68,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                         return result;
                     }
 
-                    return v.IsInteger() && v.AsInteger() != 0
-                        ? v
-                        : JsNumber.Create(TypeConverter.ToNumber(v));
+                    return TypeConverter.ToNumber(v);
                 }
                 case UnaryOperator.Minus:
                 {
@@ -91,7 +90,13 @@ namespace Jint.Runtime.Interpreter.Expressions
                         return result;
                     }
 
-                    return JsNumber.Create(~TypeConverter.ToInt32(v));
+                    var value = TypeConverter.ToNumeric(v);
+                    if (value.IsNumber())
+                    {
+                        return JsNumber.Create(~TypeConverter.ToInt32(value));
+                    }
+
+                    return JsBigInt.Create(~value.AsBigInt());
                 }
                 case UnaryOperator.LogicalNot:
                 {
@@ -190,6 +195,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                     {
                         case Types.Boolean: return JsString.BooleanString;
                         case Types.Number: return JsString.NumberString;
+                        case Types.BigInt: return JsString.BigIntString;
                         case Types.String: return JsString.StringString;
                         case Types.Symbol: return JsString.SymbolString;
                     }
@@ -207,20 +213,26 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
         }
 
-        private static JsNumber EvaluateMinus(JsValue value)
+        private static JsValue EvaluateMinus(JsValue value)
         {
-            var minusValue = value;
-            if (minusValue.IsInteger())
+            if (value.IsInteger())
             {
-                var asInteger = minusValue.AsInteger();
+                var asInteger = value.AsInteger();
                 if (asInteger != 0)
                 {
                     return JsNumber.Create(asInteger * -1);
                 }
             }
 
-            var n = TypeConverter.ToNumber(minusValue);
-            return JsNumber.Create(double.IsNaN(n) ? double.NaN : n * -1);
+            value = TypeConverter.ToNumeric(value);
+            if (value.IsNumber())
+            {
+                var n = ((JsNumber) value)._value;
+                return double.IsNaN(n) ? JsNumber.DoubleNaN : JsNumber.Create(n * -1);
+            }
+
+            var bigInt = value.AsBigInt();
+            return JsBigInt.Create(BigInteger.Negate(bigInt));
         }
 
         internal static bool TryOperatorOverloading(EvaluationContext context, JsValue value, string clrName, out JsValue result)

+ 53 - 12
Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs

@@ -78,17 +78,41 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             if (!operatorOverloaded)
             {
-                newValue = isInteger
-                    ? JsNumber.Create(value.AsInteger() + _change)
-                    : JsNumber.Create(TypeConverter.ToNumber(value) + _change);
+                if (isInteger)
+                {
+                    newValue = JsNumber.Create(value.AsInteger() + _change);
+                }
+                else if (!value.IsBigInt())
+                {
+                    newValue = JsNumber.Create(TypeConverter.ToNumber(value) + _change);
+                }
+                else
+                {
+                    newValue = JsBigInt.Create(TypeConverter.ToBigInt(value) + _change);
+                }
             }
 
             engine.PutValue(reference, newValue);
             engine._referencePool.Return(reference);
 
-            return _prefix
-                ? newValue
-                : (isInteger || operatorOverloaded ? value : JsNumber.Create(TypeConverter.ToNumber(value)));
+            if (_prefix)
+            {
+                return newValue;
+            }
+            else
+            {
+                if (isInteger || operatorOverloaded)
+                {
+                    return value;
+                }
+
+                if (!value.IsBigInt())
+                {
+                    return JsNumber.Create(TypeConverter.ToNumber(value));
+                }
+
+                return JsBigInt.Create(value);
+            }
         }
 
         private JsValue UpdateIdentifier(EvaluationContext context)
@@ -126,15 +150,32 @@ namespace Jint.Runtime.Interpreter.Expressions
 
                 if (!operatorOverloaded)
                 {
-                    newValue = isInteger
-                    ? JsNumber.Create(value.AsInteger() + _change)
-                    : JsNumber.Create(TypeConverter.ToNumber(value) + _change);
+                    if (isInteger)
+                    {
+                        newValue = JsNumber.Create(value.AsInteger() + _change);
+                    }
+                    else if (value._type != InternalTypes.BigInt)
+                    {
+                        newValue = JsNumber.Create(TypeConverter.ToNumber(value) + _change);
+                    }
+                    else
+                    {
+                        newValue = JsBigInt.Create(TypeConverter.ToBigInt(value) + _change);
+                    }
                 }
 
                 environmentRecord.SetMutableBinding(name.Key.Name, newValue, strict);
-                return _prefix
-                    ? newValue
-                    : (isInteger || operatorOverloaded ? value : JsNumber.Create(TypeConverter.ToNumber(value)));
+                if (_prefix)
+                {
+                    return newValue;
+                }
+
+                if (!value.IsBigInt() && !value.IsNumber() && !operatorOverloaded)
+                {
+                    return JsNumber.Create(TypeConverter.ToNumber(value));
+                }
+
+                return value;
             }
 
             return null;

+ 1 - 1
Jint/Runtime/Interpreter/Statements/JintSwitchBlock.cs

@@ -62,7 +62,7 @@ namespace Jint.Runtime.Interpreter.Statements
                 else
                 {
                     var clauseSelector = clause.Test.GetValue(context).Value;
-                    if (JintBinaryExpression.StrictlyEqual(clauseSelector, input))
+                    if (clauseSelector == input)
                     {
                         hit = true;
                     }

+ 5 - 0
Jint/Runtime/Intrinsics.cs

@@ -1,6 +1,7 @@
 using Jint.Native;
 using Jint.Native.Array;
 using Jint.Native.ArrayBuffer;
+using Jint.Native.BigInt;
 using Jint.Native.Boolean;
 using Jint.Native.DataView;
 using Jint.Native.Date;
@@ -60,6 +61,7 @@ namespace Jint.Runtime
         private RegExpConstructor _regExp;
         private RegExpStringIteratorPrototype _regExpStringIteratorPrototype;
         private NumberConstructor _number;
+        private BigIntConstructor _bigInt;
         private StringConstructor _string;
         private StringIteratorPrototype _stringIteratorPrototype;
         private MapConstructor _map;
@@ -194,6 +196,9 @@ namespace Jint.Runtime
         public NumberConstructor Number =>
             _number ??= new NumberConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject);
 
+        public BigIntConstructor BigInt =>
+            _bigInt ??= new BigIntConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject);
+
         public DateConstructor Date =>
             _date ??= new DateConstructor(_engine, _realm, Function.PrototypeObject, Object.PrototypeObject);
 

+ 2 - 2
Jint/Runtime/OrderedSet.cs

@@ -7,10 +7,10 @@ namespace Jint.Runtime
         internal readonly List<T> _list;
         private readonly HashSet<T> _set;
 
-        public OrderedSet()
+        public OrderedSet(IEqualityComparer<T> comparer)
         {
             _list = new List<T>();
-            _set = new HashSet<T>();
+            _set = new HashSet<T>(comparer);
         }
 
         public T this[int index]

+ 216 - 25
Jint/Runtime/TypeConverter.cs

@@ -1,7 +1,9 @@
 using System;
 using System.Collections.Generic;
 using System.Globalization;
+using System.Numerics;
 using System.Runtime.CompilerServices;
+using Esprima;
 using Esprima.Ast;
 using Jint.Extensions;
 using Jint.Native;
@@ -25,7 +27,8 @@ namespace Jint.Runtime
         String = 8,
         Number = 16,
         Symbol = 64,
-        Object = 128
+        BigInt = 128,
+        Object = 256
     }
 
     [Flags]
@@ -43,15 +46,16 @@ namespace Jint.Runtime
         Number = 16,
         Integer = 32,
         Symbol = 64,
+        BigInt = 128,
 
         // primitive  types range end
-        Object = 128,
+        Object = 256,
 
         // internal usage
         ObjectEnvironmentRecord = 512,
         RequiresCloning = 1024,
 
-        Primitive = Boolean | String | Number | Integer | Symbol,
+        Primitive = Boolean | String | Number | Integer | BigInt | Symbol,
         InternalFlags = ObjectEnvironmentRecord | RequiresCloning
     }
 
@@ -90,16 +94,16 @@ namespace Jint.Runtime
 
         private static JsValue ToPrimitiveObjectInstance(ObjectInstance oi, Types preferredType)
         {
-            var hint = preferredType switch
-            {
-                Types.String => JsString.StringString,
-                Types.Number => JsString.NumberString,
-                _ => JsString.DefaultString
-            };
-
             var exoticToPrim = oi.GetMethod(GlobalSymbolRegistry.ToPrimitive);
-            if (exoticToPrim is object)
+            if (exoticToPrim is not null)
             {
+                var hint = preferredType switch
+                {
+                    Types.String => JsString.StringString,
+                    Types.Number => JsString.NumberString,
+                    _ => JsString.DefaultString
+                };
+
                 var str = exoticToPrim.Call(oi, new JsValue[] { hint });
                 if (str.IsPrimitive())
                 {
@@ -181,11 +185,32 @@ namespace Jint.Runtime
                     return n != 0 && !double.IsNaN(n);
                 case InternalTypes.String:
                     return !((JsString) o).IsNullOrEmpty();
+                case InternalTypes.BigInt:
+                    return ((JsBigInt) o)._value != 0;
                 default:
                     return true;
             }
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-tonumeric
+        /// </summary>
+        public static JsValue ToNumeric(JsValue value)
+        {
+            if (value.IsNumber() || value.IsBigInt())
+            {
+                return value;
+            }
+
+            var primValue = ToPrimitive(value, Types.Number);
+            if (primValue.IsBigInt())
+            {
+                return primValue;
+            }
+
+            return ToNumber(primValue);
+        }
+
         /// <summary>
         /// https://tc39.es/ecma262/#sec-tonumber
         /// </summary>
@@ -207,15 +232,14 @@ namespace Jint.Runtime
                     return double.NaN;
                 case InternalTypes.Null:
                     return 0;
-                case InternalTypes.Object when o is IPrimitiveInstance p:
-                    return ToNumber(ToPrimitive(p.PrimitiveValue, Types.Number));
                 case InternalTypes.Boolean:
                     return ((JsBoolean) o)._value ? 1 : 0;
                 case InternalTypes.String:
                     return ToNumber(o.ToString());
                 case InternalTypes.Symbol:
+                case InternalTypes.BigInt:
                     // TODO proper TypeError would require Engine instance and a lot of API changes
-                    ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a Symbol value to a number");
+                    ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a " + type + " value to a number");
                     return 0;
                 default:
                     return ToNumber(ToPrimitive(o, Types.Number));
@@ -546,30 +570,178 @@ namespace Jint.Runtime
         /// <summary>
         /// https://tc39.es/ecma262/#sec-tobigint
         /// </summary>
-        public static double ToBigInt(JsValue value)
+        public static BigInteger ToBigInt(JsValue value)
+        {
+            return value is JsBigInt bigInt
+                ? bigInt._value
+                : ToBigIntUnlikely(value);
+        }
+
+        private static BigInteger ToBigIntUnlikely(JsValue value)
+        {
+            var prim = ToPrimitive(value, Types.Number);
+            switch (prim.Type)
+            {
+                case Types.BigInt:
+                    return ((JsBigInt) prim)._value;
+                case Types.Boolean:
+                    return ((JsBoolean) prim)._value ? BigInteger.One : BigInteger.Zero;
+                case Types.String:
+                    return StringToBigInt(prim.ToString());
+                default:
+                    ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a " + prim.Type + " to a BigInt");
+                    return BigInteger.One;
+            }
+        }
+
+        internal static BigInteger StringToBigInt(string str)
+        {
+            if (!TryStringToBigInt(str, out var result))
+            {
+                throw new ParserException(" Cannot convert " + str + " to a BigInt");
+            }
+
+            return result;
+        }
+
+        internal static bool TryStringToBigInt(string str, out BigInteger result)
         {
-            ExceptionHelper.ThrowNotImplementedException("BigInt not implemented");
-            return 0;
+            if (string.IsNullOrWhiteSpace(str))
+            {
+                result = BigInteger.Zero;
+                return true;
+            }
+
+            str = str.Trim();
+
+            for (var i = 0; i < str.Length; i++)
+            {
+                var c = str[i];
+                if (!char.IsDigit(c))
+                {
+                    if (i == 0 && (c == '-' || Character.IsDecimalDigit(c)))
+                    {
+                        // ok
+                        continue;
+                    }
+
+                    if (i != 1 && Character.IsHexDigit(c))
+                    {
+                        // ok
+                        continue;
+                    }
+
+                    if (i == 1 && (Character.IsDecimalDigit(c) || c is 'x' or 'X' or 'b' or 'B' or 'o' or 'O'))
+                    {
+                        // allowed, can be probably parsed
+                        continue;
+                    }
+
+                    result = default;
+                    return false;
+                }
+            }
+
+            // check if we can get by using plain parsing
+            if (BigInteger.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
+            {
+                return true;
+            }
+
+            if (str.Length > 2)
+            {
+                if (str.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
+                {
+                    // we get better precision if we don't hit floating point parsing that is performed by Esprima
+#if NETSTANDARD2_1_OR_GREATER
+                    var source = str.AsSpan(2);
+#else
+                    var source = str.Substring(2);
+#endif
+
+                    var c = source[0];
+                    if (c > 7 && Character.IsHexDigit(c))
+                    {
+                        // ensure we get positive number
+                        source = "0" + source.ToString();
+                    }
+
+                    if (BigInteger.TryParse(source, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out result))
+                    {
+                        return true;
+                    }
+                }
+                else if (str.StartsWith("0o", StringComparison.OrdinalIgnoreCase) && Character.IsOctalDigit(str[2]))
+                {
+                    // try parse large octal
+                    var bigInteger = new BigInteger();
+                    for (var i = 2; i < str.Length; i++)
+                    {
+                        var c = str[i];
+                        if (!Character.IsHexDigit(c))
+                        {
+                            return false;
+                        }
+                        bigInteger = bigInteger * 8 + c - '0';
+                    }
+
+                    result = bigInteger;
+                    return true;
+                }
+                else if (str.StartsWith("0b", StringComparison.OrdinalIgnoreCase) && Character.IsDecimalDigit(str[2]))
+                {
+                    // try parse large binary
+                    var bigInteger = new BigInteger();
+                    for (var i = 2; i < str.Length; i++)
+                    {
+                        var c = str[i];
+
+                        if (c != '0' && c != '1')
+                        {
+                            // not good
+                            return false;
+                        }
+
+                        bigInteger <<= 1;
+                        bigInteger += c == '1' ? 1 : 0;
+                    }
+
+                    result = bigInteger;
+                    return true;
+                }
+            }
+
+            return false;
         }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-tobigint64
         /// </summary>
-        internal static double ToBigInt64(JsValue value)
+        internal static long ToBigInt64(BigInteger value)
         {
-            ExceptionHelper.ThrowNotImplementedException("BigInt not implemented");
-            return 0;
+            var int64bit = BigIntegerModulo(value, BigInteger.Pow(2, 64));
+            if (int64bit >= BigInteger.Pow(2, 63))
+            {
+                return (long) (int64bit - BigInteger.Pow(2, 64));
+            }
+            return (long) int64bit;
         }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-tobiguint64
         /// </summary>
-        internal static double ToBigUint64(JsValue value)
+        internal static ulong ToBigUint64(BigInteger value)
         {
-            ExceptionHelper.ThrowNotImplementedException("BigInt not implemented");
-            return 0;
+            return (ulong) BigIntegerModulo(value, BigInteger.Pow(2, 64));
         }
 
+        /// <summary>
+        /// Implements the JS spec modulo operation as expected.
+        /// </summary>
+        internal static BigInteger BigIntegerModulo(BigInteger a, BigInteger n)
+        {
+            return (a %= n) < 0 && n > 0 || a > 0 && n < 0 ? a + n : a;
+        }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-canonicalnumericindexstring
@@ -679,6 +851,12 @@ namespace Jint.Runtime
             return NumberPrototype.NumberToString(d, new DtoaBuilder(17), stringBuilder.Builder);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static string ToString(BigInteger bigInteger)
+        {
+            return bigInteger.ToString();
+        }
+
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/6.0/#sec-topropertykey
         /// </summary>
@@ -732,6 +910,8 @@ namespace Jint.Runtime
                     return ToString((int) ((JsNumber) o)._value);
                 case InternalTypes.Number:
                     return ToString(((JsNumber) o)._value);
+                case InternalTypes.BigInt:
+                    return ToString(((JsBigInt) o)._value);
                 case InternalTypes.Symbol:
                     ExceptionHelper.ThrowTypeErrorNoEngine("Cannot convert a Symbol value to a string");
                     return null;
@@ -739,8 +919,6 @@ namespace Jint.Runtime
                     return Undefined.Text;
                 case InternalTypes.Null:
                     return Null.Text;
-                case InternalTypes.Object when o is IPrimitiveInstance p:
-                    return ToString(ToPrimitive(p.PrimitiveValue, Types.String));
                 case InternalTypes.Object when o is IObjectWrapper p:
                     return p.Target?.ToString();
                 default:
@@ -759,6 +937,17 @@ namespace Jint.Runtime
             return ToObjectNonObject(realm, value);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-isintegralnumber
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool IsIntegralNumber(double value)
+        {
+            return !double.IsNaN(value)
+                   && !double.IsInfinity(value)
+                   && Math.Floor(Math.Abs(value)) == Math.Abs(value);
+        }
+
         private static ObjectInstance ToObjectNonObject(Realm realm, JsValue value)
         {
             var type = value._type & ~InternalTypes.InternalFlags;
@@ -769,6 +958,8 @@ namespace Jint.Runtime
                 case InternalTypes.Number:
                 case InternalTypes.Integer:
                     return realm.Intrinsics.Number.Construct((JsNumber) value);
+                case InternalTypes.BigInt:
+                    return realm.Intrinsics.BigInt.Construct((JsBigInt) value);
                 case InternalTypes.String:
                     return realm.Intrinsics.String.Construct(value.ToString());
                 case InternalTypes.Symbol:

+ 1 - 1
README.md

@@ -70,7 +70,7 @@ The entire execution engine was rebuild with performance in mind, in many cases
 
 #### ECMAScript 2020
 
--  BigInt
+-  BigInt
 - ✔ `globalThis` object
 - ✔ Nullish coalescing operator (`??`)