Changeset 260863 in webkit


Ignore:
Timestamp:
Apr 28, 2020 8:54:56 PM (4 years ago)
Author:
ysuzuki@apple.com
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/JavaScriptCore:

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:
Location:
trunk
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r260834 r260863  
     12020-04-28  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] BigInt constructor should accept larger integers than safe-integers
     4        https://bugs.webkit.org/show_bug.cgi?id=210755
     5
     6        Reviewed by Darin Adler.
     7
     8        * stress/big-int-constructor.js:
     9        * stress/large-number-to-bigint.js: Added.
     10        (shouldBe):
     11        (shouldThrow):
     12        * test262/config.yaml:
     13
    1142020-04-28  Yusuke Suzuki  <ysuzuki@apple.com>
    215
  • trunk/JSTests/stress/big-int-constructor.js

    r227004 r260863  
    248248assertThrowRangeError(0.5);
    249249assertThrowRangeError(-.5);
    250 assertThrowRangeError(9007199254740992);
    251250assertThrowRangeError(Infinity);
    252251assertThrowRangeError(-Infinity);
  • trunk/JSTests/test262/config.yaml

    r260591 r260863  
    159159    - test/intl402/RelativeTimeFormat/prototype/format/en-us-numeric-auto.js
    160160    - test/intl402/RelativeTimeFormat/prototype/formatToParts/en-us-numeric-auto.js
    161 
    162     # Spec is changed. https://github.com/tc39/proposal-bigint/pull/138
    163     - test/built-ins/BigInt/constructor-integer.js
  • trunk/Source/JavaScriptCore/ChangeLog

    r260848 r260863  
     12020-04-28  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] BigInt constructor should accept larger integers than safe-integers
     4        https://bugs.webkit.org/show_bug.cgi?id=210755
     5
     6        Reviewed by Darin Adler.
     7
     8        While our implementation of BigInt constructor only accepts safe integers, it should accept all integers.
     9        This patch implements it by creating JSBigInt::createFrom(double). We port double bit processing part from
     10        V8 as the same to the other part of JSBigInt.
     11
     12        * runtime/BigIntConstructor.cpp:
     13        (JSC::callBigIntConstructor):
     14        * runtime/JSBigInt.cpp:
     15        (JSC::JSBigInt::createFrom):
     16        * runtime/JSBigInt.h:
     17        * runtime/MathCommon.h:
     18        (JSC::isInteger):
     19        (JSC::isSafeInteger):
     20        * runtime/NumberConstructor.cpp:
     21        (JSC::numberConstructorFuncIsSafeInteger):
     22        * runtime/NumberConstructor.h:
     23
    1242020-04-28  Ross Kirsling  <ross.kirsling@sony.com>
    225
  • trunk/Source/JavaScriptCore/runtime/BigIntConstructor.cpp

    r260683 r260863  
    123123
    124124    if (primitive.isDouble()) {
    125         // FIXME: Accept larger integers than safe-integers.
    126         // https://bugs.webkit.org/show_bug.cgi?id=210755
    127125        double number = primitive.asDouble();
    128         if (trunc(number) != number || std::abs(number) > maxSafeInteger())
    129             return throwVMError(globalObject, scope, createRangeError(globalObject, "Not a safe integer"_s));
    130 
    131         return JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(vm, static_cast<int64_t>(primitive.asDouble())));
     126        if (!isInteger(number))
     127            return throwVMError(globalObject, scope, createRangeError(globalObject, "Not an integer"_s));
     128        return JSValue::encode(JSBigInt::makeHeapBigIntOrBigInt32(vm, primitive.asDouble()));
    132129    }
    133130
  • trunk/Source/JavaScriptCore/runtime/JSBigInt.cpp

    r260834 r260863  
    186186}
    187187
     188JSBigInt* JSBigInt::createFrom(VM& vm, double value)
     189{
     190    ASSERT(isInteger(value));
     191    if (!value)
     192        return createZero(vm);
     193
     194    bool sign = value < 0; // -0 was already handled above.
     195    uint64_t doubleBits = bitwise_cast<uint64_t>(value);
     196    int32_t rawExponent = static_cast<int32_t>(doubleBits >> doublePhysicalMantissaSize) & 0x7ff;
     197    ASSERT(rawExponent != 0x7ff); // Since value is integer, exponent should not be 0x7ff (full bits, used for infinity etc.).
     198    ASSERT(rawExponent >= 0x3ff); // Since value is integer, exponent should be >= 0 + bias (0x3ff).
     199    int32_t exponent = rawExponent - 0x3ff;
     200    int32_t digits = exponent / digitBits + 1;
     201    JSBigInt* result = createWithLengthUnchecked(vm, digits);
     202    ASSERT(result);
     203    result->initialize(InitializationType::WithZero);
     204    result->setSign(sign);
     205
     206    // We construct a BigInt from the double value by shifting its mantissa
     207    // according to its exponent and mapping the bit pattern onto digits.
     208    //
     209    //               <----------- bitlength = exponent + 1 ----------->
     210    //                <----- 52 ------> <------ trailing zeroes ------>
     211    // mantissa:     1yyyyyyyyyyyyyyyyy 0000000000000000000000000000000
     212    // digits:    0001xxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
     213    //                <-->          <------>
     214    //           msdTopBit         digitBits
     215    //
     216
     217    uint64_t mantissa = (doubleBits & doublePhysicalMantissaMask) | doubleMantissaHiddenBit;
     218
     219    int32_t mantissaTopBit = doubleMantissaSize - 1; // 0-indexed.
     220    // 0-indexed position of most significant bit in the most significant digit.
     221    int32_t msdTopBit = exponent % digitBits;
     222    // Number of unused bits in mantissa. We'll keep them shifted to the
     223    // left (i.e. most significant part) of the underlying uint64_t.
     224    int32_t remainingMantissaBits = 0;
     225    // Next digit under construction.
     226    Digit digit = 0;
     227
     228    // First, build the MSD by shifting the mantissa appropriately.
     229    if (msdTopBit < mantissaTopBit) {
     230        remainingMantissaBits = mantissaTopBit - msdTopBit;
     231        digit = mantissa >> remainingMantissaBits;
     232        mantissa = mantissa << (64 - remainingMantissaBits);
     233    } else {
     234        ASSERT(msdTopBit >= mantissaTopBit);
     235        digit = mantissa << (msdTopBit - mantissaTopBit);
     236        mantissa = 0;
     237    }
     238    result->setDigit(digits - 1, digit);
     239    // Then fill in the rest of the digits.
     240    for (int32_t digitIndex = digits - 2; digitIndex >= 0; digitIndex--) {
     241        if (remainingMantissaBits > 0) {
     242            remainingMantissaBits -= digitBits;
     243            if constexpr (sizeof(Digit) == 4) {
     244                digit = mantissa >> 32;
     245                mantissa = mantissa << 32;
     246            } else {
     247                ASSERT(sizeof(Digit) == 8);
     248                digit = mantissa;
     249                mantissa = 0;
     250            }
     251        } else
     252            digit = 0;
     253        result->setDigit(digitIndex, digit);
     254    }
     255    return result->rightTrim(vm);
     256}
     257
    188258JSValue JSBigInt::toPrimitive(JSGlobalObject*, PreferredPrimitiveType) const
    189259{
  • trunk/Source/JavaScriptCore/runtime/JSBigInt.h

    r260834 r260863  
    3232#include "JSGlobalObject.h"
    3333#include "JSObject.h"
     34#include "MathCommon.h"
    3435#include <wtf/CagedUniquePtr.h>
    3536#include <wtf/text/StringBuilder.h>
     
    7374    static JSBigInt* createFrom(VM&, int64_t value);
    7475    static JSBigInt* createFrom(VM&, bool value);
     76    static JSBigInt* createFrom(VM&, double value);
    7577
    7678    static size_t offsetOfLength()
     
    9496            return jsBigInt32(static_cast<int32_t>(value));
    9597#endif
    96 
    97         auto ptr = JSBigInt::createFrom(vm, value);
    98         return JSValue(static_cast<JSCell*>(ptr));
     98        return JSBigInt::createFrom(vm, value);
     99    }
     100
     101    static JSValue makeHeapBigIntOrBigInt32(VM& vm, double value)
     102    {
     103        ASSERT(isInteger(value));
     104        if (std::abs(value) <= maxSafeInteger())
     105            return makeHeapBigIntOrBigInt32(vm, static_cast<int64_t>(value));
     106        return JSBigInt::createFrom(vm, value);
    99107    }
    100108
     
    418426    static constexpr int maxInt = 0x7FFFFFFF;
    419427
    420     static constexpr unsigned doublePhysicalMantissaSize = 52;
     428    static constexpr unsigned doubleMantissaSize = 53;
     429    static constexpr unsigned doublePhysicalMantissaSize = 52; // Excluding hidden-bit.
     430    static constexpr uint64_t doublePhysicalMantissaMask = (1ULL << doublePhysicalMantissaSize) - 1;
     431    static constexpr uint64_t doubleMantissaHiddenBit = 1ULL << doublePhysicalMantissaSize;
    421432   
    422433    // The maximum length that the current implementation supports would be
  • trunk/Source/JavaScriptCore/runtime/MathCommon.h

    r259320 r260863  
    4949}
    5050
     51inline bool isInteger(double value)
     52{
     53    return std::isfinite(value) && std::trunc(value) == value;
     54}
     55
     56inline bool isInteger(float value)
     57{
     58    return std::isfinite(value) && std::trunc(value) == value;
     59}
     60
     61inline bool isSafeInteger(double value)
     62{
     63    return std::trunc(value) == value && std::abs(value) <= maxSafeInteger();
     64}
     65
    5166// This in the ToInt32 operation is defined in section 9.5 of the ECMA-262 spec.
    5267// Note that this operation is identical to ToUInt32 other than to interpretation
  • trunk/Source/JavaScriptCore/runtime/NumberConstructor.cpp

    r260834 r260863  
    141141{
    142142    JSValue argument = callFrame->argument(0);
    143     bool isInteger;
    144143    if (argument.isInt32())
    145         isInteger = true;
    146     else if (!argument.isDouble())
    147         isInteger = false;
    148     else {
    149         double number = argument.asDouble();
    150         isInteger = trunc(number) == number && std::abs(number) <= maxSafeInteger();
    151     }
    152     return JSValue::encode(jsBoolean(isInteger));
     144        return JSValue::encode(jsBoolean(true));
     145    if (!argument.isDouble())
     146        return JSValue::encode(jsBoolean(false));
     147    return JSValue::encode(jsBoolean(isSafeInteger(argument.asDouble())));
    153148}
    154149
  • trunk/Source/JavaScriptCore/runtime/NumberConstructor.h

    r260415 r260863  
    2222
    2323#include "InternalFunction.h"
     24#include "MathCommon.h"
    2425
    2526namespace JSC {
     
    4950    static bool isIntegerImpl(JSValue value)
    5051    {
    51         if (value.isInt32())
    52             return true;
    53         if (!value.isDouble())
    54             return false;
    55 
    56         double number = value.asDouble();
    57         return std::isfinite(number) && trunc(number) == number;
     52        return value.isInt32() || (value.isDouble() && isInteger(value.asDouble()));
    5853    }
    5954
Note: See TracChangeset for help on using the changeset viewer.