Title: [260863] trunk
Revision
260863
Author
[email protected]
Date
2020-04-28 20:54:56 -0700 (Tue, 28 Apr 2020)

Log Message

[JSC] BigInt constructor should accept larger integers than safe-integers
https://bugs.webkit.org/show_bug.cgi?id=210755

Reviewed by Darin Adler.

JSTests:

* stress/big-int-constructor.js:
* stress/large-number-to-bigint.js: Added.
(shouldBe):
(shouldThrow):
* test262/config.yaml:

Source/_javascript_Core:

While our implementation of BigInt constructor only accepts safe integers, it should accept all integers.
This patch implements it by creating JSBigInt::createFrom(double). We port double bit processing part from
V8 as the same to the other part of JSBigInt.

* runtime/BigIntConstructor.cpp:
(JSC::callBigIntConstructor):
* runtime/JSBigInt.cpp:
(JSC::JSBigInt::createFrom):
* runtime/JSBigInt.h:
* runtime/MathCommon.h:
(JSC::isInteger):
(JSC::isSafeInteger):
* runtime/NumberConstructor.cpp:
(JSC::numberConstructorFuncIsSafeInteger):
* runtime/NumberConstructor.h:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (260862 => 260863)


--- trunk/JSTests/ChangeLog	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/JSTests/ChangeLog	2020-04-29 03:54:56 UTC (rev 260863)
@@ -1,5 +1,18 @@
 2020-04-28  Yusuke Suzuki  <[email protected]>
 
+        [JSC] BigInt constructor should accept larger integers than safe-integers
+        https://bugs.webkit.org/show_bug.cgi?id=210755
+
+        Reviewed by Darin Adler.
+
+        * stress/big-int-constructor.js:
+        * stress/large-number-to-bigint.js: Added.
+        (shouldBe):
+        (shouldThrow):
+        * test262/config.yaml:
+
+2020-04-28  Yusuke Suzuki  <[email protected]>
+
         [JSC] NumberConstructor should accept BigInt
         https://bugs.webkit.org/show_bug.cgi?id=210835
 

Modified: trunk/JSTests/stress/big-int-constructor.js (260862 => 260863)


--- trunk/JSTests/stress/big-int-constructor.js	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/JSTests/stress/big-int-constructor.js	2020-04-29 03:54:56 UTC (rev 260863)
@@ -247,7 +247,6 @@
 assertThrowTypeError(Symbol("a"));
 assertThrowRangeError(0.5);
 assertThrowRangeError(-.5);
-assertThrowRangeError(9007199254740992);
 assertThrowRangeError(Infinity);
 assertThrowRangeError(-Infinity);
 assertThrowRangeError(NaN);

Added: trunk/JSTests/stress/large-number-to-bigint.js (0 => 260863)


--- trunk/JSTests/stress/large-number-to-bigint.js	                        (rev 0)
+++ trunk/JSTests/stress/large-number-to-bigint.js	2020-04-29 03:54:56 UTC (rev 260863)
@@ -0,0 +1,61 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+function shouldThrow(func, errorMessage) {
+    var errorThrown = false;
+    var error = null;
+    try {
+        func();
+    } catch (e) {
+        errorThrown = true;
+        error = e;
+    }
+    if (!errorThrown)
+        throw new Error('not thrown');
+    if (String(error) !== errorMessage)
+        throw new Error(`bad error: ${String(error)}`);
+}
+
+shouldThrow(() => {
+    BigInt(Infinity);
+}, `RangeError: Not an integer`);
+
+shouldThrow(() => {
+    BigInt(-Infinity);
+}, `RangeError: Not an integer`);
+
+shouldThrow(() => {
+    BigInt(-NaN);
+}, `RangeError: Not an integer`);
+
+shouldThrow(() => {
+    BigInt(0.42);
+}, `RangeError: Not an integer`);
+
+shouldThrow(() => {
+    BigInt(-0.42);
+}, `RangeError: Not an integer`);
+
+shouldBe(BigInt(Number.MAX_SAFE_INTEGER), 9007199254740991n);
+shouldBe(BigInt(Number.MIN_SAFE_INTEGER), -9007199254740991n);
+shouldBe(BigInt(Number.MAX_SAFE_INTEGER + 1), 9007199254740992n);
+shouldBe(BigInt(Number.MIN_SAFE_INTEGER - 1), -9007199254740992n);
+shouldBe(BigInt(Number.MAX_SAFE_INTEGER - 1), 9007199254740990n);
+shouldBe(BigInt(Number.MIN_SAFE_INTEGER + 1), -9007199254740990n);
+shouldBe(BigInt(Number.MAX_VALUE), 0xfffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n);
+shouldBe(BigInt(-Number.MAX_VALUE), -0xfffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n);
+
+shouldBe(BigInt(0x1fffffffffffff), 9007199254740991n);
+shouldBe(BigInt(0x1fffffffffffff00), 2305843009213693696n);
+shouldBe(BigInt(0x1fffffffffffff000), 36893488147419099136n);
+shouldBe(BigInt(0b111111111111111111111111111111111111111111111111111110000000000), 9223372036854774784n);
+shouldBe(BigInt(0b1111111111111111111111111111111111111111111111111111100000000000), 18446744073709549568n);
+shouldBe(BigInt(0b11111111111111111111111111111111111111111111111111111000000000000), 36893488147419099136n);
+shouldBe(BigInt(-0x1fffffffffffff), -9007199254740991n);
+shouldBe(BigInt(-0x1fffffffffffff00), -2305843009213693696n);
+shouldBe(BigInt(-0x1fffffffffffff000), -36893488147419099136n);
+shouldBe(BigInt(-0b111111111111111111111111111111111111111111111111111110000000000), -9223372036854774784n);
+shouldBe(BigInt(-0b1111111111111111111111111111111111111111111111111111100000000000), -18446744073709549568n);
+shouldBe(BigInt(-0b11111111111111111111111111111111111111111111111111111000000000000), -36893488147419099136n);

Modified: trunk/JSTests/test262/config.yaml (260862 => 260863)


--- trunk/JSTests/test262/config.yaml	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/JSTests/test262/config.yaml	2020-04-29 03:54:56 UTC (rev 260863)
@@ -158,6 +158,3 @@
     # requires ICU 65 (https://unicode-org.atlassian.net/browse/ICU-20654)
     - test/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js
     - test/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-auto.js
-
-    # Spec is changed. https://github.com/tc39/proposal-bigint/pull/138
-    - test/built-ins/BigInt/constructor-integer.js

Modified: trunk/Source/_javascript_Core/ChangeLog (260862 => 260863)


--- trunk/Source/_javascript_Core/ChangeLog	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-04-29 03:54:56 UTC (rev 260863)
@@ -1,3 +1,26 @@
+2020-04-28  Yusuke Suzuki  <[email protected]>
+
+        [JSC] BigInt constructor should accept larger integers than safe-integers
+        https://bugs.webkit.org/show_bug.cgi?id=210755
+
+        Reviewed by Darin Adler.
+
+        While our implementation of BigInt constructor only accepts safe integers, it should accept all integers.
+        This patch implements it by creating JSBigInt::createFrom(double). We port double bit processing part from
+        V8 as the same to the other part of JSBigInt.
+
+        * runtime/BigIntConstructor.cpp:
+        (JSC::callBigIntConstructor):
+        * runtime/JSBigInt.cpp:
+        (JSC::JSBigInt::createFrom):
+        * runtime/JSBigInt.h:
+        * runtime/MathCommon.h:
+        (JSC::isInteger):
+        (JSC::isSafeInteger):
+        * runtime/NumberConstructor.cpp:
+        (JSC::numberConstructorFuncIsSafeInteger):
+        * runtime/NumberConstructor.h:
+
 2020-04-28  Ross Kirsling  <[email protected]>
 
         [JSC] Align upon the name isCallable instead of isFunction

Modified: trunk/Source/_javascript_Core/runtime/BigIntConstructor.cpp (260862 => 260863)


--- trunk/Source/_javascript_Core/runtime/BigIntConstructor.cpp	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/Source/_javascript_Core/runtime/BigIntConstructor.cpp	2020-04-29 03:54:56 UTC (rev 260863)
@@ -122,13 +122,10 @@
     }
 
     if (primitive.isDouble()) {
-        // FIXME: Accept larger integers than safe-integers.
-        // https://bugs.webkit.org/show_bug.cgi?id=210755
         double number = primitive.asDouble();
-        if (trunc(number) != number || std::abs(number) > maxSafeInteger())
-            return throwVMError(globalObject, scope, createRangeError(globalObject, "Not a safe integer"_s));
-
-        return JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(vm, static_cast<int64_t>(primitive.asDouble())));
+        if (!isInteger(number))
+            return throwVMError(globalObject, scope, createRangeError(globalObject, "Not an integer"_s));
+        return JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(vm, primitive.asDouble()));
     }
 
     RELEASE_AND_RETURN(scope, JSValue::encode(toBigInt(globalObject, primitive)));

Modified: trunk/Source/_javascript_Core/runtime/JSBigInt.cpp (260862 => 260863)


--- trunk/Source/_javascript_Core/runtime/JSBigInt.cpp	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/Source/_javascript_Core/runtime/JSBigInt.cpp	2020-04-29 03:54:56 UTC (rev 260863)
@@ -185,6 +185,76 @@
     return bigInt;
 }
 
+JSBigInt* JSBigInt::createFrom(VM& vm, double value)
+{
+    ASSERT(isInteger(value));
+    if (!value)
+        return createZero(vm);
+
+    bool sign = value < 0; // -0 was already handled above.
+    uint64_t doubleBits = bitwise_cast<uint64_t>(value);
+    int32_t rawExponent = static_cast<int32_t>(doubleBits >> doublePhysicalMantissaSize) & 0x7ff;
+    ASSERT(rawExponent != 0x7ff); // Since value is integer, exponent should not be 0x7ff (full bits, used for infinity etc.).
+    ASSERT(rawExponent >= 0x3ff); // Since value is integer, exponent should be >= 0 + bias (0x3ff).
+    int32_t exponent = rawExponent - 0x3ff;
+    int32_t digits = exponent / digitBits + 1;
+    JSBigInt* result = createWithLengthUnchecked(vm, digits);
+    ASSERT(result);
+    result->initialize(InitializationType::WithZero);
+    result->setSign(sign);
+
+    // We construct a BigInt from the double value by shifting its mantissa
+    // according to its exponent and mapping the bit pattern onto digits.
+    //
+    //               <----------- bitlength = exponent + 1 ----------->
+    //                <----- 52 ------> <------ trailing zeroes ------>
+    // mantissa:     1yyyyyyyyyyyyyyyyy 0000000000000000000000000000000
+    // digits:    0001xxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+    //                <-->          <------>
+    //           msdTopBit         digitBits
+    //
+
+    uint64_t mantissa = (doubleBits & doublePhysicalMantissaMask) | doubleMantissaHiddenBit;
+
+    int32_t mantissaTopBit = doubleMantissaSize - 1; // 0-indexed.
+    // 0-indexed position of most significant bit in the most significant digit.
+    int32_t msdTopBit = exponent % digitBits;
+    // Number of unused bits in mantissa. We'll keep them shifted to the
+    // left (i.e. most significant part) of the underlying uint64_t.
+    int32_t remainingMantissaBits = 0;
+    // Next digit under construction.
+    Digit digit = 0;
+
+    // First, build the MSD by shifting the mantissa appropriately.
+    if (msdTopBit < mantissaTopBit) {
+        remainingMantissaBits = mantissaTopBit - msdTopBit;
+        digit = mantissa >> remainingMantissaBits;
+        mantissa = mantissa << (64 - remainingMantissaBits);
+    } else {
+        ASSERT(msdTopBit >= mantissaTopBit);
+        digit = mantissa << (msdTopBit - mantissaTopBit);
+        mantissa = 0;
+    }
+    result->setDigit(digits - 1, digit);
+    // Then fill in the rest of the digits.
+    for (int32_t digitIndex = digits - 2; digitIndex >= 0; digitIndex--) {
+        if (remainingMantissaBits > 0) {
+            remainingMantissaBits -= digitBits;
+            if constexpr (sizeof(Digit) == 4) {
+                digit = mantissa >> 32;
+                mantissa = mantissa << 32;
+            } else {
+                ASSERT(sizeof(Digit) == 8);
+                digit = mantissa;
+                mantissa = 0;
+            }
+        } else
+            digit = 0;
+        result->setDigit(digitIndex, digit);
+    }
+    return result->rightTrim(vm);
+}
+
 JSValue JSBigInt::toPrimitive(JSGlobalObject*, PreferredPrimitiveType) const
 {
     return const_cast<JSBigInt*>(this);

Modified: trunk/Source/_javascript_Core/runtime/JSBigInt.h (260862 => 260863)


--- trunk/Source/_javascript_Core/runtime/JSBigInt.h	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/Source/_javascript_Core/runtime/JSBigInt.h	2020-04-29 03:54:56 UTC (rev 260863)
@@ -31,6 +31,7 @@
 #include "ExceptionHelpers.h"
 #include "JSGlobalObject.h"
 #include "JSObject.h"
+#include "MathCommon.h"
 #include <wtf/CagedUniquePtr.h>
 #include <wtf/text/StringBuilder.h>
 #include <wtf/text/StringView.h>
@@ -72,6 +73,7 @@
     static JSBigInt* createFrom(VM&, uint32_t value);
     static JSBigInt* createFrom(VM&, int64_t value);
     static JSBigInt* createFrom(VM&, bool value);
+    static JSBigInt* createFrom(VM&, double value);
 
     static size_t offsetOfLength()
     {
@@ -93,9 +95,15 @@
         if (value <= INT_MAX && value >= INT_MIN)
             return jsBigInt32(static_cast<int32_t>(value));
 #endif
+        return JSBigInt::createFrom(vm, value);
+    }
 
-        auto ptr = JSBigInt::createFrom(vm, value);
-        return JSValue(static_cast<JSCell*>(ptr));
+    static JSValue makeHeapBigIntOrBigInt32(VM& vm, double value)
+    {
+        ASSERT(isInteger(value));
+        if (std::abs(value) <= maxSafeInteger())
+            return makeHeapBigIntOrBigInt32(vm, static_cast<int64_t>(value));
+        return JSBigInt::createFrom(vm, value);
     }
 
     enum class ErrorParseMode {
@@ -417,7 +425,10 @@
     static constexpr Digit halfDigitMask = (1ull << halfDigitBits) - 1;
     static constexpr int maxInt = 0x7FFFFFFF;
 
-    static constexpr unsigned doublePhysicalMantissaSize = 52;
+    static constexpr unsigned doubleMantissaSize = 53;
+    static constexpr unsigned doublePhysicalMantissaSize = 52; // Excluding hidden-bit.
+    static constexpr uint64_t doublePhysicalMantissaMask = (1ULL << doublePhysicalMantissaSize) - 1;
+    static constexpr uint64_t doubleMantissaHiddenBit = 1ULL << doublePhysicalMantissaSize;
     
     // The maximum length that the current implementation supports would be
     // maxInt / digitBits. However, we use a lower limit for now, because

Modified: trunk/Source/_javascript_Core/runtime/MathCommon.h (260862 => 260863)


--- trunk/Source/_javascript_Core/runtime/MathCommon.h	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/Source/_javascript_Core/runtime/MathCommon.h	2020-04-29 03:54:56 UTC (rev 260863)
@@ -48,6 +48,21 @@
     return -9007199254740991.0;
 }
 
+inline bool isInteger(double value)
+{
+    return std::isfinite(value) && std::trunc(value) == value;
+}
+
+inline bool isInteger(float value)
+{
+    return std::isfinite(value) && std::trunc(value) == value;
+}
+
+inline bool isSafeInteger(double value)
+{
+    return std::trunc(value) == value && std::abs(value) <= maxSafeInteger();
+}
+
 // This in the ToInt32 operation is defined in section 9.5 of the ECMA-262 spec.
 // Note that this operation is identical to ToUInt32 other than to interpretation
 // of the resulting bit-pattern (as such this method is also called to implement

Modified: trunk/Source/_javascript_Core/runtime/NumberConstructor.cpp (260862 => 260863)


--- trunk/Source/_javascript_Core/runtime/NumberConstructor.cpp	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/Source/_javascript_Core/runtime/NumberConstructor.cpp	2020-04-29 03:54:56 UTC (rev 260863)
@@ -140,16 +140,11 @@
 static EncodedJSValue JSC_HOST_CALL numberConstructorFuncIsSafeInteger(JSGlobalObject*, CallFrame* callFrame)
 {
     JSValue argument = callFrame->argument(0);
-    bool isInteger;
     if (argument.isInt32())
-        isInteger = true;
-    else if (!argument.isDouble())
-        isInteger = false;
-    else {
-        double number = argument.asDouble();
-        isInteger = trunc(number) == number && std::abs(number) <= maxSafeInteger();
-    }
-    return JSValue::encode(jsBoolean(isInteger));
+        return JSValue::encode(jsBoolean(true));
+    if (!argument.isDouble())
+        return JSValue::encode(jsBoolean(false));
+    return JSValue::encode(jsBoolean(isSafeInteger(argument.asDouble())));
 }
 
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/NumberConstructor.h (260862 => 260863)


--- trunk/Source/_javascript_Core/runtime/NumberConstructor.h	2020-04-29 03:13:28 UTC (rev 260862)
+++ trunk/Source/_javascript_Core/runtime/NumberConstructor.h	2020-04-29 03:54:56 UTC (rev 260863)
@@ -21,6 +21,7 @@
 #pragma once
 
 #include "InternalFunction.h"
+#include "MathCommon.h"
 
 namespace JSC {
 
@@ -48,13 +49,7 @@
 
     static bool isIntegerImpl(JSValue value)
     {
-        if (value.isInt32())
-            return true;
-        if (!value.isDouble())
-            return false;
-
-        double number = value.asDouble();
-        return std::isfinite(number) && trunc(number) == number;
+        return value.isInt32() || (value.isDouble() && isInteger(value.asDouble()));
     }
 
 private:
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to