Bug 892634 - Part 1: Add thread safe number conversion functions. (r=billm)
authorShu-yu Guo <shu@rfrn.org>
Fri, 02 Aug 2013 08:24:57 -0700
changeset 153424 56364871a2887fce84dcb44be001393e1a1f48c2
parent 153423 0834a49b378d6175315b4e78fb9dc340cb039b7e
child 153425 4e12ba2df6a6cf16721c337217e2470dbe2d0745
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs892634
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 892634 - Part 1: Add thread safe number conversion functions. (r=billm)
js/src/ion/CodeGenerator.cpp
js/src/ion/ParallelFunctions.cpp
js/src/jsnum.cpp
js/src/jsnum.h
js/src/jsstr.h
js/src/vm/String.cpp
--- a/js/src/ion/CodeGenerator.cpp
+++ b/js/src/ion/CodeGenerator.cpp
@@ -4033,17 +4033,17 @@ CodeGenerator::visitEmulatesUndefinedAnd
     }
 
     Register objreg = ToRegister(lir->input());
 
     testObjectTruthy(objreg, unequal, equal, ToRegister(lir->temp()), ool);
     return true;
 }
 
-typedef JSString *(*ConcatStringsFn)(JSContext *, HandleString, HandleString);
+typedef JSString *(*ConcatStringsFn)(ThreadSafeContext *, HandleString, HandleString);
 typedef ParallelResult (*ConcatStringsParFn)(ForkJoinSlice *, HandleString, HandleString,
                                              MutableHandleString);
 static const VMFunctionsModal ConcatStringsInfo = VMFunctionsModal(
     FunctionInfo<ConcatStringsFn>(ConcatStrings<CanGC>),
     FunctionInfo<ConcatStringsParFn>(ConcatStringsPar));
 
 bool
 CodeGenerator::emitConcat(LInstruction *lir, Register lhs, Register rhs, Register output)
--- a/js/src/ion/ParallelFunctions.cpp
+++ b/js/src/ion/ParallelFunctions.cpp
@@ -186,17 +186,17 @@ ion::ExtendArrayPar(ForkJoinSlice *slice
         return NULL;
     return array;
 }
 
 ParallelResult
 ion::ConcatStringsPar(ForkJoinSlice *slice, HandleString left, HandleString right,
                       MutableHandleString out)
 {
-    JSString *str = ConcatStringsPure(slice, left, right);
+    JSString *str = ConcatStrings<NoGC>(slice, left, right);
     if (!str)
         return TP_RETRY_SEQUENTIALLY;
     out.set(str);
     return TP_SUCCESS;
 }
 
 ParallelResult
 ion::IntToStringPar(ForkJoinSlice *slice, int i, MutableHandleString out)
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -49,17 +49,17 @@ using mozilla::PodCopy;
 using mozilla::RangedPtr;
 
 /*
  * If we're accumulating a decimal number and the number is >= 2^53, then the
  * fast result from the loop in Get{Prefix,Decimal}Integer may be inaccurate.
  * Call js_strtod_harder to get the correct answer.
  */
 static bool
-ComputeAccurateDecimalInteger(ExclusiveContext *cx,
+ComputeAccurateDecimalInteger(ThreadSafeContext *cx,
                               const jschar *start, const jschar *end, double *dp)
 {
     size_t length = end - start;
     char *cstr = cx->pod_malloc<char>(length + 1);
     if (!cstr)
         return false;
 
     for (size_t i = 0; i < length; i++) {
@@ -183,17 +183,17 @@ js::ParseDecimalNumber(const JS::TwoByte
         MOZ_ASSERT(next < DOUBLE_INTEGRAL_PRECISION_LIMIT,
                    "next value won't be an integrally-precise double");
         dec = next;
     } while (++s < end);
     return static_cast<double>(dec);
 }
 
 bool
-js::GetPrefixInteger(ExclusiveContext *cx, const jschar *start, const jschar *end, int base,
+js::GetPrefixInteger(ThreadSafeContext *cx, const jschar *start, const jschar *end, int base,
                      const jschar **endp, double *dp)
 {
     JS_ASSERT(start <= end);
     JS_ASSERT(2 <= base && base <= 36);
 
     const jschar *s = start;
     double d = 0.0;
     for (; s < end; s++) {
@@ -1385,36 +1385,28 @@ js::NumberValueToStringBuffer(JSContext 
      * Inflate to jschar string.  The input C-string characters are < 127, so
      * even if jschars are UTF-8, all chars should map to one jschar.
      */
     size_t cstrlen = strlen(cstr);
     JS_ASSERT(!cbuf.dbuf && cstrlen < cbuf.sbufSize);
     return sb.appendInflated(cstr, cstrlen);
 }
 
-bool
-js::StringToNumber(ExclusiveContext *cx, JSString *str, double *result)
+static void
+CharsToNumber(ThreadSafeContext *cx, const jschar *chars, size_t length, double *result)
 {
-    size_t length = str->length();
-    const jschar *chars = str->getChars(NULL);
-    if (!chars)
-        return false;
-
     if (length == 1) {
         jschar c = chars[0];
-        if ('0' <= c && c <= '9') {
+        if ('0' <= c && c <= '9')
             *result = c - '0';
-            return true;
-        }
-        if (unicode::IsSpace(c)) {
+        else if (unicode::IsSpace(c))
             *result = 0.0;
-            return true;
-        }
-        *result = js_NaN;
-        return true;
+        else
+            *result = js_NaN;
+        return;
     }
 
     const jschar *end = chars + length;
     const jschar *bp = SkipSpace(chars, end);
 
     /* ECMA doesn't allow signed hex numbers (bug 273467). */
     if (end - bp >= 2 && bp[0] == '0' && (bp[1] == 'x' || bp[1] == 'X')) {
         /*
@@ -1424,36 +1416,66 @@ js::StringToNumber(ExclusiveContext *cx,
          */
         const jschar *endptr;
         double d;
         if (!GetPrefixInteger(cx, bp + 2, end, 16, &endptr, &d) ||
             endptr == bp + 2 ||
             SkipSpace(endptr, end) != end)
         {
             *result = js_NaN;
-            return true;
+        } else {
+            *result = d;
         }
-        *result = d;
-        return true;
+        return;
     }
 
     /*
      * Note that ECMA doesn't treat a string beginning with a '0' as
      * an octal number here. This works because all such numbers will
      * be interpreted as decimal by js_strtod.  Also, any hex numbers
      * that have made it here (which can only be negative ones) will
      * be treated as 0 without consuming the 'x' by js_strtod.
      */
     const jschar *ep;
     double d;
-    if (!js_strtod(cx, bp, end, &ep, &d) || SkipSpace(ep, end) != end) {
+    if (!js_strtod(cx, bp, end, &ep, &d) || SkipSpace(ep, end) != end)
         *result = js_NaN;
+    else
+        *result = d;
+}
+
+bool
+js::StringToNumber(ThreadSafeContext *cx, JSString *str, double *result)
+{
+    ScopedThreadSafeStringInspector inspector(str);
+    if (!inspector.ensureChars(cx))
+        return false;
+    CharsToNumber(cx, inspector.chars(), str->length(), result);
+    return true;
+}
+
+bool
+js::NonObjectToNumberSlow(ThreadSafeContext *cx, Value v, double *out)
+{
+    JS_ASSERT(!v.isNumber());
+    JS_ASSERT(!v.isObject());
+
+    if (v.isString())
+        return StringToNumber(cx, v.toString(), out);
+    if (v.isBoolean()) {
+        *out = v.toBoolean() ? 1.0 : 0.0;
         return true;
     }
-    *result = d;
+    if (v.isNull()) {
+        *out = 0.0;
+        return true;
+    }
+
+    JS_ASSERT(v.isUndefined());
+    *out = js_NaN;
     return true;
 }
 
 #if defined(_MSC_VER)
 # pragma optimize("g", off)
 #endif
 
 bool
@@ -1480,35 +1502,21 @@ js::ToNumberSlow(ExclusiveContext *cx, V
 
     JS_ASSERT(!v.isNumber());
     goto skip_int_double;
     for (;;) {
         if (v.isNumber()) {
             *out = v.toNumber();
             return true;
         }
+
       skip_int_double:
-        if (v.isString())
-            return StringToNumber(cx, v.toString(), out);
-        if (v.isBoolean()) {
-            if (v.toBoolean()) {
-                *out = 1.0;
-                return true;
-            }
-            *out = 0.0;
-            return true;
-        }
-        if (v.isNull()) {
-            *out = 0.0;
-            return true;
-        }
-        if (v.isUndefined())
-            break;
+        if (!v.isObject())
+            return NonObjectToNumberSlow(cx, v, out);
 
-        JS_ASSERT(v.isObject());
         if (!cx->isJSContext())
             return false;
 
         RootedValue v2(cx, v);
         if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v2))
             return false;
         v = v2;
         if (v.isObject())
@@ -1562,47 +1570,77 @@ js::ToUint64Slow(JSContext *cx, const Ha
     } else {
         if (!ToNumberSlow(cx, v, &d))
             return false;
     }
     *out = ToUint64(d);
     return true;
 }
 
-JS_PUBLIC_API(bool)
-js::ToInt32Slow(JSContext *cx, const HandleValue v, int32_t *out)
+template <typename ContextType,
+          bool (*ToNumberSlowFn)(ContextType *, Value, double *),
+          typename ValueType>
+static bool
+ToInt32SlowImpl(ContextType *cx, const ValueType v, int32_t *out)
 {
     JS_ASSERT(!v.isInt32());
     double d;
     if (v.isDouble()) {
         d = v.toDouble();
     } else {
-        if (!ToNumberSlow(cx, v, &d))
+        if (!ToNumberSlowFn(cx, v, &d))
             return false;
     }
     *out = ToInt32(d);
     return true;
 }
 
 JS_PUBLIC_API(bool)
-js::ToUint32Slow(JSContext *cx, const HandleValue v, uint32_t *out)
+js::ToInt32Slow(JSContext *cx, const HandleValue v, int32_t *out)
+{
+    return ToInt32SlowImpl<JSContext, ToNumberSlow>(cx, v, out);
+}
+
+bool
+js::NonObjectToInt32Slow(ThreadSafeContext *cx, const Value &v, int32_t *out)
+{
+    return ToInt32SlowImpl<ThreadSafeContext, NonObjectToNumberSlow>(cx, v, out);
+}
+
+template <typename ContextType,
+          bool (*ToNumberSlowFn)(ContextType *, Value, double *),
+          typename ValueType>
+static bool
+ToUint32SlowImpl(ContextType *cx, const ValueType v, uint32_t *out)
 {
     JS_ASSERT(!v.isInt32());
     double d;
     if (v.isDouble()) {
         d = v.toDouble();
     } else {
-        if (!ToNumberSlow(cx, v, &d))
+        if (!ToNumberSlowFn(cx, v, &d))
             return false;
     }
     *out = ToUint32(d);
     return true;
 }
 
 JS_PUBLIC_API(bool)
+js::ToUint32Slow(JSContext *cx, const HandleValue v, uint32_t *out)
+{
+    return ToUint32SlowImpl<JSContext, ToNumberSlow>(cx, v, out);
+}
+
+bool
+js::NonObjectToUint32Slow(ThreadSafeContext *cx, const Value &v, uint32_t *out)
+{
+    return ToUint32SlowImpl<ThreadSafeContext, NonObjectToNumberSlow>(cx, v, out);
+}
+
+JS_PUBLIC_API(bool)
 js::ToUint16Slow(JSContext *cx, const HandleValue v, uint16_t *out)
 {
     JS_ASSERT(!v.isInt32());
     double d;
     if (v.isDouble()) {
         d = v.toDouble();
     } else if (!ToNumberSlow(cx, v, &d)) {
         return false;
@@ -1626,17 +1664,17 @@ js::ToUint16Slow(JSContext *cx, const Ha
     d = fmod(d, (double) m);
     if (d < 0)
         d += m;
     *out = (uint16_t) d;
     return true;
 }
 
 JSBool
-js_strtod(ExclusiveContext *cx, const jschar *s, const jschar *send,
+js_strtod(ThreadSafeContext *cx, const jschar *s, const jschar *send,
           const jschar **ep, double *dp)
 {
     size_t i;
     char cbuf[32];
     char *cstr, *istr, *estr;
     JSBool negative;
     double d;
 
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -123,29 +123,29 @@ ParseDecimalNumber(const JS::TwoByteChar
  * digits of the integer in *endp, and return the integer itself in *dp.  If
  * base is 10 or a power of two the returned integer is the closest possible
  * double; otherwise extremely large integers may be slightly inaccurate.
  *
  * If [start, end) does not begin with a number with the specified base,
  * *dp == 0 and *endp == start upon return.
  */
 extern bool
-GetPrefixInteger(ExclusiveContext *cx, const jschar *start, const jschar *end, int base,
+GetPrefixInteger(ThreadSafeContext *cx, const jschar *start, const jschar *end, int base,
                  const jschar **endp, double *dp);
 
 /*
  * This is like GetPrefixInteger, but only deals with base 10, and doesn't have
  * and |endp| outparam.  It should only be used when the jschars are known to
  * only contain digits.
  */
 extern bool
 GetDecimalInteger(ExclusiveContext *cx, const jschar *start, const jschar *end, double *dp);
 
 extern bool
-StringToNumber(ExclusiveContext *cx, JSString *str, double *result);
+StringToNumber(ThreadSafeContext *cx, JSString *str, double *result);
 
 /* ES5 9.3 ToNumber, overwriting *vp with the appropriate number value. */
 JS_ALWAYS_INLINE bool
 ToNumber(JSContext *cx, JS::MutableHandleValue vp)
 {
 #ifdef DEBUG
     MaybeCheckStackRoots(cx);
 #endif
@@ -172,17 +172,17 @@ num_parseInt(JSContext *cx, unsigned arg
  * return the closest double number to the given input in dp.
  *
  * Also allows inputs of the form [+|-]Infinity, which produce an infinity of
  * the appropriate sign.  The case of the "Infinity" string must match exactly.
  * If the string does not contain a number, set *ep to s and return 0.0 in dp.
  * Return false if out of memory.
  */
 extern JSBool
-js_strtod(js::ExclusiveContext *cx, const jschar *s, const jschar *send,
+js_strtod(js::ThreadSafeContext *cx, const jschar *s, const jschar *send,
           const jschar **ep, double *dp);
 
 extern JSBool
 js_num_toString(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern JSBool
 js_num_valueOf(JSContext *cx, unsigned argc, js::Value *vp);
 
@@ -284,11 +284,54 @@ ToNumber(ExclusiveContext *cx, const Val
 {
     if (v.isNumber()) {
         *out = v.toNumber();
         return true;
     }
     return ToNumberSlow(cx, v, out);
 }
 
+/*
+ * Thread safe variants of number conversion functions.
+ */
+
+bool
+NonObjectToNumberSlow(ThreadSafeContext *cx, Value v, double *out);
+
+inline bool
+NonObjectToNumber(ThreadSafeContext *cx, const Value &v, double *out)
+{
+    if (v.isNumber()) {
+        *out = v.toNumber();
+        return true;
+    }
+    return NonObjectToNumberSlow(cx, v, out);
+}
+
+bool
+NonObjectToInt32Slow(ThreadSafeContext *cx, const Value &v, int32_t *out);
+
+inline bool
+NonObjectToInt32(ThreadSafeContext *cx, const Value &v, int32_t *out)
+{
+    if (v.isInt32()) {
+        *out = v.toInt32();
+        return true;
+    }
+    return NonObjectToInt32Slow(cx, v, out);
+}
+
+bool
+NonObjectToUint32Slow(ThreadSafeContext *cx, const Value &v, uint32_t *out);
+
+JS_ALWAYS_INLINE bool
+NonObjectToUint32(ThreadSafeContext *cx, const Value &v, uint32_t *out)
+{
+    if (v.isInt32()) {
+        *out = uint32_t(v.toInt32());
+        return true;
+    }
+    return NonObjectToUint32Slow(cx, v, out);
+}
+
 } /* namespace js */
 
 #endif /* jsnum_h */
--- a/js/src/jsstr.h
+++ b/js/src/jsstr.h
@@ -39,23 +39,20 @@ class MutatingRopeSegmentRange;
 
 /*
  * Utility for building a rope (lazy concatenation) of strings.
  */
 class RopeBuilder;
 
 template <AllowGC allowGC>
 extern JSString *
-ConcatStrings(JSContext *cx,
+ConcatStrings(ThreadSafeContext *cx,
               typename MaybeRooted<JSString*, allowGC>::HandleType left,
               typename MaybeRooted<JSString*, allowGC>::HandleType right);
 
-extern JSString *
-ConcatStringsPure(ThreadSafeContext *cx, JSString *left, JSString *right);
-
 // Return s advanced past any Unicode white space characters.
 static inline const jschar *
 SkipSpace(const jschar *s, const jschar *end)
 {
     JS_ASSERT(s <= end);
 
     while (s < end && unicode::IsSpace(*s))
         s++;
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -396,17 +396,17 @@ JSRope::flatten(JSContext *maybecx)
         return flattenInternal<NoBarrier>(maybecx);
 #else
     return flattenInternal<NoBarrier>(maybecx);
 #endif
 }
 
 template <AllowGC allowGC>
 JSString *
-js::ConcatStrings(JSContext *cx,
+js::ConcatStrings(ThreadSafeContext *cx,
                   typename MaybeRooted<JSString*, allowGC>::HandleType left,
                   typename MaybeRooted<JSString*, allowGC>::HandleType right)
 {
     JS_ASSERT_IF(!left->isAtom(), cx->isInsideCurrentZone(left));
     JS_ASSERT_IF(!right->isAtom(), cx->isInsideCurrentZone(right));
 
     size_t leftLen = left->length();
     if (leftLen == 0)
@@ -419,78 +419,38 @@ js::ConcatStrings(JSContext *cx,
     size_t wholeLength = leftLen + rightLen;
     if (!JSString::validateLength(cx, wholeLength))
         return NULL;
 
     if (JSShortString::lengthFits(wholeLength) && cx->isJSContext()) {
         JSShortString *str = js_NewGCShortString<allowGC>(cx);
         if (!str)
             return NULL;
-        const jschar *leftChars = left->getChars(cx);
-        if (!leftChars)
-            return NULL;
-        const jschar *rightChars = right->getChars(cx);
-        if (!rightChars)
+
+        ScopedThreadSafeStringInspector leftInspector(left);
+        ScopedThreadSafeStringInspector rightInspector(right);
+        if (!leftInspector.ensureChars(cx) || !rightInspector.ensureChars(cx))
             return NULL;
 
         jschar *buf = str->init(wholeLength);
-        PodCopy(buf, leftChars, leftLen);
-        PodCopy(buf + leftLen, rightChars, rightLen);
+        PodCopy(buf, leftInspector.chars(), leftLen);
+        PodCopy(buf + leftLen, rightInspector.chars(), rightLen);
+
         buf[wholeLength] = 0;
         return str;
     }
 
     return JSRope::new_<allowGC>(cx, left, right, wholeLength);
 }
 
 template JSString *
-js::ConcatStrings<CanGC>(JSContext *cx, HandleString left, HandleString right);
+js::ConcatStrings<CanGC>(ThreadSafeContext *cx, HandleString left, HandleString right);
 
 template JSString *
-js::ConcatStrings<NoGC>(JSContext *cx, JSString *left, JSString *right);
-
-JSString *
-js::ConcatStringsPure(ThreadSafeContext *cx, JSString *left, JSString *right)
-{
-    JS_ASSERT_IF(!left->isAtom(), cx->isInsideCurrentZone(left));
-    JS_ASSERT_IF(!right->isAtom(), cx->isInsideCurrentZone(right));
-
-    size_t leftLen = left->length();
-    if (leftLen == 0)
-        return right;
-
-    size_t rightLen = right->length();
-    if (rightLen == 0)
-        return left;
-
-    size_t wholeLength = leftLen + rightLen;
-    if (!JSString::validateLength(NULL, wholeLength))
-        return NULL;
-
-    if (JSShortString::lengthFits(wholeLength)) {
-        JSShortString *str = js_NewGCShortString<NoGC>(cx);
-        if (!str)
-            return NULL;
-
-        jschar *buf = str->init(wholeLength);
-
-        ScopedThreadSafeStringInspector leftInspector(left);
-        ScopedThreadSafeStringInspector rightInspector(right);
-        if (!leftInspector.ensureChars(cx) || !rightInspector.ensureChars(cx))
-            return NULL;
-
-        PodCopy(buf, leftInspector.chars(), leftLen);
-        PodCopy(buf + leftLen, rightInspector.chars(), rightLen);
-
-        buf[wholeLength] = 0;
-        return str;
-    }
-
-    return JSRope::new_<NoGC>(cx, left, right, wholeLength);
-}
+js::ConcatStrings<NoGC>(ThreadSafeContext *cx, JSString *left, JSString *right);
 
 bool
 JSDependentString::getCharsZNonDestructive(ThreadSafeContext *cx, ScopedJSFreePtr<jschar> &out) const
 {
     JS_ASSERT(JSString::isDependent());
 
     size_t n = length();
     jschar *s = cx->pod_malloc<jschar>(n + 1);