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: