Bug 1435306: Cache UPluralRules and UNumberFormat for Intl.PluralRules instances. r=gandalf
authorAndré Bargull <andre.bargull@gmail.com>
Fri, 02 Feb 2018 09:20:27 -0800
changeset 402609 a3d21e2035195895b58e0e325aaf4a8c6e7257c2
parent 402608 421caf8d226b793f8cd7b3919863b9e93ad7c990
child 402610 af6cc52c9b8b737e4734710ad6786dacdb59d923
push id33393
push userrgurzau@mozilla.com
push dateTue, 06 Feb 2018 21:54:26 +0000
treeherdermozilla-central@0790ec12200d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgandalf
bugs1435306
milestone60.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 1435306: Cache UPluralRules and UNumberFormat for Intl.PluralRules instances. r=gandalf
js/src/builtin/intl/PluralRules.cpp
js/src/builtin/intl/PluralRules.h
js/src/builtin/intl/PluralRules.js
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/intl/PluralRules.cpp
+++ b/js/src/builtin/intl/PluralRules.cpp
@@ -102,16 +102,17 @@ PluralRules(JSContext* cx, unsigned argc
 
     Rooted<PluralRulesObject*> pluralRules(cx);
     pluralRules = NewObjectWithGivenProto<PluralRulesObject>(cx, proto);
     if (!pluralRules)
         return false;
 
     pluralRules->setReservedSlot(PluralRulesObject::INTERNALS_SLOT, NullValue());
     pluralRules->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(nullptr));
+    pluralRules->setReservedSlot(PluralRulesObject::UNUMBER_FORMAT_SLOT, PrivateValue(nullptr));
 
     HandleValue locales = args.get(0);
     HandleValue options = args.get(1);
 
     // Step 3.
     if (!intl::InitializeObject(cx, pluralRules, cx->names().InitializePluralRules, locales,
                                 options))
     {
@@ -122,20 +123,25 @@ PluralRules(JSContext* cx, unsigned argc
     return true;
 }
 
 void
 js::PluralRulesObject::finalize(FreeOp* fop, JSObject* obj)
 {
     MOZ_ASSERT(fop->onActiveCooperatingThread());
 
-    const Value& slot =
-        obj->as<PluralRulesObject>().getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT);
-    if (UPluralRules* pr = static_cast<UPluralRules*>(slot.toPrivate()))
+    PluralRulesObject* pluralRules = &obj->as<PluralRulesObject>();
+
+    const Value& prslot = pluralRules->getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT);
+    if (UPluralRules* pr = static_cast<UPluralRules*>(prslot.toPrivate()))
         uplrules_close(pr);
+
+    const Value& nfslot = pluralRules->getReservedSlot(PluralRulesObject::UNUMBER_FORMAT_SLOT);
+    if (UNumberFormat* nf = static_cast<UNumberFormat*>(nfslot.toPrivate()))
+        unum_close(nf);
 }
 
 JSObject*
 js::CreatePluralRulesPrototype(JSContext* cx, HandleObject Intl, Handle<GlobalObject*> global)
 {
     RootedFunction ctor(cx);
     ctor = global->createConstructor(cx, &PluralRules, cx->names().PluralRules, 0);
     if (!ctor)
@@ -247,110 +253,120 @@ NewUNumberFormatForPluralRules(JSContext
         unum_setAttribute(nf, UNUM_MIN_INTEGER_DIGITS, uMinimumIntegerDigits);
         unum_setAttribute(nf, UNUM_MIN_FRACTION_DIGITS, uMinimumFractionDigits);
         unum_setAttribute(nf, UNUM_MAX_FRACTION_DIGITS, uMaximumFractionDigits);
     }
 
     return toClose.forget();
 }
 
+/**
+ * Returns a new UPluralRules with the locale and type options of the given
+ * PluralRules.
+ */
+static UPluralRules*
+NewUPluralRules(JSContext* cx, Handle<PluralRulesObject*> pluralRules)
+{
+    RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
+    if (!internals)
+        return nullptr;
+
+    RootedValue value(cx);
+
+    if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
+        return nullptr;
+    JSAutoByteString locale(cx, value.toString());
+    if (!locale)
+        return nullptr;
+
+    if (!GetProperty(cx, internals, internals, cx->names().type, &value))
+        return nullptr;
+
+    UPluralType category;
+    {
+        JSLinearString* type = value.toString()->ensureLinear(cx);
+        if (!type)
+            return nullptr;
+
+        if (StringEqualsAscii(type, "cardinal")) {
+            category = UPLURAL_TYPE_CARDINAL;
+        } else {
+            MOZ_ASSERT(StringEqualsAscii(type, "ordinal"));
+            category = UPLURAL_TYPE_ORDINAL;
+        }
+    }
+
+    UErrorCode status = U_ZERO_ERROR;
+    UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
+    if (U_FAILURE(status)) {
+        intl::ReportInternalError(cx);
+        return nullptr;
+    }
+    return pr;
+}
+
 bool
 js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 2);
 
     Rooted<PluralRulesObject*> pluralRules(cx, &args[0].toObject().as<PluralRulesObject>());
 
     double x = args[1].toNumber();
 
-    UNumberFormat* nf = NewUNumberFormatForPluralRules(cx, pluralRules);
-    if (!nf)
-        return false;
-
-    ScopedICUObject<UNumberFormat, unum_close> closeNumberFormat(nf);
-
-    RootedObject internals(cx, intl::GetInternalsObject(cx, pluralRules));
-    if (!internals)
-        return false;
-
-    RootedValue value(cx);
-
-    if (!GetProperty(cx, internals, internals, cx->names().locale, &value))
-        return false;
-    JSAutoByteString locale(cx, value.toString());
-    if (!locale)
-        return false;
-
-    if (!GetProperty(cx, internals, internals, cx->names().type, &value))
-        return false;
-
-    UPluralType category;
-    {
-        JSLinearString* type = value.toString()->ensureLinear(cx);
-        if (!type)
+    // Obtain a cached UPluralRules object.
+    void* priv = pluralRules->getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT).toPrivate();
+    UPluralRules* pr = static_cast<UPluralRules*>(priv);
+    if (!pr) {
+        pr = NewUPluralRules(cx, pluralRules);
+        if (!pr)
             return false;
-
-        if (StringEqualsAscii(type, "cardinal")) {
-            category = UPLURAL_TYPE_CARDINAL;
-        } else {
-            MOZ_ASSERT(StringEqualsAscii(type, "ordinal"));
-            category = UPLURAL_TYPE_ORDINAL;
-        }
+        pluralRules->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(pr));
     }
 
-    // TODO: Cache UPluralRules in PluralRulesObject::UPluralRulesSlot.
-    UErrorCode status = U_ZERO_ERROR;
-    UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
-    if (U_FAILURE(status)) {
-        intl::ReportInternalError(cx);
-        return false;
+    // Obtain a cached UNumberFormat object.
+    priv = pluralRules->getReservedSlot(PluralRulesObject::UNUMBER_FORMAT_SLOT).toPrivate();
+    UNumberFormat* nf = static_cast<UNumberFormat*>(priv);
+    if (!nf) {
+        nf = NewUNumberFormatForPluralRules(cx, pluralRules);
+        if (!nf)
+            return false;
+        pluralRules->setReservedSlot(PluralRulesObject::UNUMBER_FORMAT_SLOT, PrivateValue(nf));
     }
-    ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
 
     JSString* str = CallICU(cx, [pr, x, nf](UChar* chars, int32_t size, UErrorCode* status) {
         return uplrules_selectWithFormat(pr, x, nf, chars, size, status);
     });
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
 bool
 js::intl_GetPluralCategories(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(args.length() == 1);
 
-    JSAutoByteString locale(cx, args[0].toString());
-    if (!locale)
-        return false;
+    Rooted<PluralRulesObject*> pluralRules(cx, &args[0].toObject().as<PluralRulesObject>());
 
-    JSLinearString* type = args[1].toString()->ensureLinear(cx);
-    if (!type)
-        return false;
-
-    UPluralType category;
-    if (StringEqualsAscii(type, "cardinal")) {
-        category = UPLURAL_TYPE_CARDINAL;
-    } else {
-        MOZ_ASSERT(StringEqualsAscii(type, "ordinal"));
-        category = UPLURAL_TYPE_ORDINAL;
+    // Obtain a cached UPluralRules object.
+    void* priv = pluralRules->getReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT).toPrivate();
+    UPluralRules* pr = static_cast<UPluralRules*>(priv);
+    if (!pr) {
+        pr = NewUPluralRules(cx, pluralRules);
+        if (!pr)
+            return false;
+        pluralRules->setReservedSlot(PluralRulesObject::UPLURAL_RULES_SLOT, PrivateValue(pr));
     }
 
     UErrorCode status = U_ZERO_ERROR;
-    UPluralRules* pr = uplrules_openForType(IcuLocale(locale.ptr()), category, &status);
-    if (U_FAILURE(status)) {
-        intl::ReportInternalError(cx);
-        return false;
-    }
-    ScopedICUObject<UPluralRules, uplrules_close> closePluralRules(pr);
-
     UEnumeration* ue = uplrules_getKeywords(pr, &status);
     if (U_FAILURE(status)) {
         intl::ReportInternalError(cx);
         return false;
     }
     ScopedICUObject<UEnumeration, uenum_close> closeEnum(ue);
 
     RootedObject res(cx, NewDenseEmptyArray(cx));
--- a/js/src/builtin/intl/PluralRules.h
+++ b/js/src/builtin/intl/PluralRules.h
@@ -20,17 +20,18 @@ class FreeOp;
 
 class PluralRulesObject : public NativeObject
 {
   public:
     static const Class class_;
 
     static constexpr uint32_t INTERNALS_SLOT = 0;
     static constexpr uint32_t UPLURAL_RULES_SLOT = 1;
-    static constexpr uint32_t SLOT_COUNT = 2;
+    static constexpr uint32_t UNUMBER_FORMAT_SLOT = 2;
+    static constexpr uint32_t SLOT_COUNT = 3;
 
     static_assert(INTERNALS_SLOT == INTL_INTERNALS_OBJECT_SLOT,
                   "INTERNALS_SLOT must match self-hosting define for internals object slot");
 
   private:
     static const ClassOps classOps_;
 
     static void finalize(FreeOp* fop, JSObject* obj);
@@ -59,23 +60,23 @@ intl_PluralRules_availableLocales(JSCont
  * (such as "one", "two", "few" etc.).
  *
  * Usage: rule = intl_SelectPluralRule(pluralRules, x)
  */
 extern MOZ_MUST_USE bool
 intl_SelectPluralRule(JSContext* cx, unsigned argc, JS::Value* vp);
 
 /**
- * Returns an array of plural rules categories for a given
- * locale and type.
+ * Returns an array of plural rules categories for a given pluralRules object.
  *
- * Usage: categories = intl_GetPluralCategories(locale, type)
+ * Usage: categories = intl_GetPluralCategories(pluralRules)
  *
  * Example:
  *
- * intl_getPluralCategories('pl', 'cardinal'); // ['one', 'few', 'many', 'other']
+ * pluralRules = new Intl.PluralRules('pl', {type: 'cardinal'});
+ * intl_getPluralCategories(pluralRules); // ['one', 'few', 'many', 'other']
  */
 extern MOZ_MUST_USE bool
 intl_GetPluralCategories(JSContext* cx, unsigned argc, JS::Value* vp);
 
 } // namespace js
 
 #endif /* builtin_intl_PluralRules_h */
--- a/js/src/builtin/intl/PluralRules.js
+++ b/js/src/builtin/intl/PluralRules.js
@@ -43,19 +43,17 @@ function resolvePluralRulesInternals(laz
                           lazyPluralRulesData.requestedLocales,
                           lazyPluralRulesData.opt,
                           PluralRules.relevantExtensionKeys, PluralRules.localeData);
 
     // Step 14.
     internalProps.locale = r.locale;
     internalProps.type = lazyPluralRulesData.type;
 
-    internalProps.pluralCategories = intl_GetPluralCategories(
-        internalProps.locale,
-        internalProps.type);
+    internalProps.pluralCategories = null;
 
     internalProps.minimumIntegerDigits = lazyPluralRulesData.minimumIntegerDigits;
     internalProps.minimumFractionDigits = lazyPluralRulesData.minimumFractionDigits;
     internalProps.maximumFractionDigits = lazyPluralRulesData.maximumFractionDigits;
 
     if ("minimumSignificantDigits" in lazyPluralRulesData) {
         assert("maximumSignificantDigits" in lazyPluralRulesData, "min/max sig digits mismatch");
         internalProps.minimumSignificantDigits = lazyPluralRulesData.minimumSignificantDigits;
@@ -210,16 +208,19 @@ function Intl_PluralRules_resolvedOption
     if (!IsObject(this) || !IsPluralRules(this)) {
         ThrowTypeError(JSMSG_INTL_OBJECT_NOT_INITED, "PluralRules", "resolvedOptions",
                        "PluralRules");
     }
 
     var internals = getPluralRulesInternals(this);
 
     var internalsPluralCategories = internals.pluralCategories;
+    if (internalsPluralCategories === null)
+        internals.pluralCategories = internalsPluralCategories = intl_GetPluralCategories(this);
+
     var pluralCategories = [];
     for (var i = 0; i < internalsPluralCategories.length; i++)
         _DefineDataProperty(pluralCategories, i, internalsPluralCategories[i]);
 
     var result = {
         locale: internals.locale,
         type: internals.type,
         pluralCategories,
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2494,17 +2494,17 @@ static const JSFunctionSpec intrinsic_fu
     JS_FN("intl_isUpperCaseFirst", intl_isUpperCaseFirst, 1,0),
     JS_FN("intl_IsValidTimeZoneName", intl_IsValidTimeZoneName, 1,0),
     JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
     JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
     JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
     JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
     JS_FN("intl_patternForStyle", intl_patternForStyle, 3,0),
     JS_FN("intl_PluralRules_availableLocales", intl_PluralRules_availableLocales, 0,0),
-    JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 2, 0),
+    JS_FN("intl_GetPluralCategories", intl_GetPluralCategories, 1, 0),
     JS_FN("intl_SelectPluralRule", intl_SelectPluralRule, 2,0),
     JS_FN("intl_RelativeTimeFormat_availableLocales", intl_RelativeTimeFormat_availableLocales, 0,0),
     JS_FN("intl_FormatRelativeTime", intl_FormatRelativeTime, 3,0),
     JS_FN("intl_toLocaleLowerCase", intl_toLocaleLowerCase, 2,0),
     JS_FN("intl_toLocaleUpperCase", intl_toLocaleUpperCase, 2,0),
 
     JS_INLINABLE_FN("IsCollator",
                     intrinsic_IsInstanceOfBuiltin<CollatorObject>, 1,0,