js/public/Conversions.h
author Kai Engert <kaie@kuix.de>
Thu, 18 Jan 2018 14:02:42 +0100
changeset 399814 2e91a03fa6b8e72435aed05993dff55ec4ce0047
parent 318287 1190abb16e18347ca1640a6ae90c3ab4de38e73b
child 401021 816c9ead4b0c87ae9d9d6138a8de35d8c1436928
permissions -rw-r--r--
Bug 1420407, uplift NSPR_4_18_RTM, no code change, only version number. Require version 4.18 for building. r=me UPGRADE_NSPR_RELEASE

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ECMAScript conversion operations. */

#ifndef js_Conversions_h
#define js_Conversions_h

#include "mozilla/Casting.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/TypeTraits.h"

#include <math.h>

#include "jspubtd.h"

#include "js/RootingAPI.h"
#include "js/Value.h"

struct JSContext;

namespace js {

/* DO NOT CALL THIS. Use JS::ToBoolean. */
extern JS_PUBLIC_API(bool)
ToBooleanSlow(JS::HandleValue v);

/* DO NOT CALL THIS.  Use JS::ToNumber. */
extern JS_PUBLIC_API(bool)
ToNumberSlow(JSContext* cx, JS::HandleValue v, double* dp);

/* DO NOT CALL THIS. Use JS::ToInt8. */
extern JS_PUBLIC_API(bool)
ToInt8Slow(JSContext *cx, JS::HandleValue v, int8_t *out);

/* DO NOT CALL THIS. Use JS::ToUint8. */
extern JS_PUBLIC_API(bool)
ToUint8Slow(JSContext *cx, JS::HandleValue v, uint8_t *out);

/* DO NOT CALL THIS. Use JS::ToInt16. */
extern JS_PUBLIC_API(bool)
ToInt16Slow(JSContext *cx, JS::HandleValue v, int16_t *out);

/* DO NOT CALL THIS. Use JS::ToInt32. */
extern JS_PUBLIC_API(bool)
ToInt32Slow(JSContext* cx, JS::HandleValue v, int32_t* out);

/* DO NOT CALL THIS. Use JS::ToUint32. */
extern JS_PUBLIC_API(bool)
ToUint32Slow(JSContext* cx, JS::HandleValue v, uint32_t* out);

/* DO NOT CALL THIS. Use JS::ToUint16. */
extern JS_PUBLIC_API(bool)
ToUint16Slow(JSContext* cx, JS::HandleValue v, uint16_t* out);

/* DO NOT CALL THIS. Use JS::ToInt64. */
extern JS_PUBLIC_API(bool)
ToInt64Slow(JSContext* cx, JS::HandleValue v, int64_t* out);

/* DO NOT CALL THIS. Use JS::ToUint64. */
extern JS_PUBLIC_API(bool)
ToUint64Slow(JSContext* cx, JS::HandleValue v, uint64_t* out);

/* DO NOT CALL THIS. Use JS::ToString. */
extern JS_PUBLIC_API(JSString*)
ToStringSlow(JSContext* cx, JS::HandleValue v);

/* DO NOT CALL THIS. Use JS::ToObject. */
extern JS_PUBLIC_API(JSObject*)
ToObjectSlow(JSContext* cx, JS::HandleValue v, bool reportScanStack);

} // namespace js

namespace JS {

namespace detail {

#ifdef JS_DEBUG
/**
 * Assert that we're not doing GC on cx, that we're in a request as
 * needed, and that the compartments for cx and v are correct.
 * Also check that GC would be safe at this point.
 */
extern JS_PUBLIC_API(void)
AssertArgumentsAreSane(JSContext* cx, HandleValue v);
#else
inline void AssertArgumentsAreSane(JSContext* cx, HandleValue v)
{}
#endif /* JS_DEBUG */

} // namespace detail

/**
 * ES6 draft 20141224, 7.1.1, second algorithm.
 *
 * Most users shouldn't call this -- use JS::ToBoolean, ToNumber, or ToString
 * instead.  This will typically only be called from custom convert hooks that
 * wish to fall back to the ES6 default conversion behavior shared by most
 * objects in JS, codified as OrdinaryToPrimitive.
 */
extern JS_PUBLIC_API(bool)
OrdinaryToPrimitive(JSContext* cx, HandleObject obj, JSType type, MutableHandleValue vp);

/* ES6 draft 20141224, 7.1.2. */
MOZ_ALWAYS_INLINE bool
ToBoolean(HandleValue v)
{
    if (v.isBoolean())
        return v.toBoolean();
    if (v.isInt32())
        return v.toInt32() != 0;
    if (v.isNullOrUndefined())
        return false;
    if (v.isDouble()) {
        double d = v.toDouble();
        return !mozilla::IsNaN(d) && d != 0;
    }
    if (v.isSymbol())
        return true;

    /* The slow path handles strings and objects. */
    return js::ToBooleanSlow(v);
}

/* ES6 draft 20141224, 7.1.3. */
MOZ_ALWAYS_INLINE bool
ToNumber(JSContext* cx, HandleValue v, double* out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isNumber()) {
        *out = v.toNumber();
        return true;
    }
    return js::ToNumberSlow(cx, v, out);
}

/* ES6 draft 20141224, ToInteger (specialized for doubles). */
inline double
ToInteger(double d)
{
    if (d == 0)
        return d;

    if (!mozilla::IsFinite(d)) {
        if (mozilla::IsNaN(d))
            return 0;
        return d;
    }

    return d < 0 ? ceil(d) : floor(d);
}

/* ES6 draft 20141224, 7.1.5. */
MOZ_ALWAYS_INLINE bool
ToInt32(JSContext* cx, JS::HandleValue v, int32_t* out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = v.toInt32();
        return true;
    }
    return js::ToInt32Slow(cx, v, out);
}

/* ES6 draft 20141224, 7.1.6. */
MOZ_ALWAYS_INLINE bool
ToUint32(JSContext* cx, HandleValue v, uint32_t* out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = uint32_t(v.toInt32());
        return true;
    }
    return js::ToUint32Slow(cx, v, out);
}

/* ES6 draft 20141224, 7.1.7. */
MOZ_ALWAYS_INLINE bool
ToInt16(JSContext *cx, JS::HandleValue v, int16_t *out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = int16_t(v.toInt32());
        return true;
    }
    return js::ToInt16Slow(cx, v, out);
}

/* ES6 draft 20141224, 7.1.8. */
MOZ_ALWAYS_INLINE bool
ToUint16(JSContext* cx, HandleValue v, uint16_t* out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = uint16_t(v.toInt32());
        return true;
    }
    return js::ToUint16Slow(cx, v, out);
}

/* ES6 draft 20141224, 7.1.9 */
MOZ_ALWAYS_INLINE bool
ToInt8(JSContext *cx, JS::HandleValue v, int8_t *out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = int8_t(v.toInt32());
        return true;
    }
    return js::ToInt8Slow(cx, v, out);
}

/* ES6 ECMA-262, 7.1.10 */
MOZ_ALWAYS_INLINE bool
ToUint8(JSContext *cx, JS::HandleValue v, uint8_t *out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = uint8_t(v.toInt32());
        return true;
    }
    return js::ToUint8Slow(cx, v, out);
}

/*
 * Non-standard, with behavior similar to that of ToInt32, except in its
 * producing an int64_t.
 */
MOZ_ALWAYS_INLINE bool
ToInt64(JSContext* cx, HandleValue v, int64_t* out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = int64_t(v.toInt32());
        return true;
    }
    return js::ToInt64Slow(cx, v, out);
}

/*
 * Non-standard, with behavior similar to that of ToUint32, except in its
 * producing a uint64_t.
 */
MOZ_ALWAYS_INLINE bool
ToUint64(JSContext* cx, HandleValue v, uint64_t* out)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isInt32()) {
        *out = uint64_t(v.toInt32());
        return true;
    }
    return js::ToUint64Slow(cx, v, out);
}

/* ES6 draft 20141224, 7.1.12. */
MOZ_ALWAYS_INLINE JSString*
ToString(JSContext* cx, HandleValue v)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isString())
        return v.toString();
    return js::ToStringSlow(cx, v);
}

/* ES6 draft 20141224, 7.1.13. */
inline JSObject*
ToObject(JSContext* cx, HandleValue v)
{
    detail::AssertArgumentsAreSane(cx, v);

    if (v.isObject())
        return &v.toObject();
    return js::ToObjectSlow(cx, v, false);
}

namespace detail {

/*
 * Convert a double value to ResultType (an unsigned integral type) using
 * ECMAScript-style semantics (that is, in like manner to how ECMAScript's
 * ToInt32 converts to int32_t).
 *
 *   If d is infinite or NaN, return 0.
 *   Otherwise compute d2 = sign(d) * floor(abs(d)), and return the ResultType
 *   value congruent to d2 mod 2**(bit width of ResultType).
 *
 * The algorithm below is inspired by that found in
 * <http://trac.webkit.org/changeset/67825/trunk/JavaScriptCore/runtime/JSValue.cpp>
 * but has been generalized to all integer widths.
 */
template<typename ResultType>
inline ResultType
ToUintWidth(double d)
{
    static_assert(mozilla::IsUnsigned<ResultType>::value,
                  "ResultType must be an unsigned type");

    uint64_t bits = mozilla::BitwiseCast<uint64_t>(d);
    unsigned DoubleExponentShift = mozilla::FloatingPoint<double>::kExponentShift;

    // Extract the exponent component.  (Be careful here!  It's not technically
    // the exponent in NaN, infinities, and subnormals.)
    int_fast16_t exp =
        int_fast16_t((bits & mozilla::FloatingPoint<double>::kExponentBits) >> DoubleExponentShift) -
        int_fast16_t(mozilla::FloatingPoint<double>::kExponentBias);

    // If the exponent's less than zero, abs(d) < 1, so the result is 0.  (This
    // also handles subnormals.)
    if (exp < 0)
        return 0;

    uint_fast16_t exponent = mozilla::AssertedCast<uint_fast16_t>(exp);

    // If the exponent is greater than or equal to the bits of precision of a
    // double plus ResultType's width, the number is either infinite, NaN, or
    // too large to have lower-order bits in the congruent value.  (Example:
    // 2**84 is exactly representable as a double.  The next exact double is
    // 2**84 + 2**32.  Thus if ResultType is int32_t, an exponent >= 84 implies
    // floor(abs(d)) == 0 mod 2**32.)  Return 0 in all these cases.
    const size_t ResultWidth = CHAR_BIT * sizeof(ResultType);
    if (exponent >= DoubleExponentShift + ResultWidth)
        return 0;

    // The significand contains the bits that will determine the final result.
    // Shift those bits left or right, according to the exponent, to their
    // locations in the unsigned binary representation of floor(abs(d)).
    static_assert(sizeof(ResultType) <= sizeof(uint64_t),
                  "Left-shifting below would lose upper bits");
    ResultType result = (exponent > DoubleExponentShift)
                        ? ResultType(bits << (exponent - DoubleExponentShift))
                        : ResultType(bits >> (DoubleExponentShift - exponent));

    // Two further complications remain.  First, |result| may contain bogus
    // sign/exponent bits.  Second, IEEE-754 numbers' significands (excluding
    // subnormals, but we already handled those) have an implicit leading 1
    // which may affect the final result.
    //
    // It may appear that there's complexity here depending on how ResultWidth
    // and DoubleExponentShift relate, but it turns out there's not.
    //
    // Assume ResultWidth < DoubleExponentShift:
    //   Only right-shifts leave bogus bits in |result|.  For this to happen,
    //   we must right-shift by > |DoubleExponentShift - ResultWidth|, implying
    //   |exponent < ResultWidth|.
    //   The implicit leading bit only matters if it appears in the final
    //   result -- if |2**exponent mod 2**ResultWidth != 0|.  This implies
    //   |exponent < ResultWidth|.
    // Otherwise assume ResultWidth >= DoubleExponentShift:
    //   Any left-shift less than |ResultWidth - DoubleExponentShift| leaves
    //   bogus bits in |result|.  This implies |exponent < ResultWidth|.  Any
    //   right-shift less than |ResultWidth| does too, which implies
    //   |DoubleExponentShift - ResultWidth < exponent|.  By assumption, then,
    //   |exponent| is negative, but we excluded that above.  So bogus bits
    //   need only |exponent < ResultWidth|.
    //   The implicit leading bit matters identically to the other case, so
    //   again, |exponent < ResultWidth|.
    if (exponent < ResultWidth) {
        ResultType implicitOne = ResultType(1) << exponent;
        result &= implicitOne - 1; // remove bogus bits
        result += implicitOne; // add the implicit bit
    }

    // Compute the congruent value in the signed range.
    return (bits & mozilla::FloatingPoint<double>::kSignBit) ? ~result + 1 : result;
}

template<typename ResultType>
inline ResultType
ToIntWidth(double d)
{
    static_assert(mozilla::IsSigned<ResultType>::value,
                  "ResultType must be a signed type");

    const ResultType MaxValue = (1ULL << (CHAR_BIT * sizeof(ResultType) - 1)) - 1;
    const ResultType MinValue = -MaxValue - 1;

    typedef typename mozilla::MakeUnsigned<ResultType>::Type UnsignedResult;
    UnsignedResult u = ToUintWidth<UnsignedResult>(d);
    if (u <= UnsignedResult(MaxValue))
        return static_cast<ResultType>(u);
    return (MinValue + static_cast<ResultType>(u - MaxValue)) - 1;
}

} // namespace detail

/* ES5 9.5 ToInt32 (specialized for doubles). */
inline int32_t
ToInt32(double d)
{
    // clang crashes compiling this when targeting arm:
    // https://llvm.org/bugs/show_bug.cgi?id=22974
#if defined (__arm__) && defined (__GNUC__) && !defined(__clang__)
    int32_t i;
    uint32_t    tmp0;
    uint32_t    tmp1;
    uint32_t    tmp2;
    asm (
    // We use a pure integer solution here. In the 'softfp' ABI, the argument
    // will start in r0 and r1, and VFP can't do all of the necessary ECMA
    // conversions by itself so some integer code will be required anyway. A
    // hybrid solution is faster on A9, but this pure integer solution is
    // notably faster for A8.

    // %0 is the result register, and may alias either of the %[QR]1 registers.
    // %Q4 holds the lower part of the mantissa.
    // %R4 holds the sign, exponent, and the upper part of the mantissa.
    // %1, %2 and %3 are used as temporary values.

    // Extract the exponent.
"   mov     %1, %R4, LSR #20\n"
"   bic     %1, %1, #(1 << 11)\n"  // Clear the sign.

    // Set the implicit top bit of the mantissa. This clobbers a bit of the
    // exponent, but we have already extracted that.
"   orr     %R4, %R4, #(1 << 20)\n"

    // Special Cases
    //   We should return zero in the following special cases:
    //    - Exponent is 0x000 - 1023: +/-0 or subnormal.
    //    - Exponent is 0x7ff - 1023: +/-INFINITY or NaN
    //      - This case is implicitly handled by the standard code path anyway,
    //        as shifting the mantissa up by the exponent will result in '0'.
    //
    // The result is composed of the mantissa, prepended with '1' and
    // bit-shifted left by the (decoded) exponent. Note that because the r1[20]
    // is the bit with value '1', r1 is effectively already shifted (left) by
    // 20 bits, and r0 is already shifted by 52 bits.

    // Adjust the exponent to remove the encoding offset. If the decoded
    // exponent is negative, quickly bail out with '0' as such values round to
    // zero anyway. This also catches +/-0 and subnormals.
"   sub     %1, %1, #0xff\n"
"   subs    %1, %1, #0x300\n"
"   bmi     8f\n"

    //  %1 = (decoded) exponent >= 0
    //  %R4 = upper mantissa and sign

    // ---- Lower Mantissa ----
"   subs    %3, %1, #52\n"         // Calculate exp-52
"   bmi     1f\n"

    // Shift r0 left by exp-52.
    // Ensure that we don't overflow ARM's 8-bit shift operand range.
    // We need to handle anything up to an 11-bit value here as we know that
    // 52 <= exp <= 1024 (0x400). Any shift beyond 31 bits results in zero
    // anyway, so as long as we don't touch the bottom 5 bits, we can use
    // a logical OR to push long shifts into the 32 <= (exp&0xff) <= 255 range.
"   bic     %2, %3, #0xff\n"
"   orr     %3, %3, %2, LSR #3\n"
    // We can now perform a straight shift, avoiding the need for any
    // conditional instructions or extra branches.
"   mov     %Q4, %Q4, LSL %3\n"
"   b       2f\n"
"1:\n" // Shift r0 right by 52-exp.
    // We know that 0 <= exp < 52, and we can shift up to 255 bits so 52-exp
    // will always be a valid shift and we can sk%3 the range check for this case.
"   rsb     %3, %1, #52\n"
"   mov     %Q4, %Q4, LSR %3\n"

    //  %1 = (decoded) exponent
    //  %R4 = upper mantissa and sign
    //  %Q4 = partially-converted integer

"2:\n"
    // ---- Upper Mantissa ----
    // This is much the same as the lower mantissa, with a few different
    // boundary checks and some masking to hide the exponent & sign bit in the
    // upper word.
    // Note that the upper mantissa is pre-shifted by 20 in %R4, but we shift
    // it left more to remove the sign and exponent so it is effectively
    // pre-shifted by 31 bits.
"   subs    %3, %1, #31\n"          // Calculate exp-31
"   mov     %1, %R4, LSL #11\n"     // Re-use %1 as a temporary register.
"   bmi     3f\n"

    // Shift %R4 left by exp-31.
    // Avoid overflowing the 8-bit shift range, as before.
"   bic     %2, %3, #0xff\n"
"   orr     %3, %3, %2, LSR #3\n"
    // Perform the shift.
"   mov     %2, %1, LSL %3\n"
"   b       4f\n"
"3:\n" // Shift r1 right by 31-exp.
    // We know that 0 <= exp < 31, and we can shift up to 255 bits so 31-exp
    // will always be a valid shift and we can skip the range check for this case.
"   rsb     %3, %3, #0\n"          // Calculate 31-exp from -(exp-31)
"   mov     %2, %1, LSR %3\n"      // Thumb-2 can't do "LSR %3" in "orr".

    //  %Q4 = partially-converted integer (lower)
    //  %R4 = upper mantissa and sign
    //  %2 = partially-converted integer (upper)

"4:\n"
    // Combine the converted parts.
"   orr     %Q4, %Q4, %2\n"
    // Negate the result if we have to, and move it to %0 in the process. To
    // avoid conditionals, we can do this by inverting on %R4[31], then adding
    // %R4[31]>>31.
"   eor     %Q4, %Q4, %R4, ASR #31\n"
"   add     %0, %Q4, %R4, LSR #31\n"
"   b       9f\n"
"8:\n"
    // +/-INFINITY, +/-0, subnormals, NaNs, and anything else out-of-range that
    // will result in a conversion of '0'.
"   mov     %0, #0\n"
"9:\n"
    : "=r" (i), "=&r" (tmp0), "=&r" (tmp1), "=&r" (tmp2), "=&r" (d)
    : "4" (d)
    : "cc"
        );
    return i;
#else
    return detail::ToIntWidth<int32_t>(d);
#endif
}

/* ES5 9.6 (specialized for doubles). */
inline uint32_t
ToUint32(double d)
{
    return detail::ToUintWidth<uint32_t>(d);
}

/* WEBIDL 4.2.4 */
inline int8_t
ToInt8(double d)
{
    return detail::ToIntWidth<int8_t>(d);
}

/* ECMA-262 7.1.10 ToUInt8() specialized for doubles. */
inline int8_t
ToUint8(double d)
{
    return detail::ToUintWidth<uint8_t>(d);
}

/* WEBIDL 4.2.6 */
inline int16_t
ToInt16(double d)
{
    return detail::ToIntWidth<int16_t>(d);
}

inline uint16_t
ToUint16(double d)
{
    return detail::ToUintWidth<uint16_t>(d);
}

/* WEBIDL 4.2.10 */
inline int64_t
ToInt64(double d)
{
    return detail::ToIntWidth<int64_t>(d);
}

/* WEBIDL 4.2.11 */
inline uint64_t
ToUint64(double d)
{
    return detail::ToUintWidth<uint64_t>(d);
}

} // namespace JS

#endif /* js_Conversions_h */