Bug 1264264 - Part 2: Enable optimization for packers again in RegExp.prototype[@@replace]. r=h4writer
authorTooru Fujisawa <arai_a@mac.com>
Sat, 23 Apr 2016 03:09:41 +0900
changeset 332458 8b08faebf408b352fde6504d33b3faaf6149a62f
parent 332457 86d33031bbd3f7173d8efbc1d5da8bb22352c53f
child 332459 b10037459c7a33e60291063807bf47a07f5a0997
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs1264264
milestone48.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 1264264 - Part 2: Enable optimization for packers again in RegExp.prototype[@@replace]. r=h4writer
js/src/builtin/RegExp.cpp
js/src/builtin/RegExp.h
js/src/builtin/RegExp.js
js/src/builtin/RegExpGlobalReplaceOpt.h.js
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/RegExp.cpp
+++ b/js/src/builtin/RegExp.cpp
@@ -1571,8 +1571,106 @@ js::RegExpInstanceOptimizableRaw(JSConte
         *result = false;
         return true;
     }
 
     cx->compartment()->regExps.setOptimizableRegExpInstanceShape(nobj->lastProperty());
     *result = true;
     return true;
 }
+
+/*
+ * Pattern match the script to check if it is is indexing into a particular
+ * object, e.g. 'function(a) { return b[a]; }'. Avoid calling the script in
+ * such cases, which are used by javascript packers (particularly the popular
+ * Dean Edwards packer) to efficiently encode large scripts. We only handle the
+ * code patterns generated by such packers here.
+ */
+bool
+js::intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc, Value* vp)
+{
+    // This can only be called from self-hosted code.
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 1);
+
+    JSObject& lambda = args[0].toObject();
+    args.rval().setUndefined();
+
+    if (!lambda.is<JSFunction>())
+        return true;
+
+    RootedFunction fun(cx, &lambda.as<JSFunction>());
+    if (!fun->isInterpreted() || fun->isClassConstructor())
+        return true;
+
+    JSScript* script = fun->getOrCreateScript(cx);
+    if (!script)
+        return false;
+
+    jsbytecode* pc = script->code();
+
+    /*
+     * JSOP_GETALIASEDVAR tells us exactly where to find the base object 'b'.
+     * Rule out the (unlikely) possibility of a function with a call object
+     * since it would make our scope walk off by 1.
+     */
+    if (JSOp(*pc) != JSOP_GETALIASEDVAR || fun->needsCallObject())
+        return true;
+    ScopeCoordinate sc(pc);
+    ScopeObject* scope = &fun->environment()->as<ScopeObject>();
+    for (unsigned i = 0; i < sc.hops(); ++i)
+        scope = &scope->enclosingScope().as<ScopeObject>();
+    Value b = scope->aliasedVar(sc);
+    pc += JSOP_GETALIASEDVAR_LENGTH;
+
+    /* Look for 'a' to be the lambda's first argument. */
+    if (JSOp(*pc) != JSOP_GETARG || GET_ARGNO(pc) != 0)
+        return true;
+    pc += JSOP_GETARG_LENGTH;
+
+    /* 'b[a]' */
+    if (JSOp(*pc) != JSOP_GETELEM)
+        return true;
+    pc += JSOP_GETELEM_LENGTH;
+
+    /* 'return b[a]' */
+    if (JSOp(*pc) != JSOP_RETURN)
+        return true;
+
+    /* 'b' must behave like a normal object. */
+    if (!b.isObject())
+        return true;
+
+    JSObject& bobj = b.toObject();
+    const Class* clasp = bobj.getClass();
+    if (!clasp->isNative() || clasp->getOpsLookupProperty() || clasp->getOpsGetProperty())
+        return true;
+
+    args.rval().setObject(bobj);
+    return true;
+}
+
+/*
+ * Emulates `b[a]` property access, that is detected in GetElemBaseForLambda.
+ * It returns the property value only if the property is data property and the
+ * propety value is a string.  Otherwise it returns undefined.
+ */
+bool
+js::intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+
+    RootedNativeObject obj(cx, &args[0].toObject().as<NativeObject>());
+    RootedString name(cx, args[1].toString());
+
+    RootedAtom atom(cx, AtomizeString(cx, name));
+    if (!atom)
+        return false;
+
+    RootedValue v(cx);
+    if (HasDataProperty(cx, obj, AtomToId(atom), v.address()) && v.isString())
+        args.rval().set(v);
+    else
+        args.rval().setUndefined();
+
+    return true;
+}
--- a/js/src/builtin/RegExp.h
+++ b/js/src/builtin/RegExp.h
@@ -56,16 +56,22 @@ RegExpSearcherRaw(JSContext* cx, HandleO
 
 extern bool
 RegExpTester(JSContext* cx, unsigned argc, Value* vp);
 
 extern bool
 RegExpTesterRaw(JSContext* cx, HandleObject regexp, HandleString input,
                 int32_t lastIndex, int32_t* endIndex);
 
+extern bool
+intrinsic_GetElemBaseForLambda(JSContext* cx, unsigned argc, Value* vp);
+
+extern bool
+intrinsic_GetStringDataProperty(JSContext* cx, unsigned argc, Value* vp);
+
 /*
  * The following functions are for use by self-hosted code.
  */
 
 /*
  * Behaves like regexp.exec(string), but doesn't set RegExp statics.
  *
  * Usage: match = regexp_exec_no_statics(regexp, string)
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -195,18 +195,22 @@ function RegExpReplace(string, replaceVa
 
     // Step 7.
     var global = !!rx.global;
 
     // Optimized paths.
     if (IsRegExpMethodOptimizable(rx)) {
         // Steps 8-16.
         if (global) {
-            if (functionalReplace)
+            if (functionalReplace) {
+                var elemBase = GetElemBaseForLambda(replaceValue);
+                if (IsObject(elemBase))
+                    return RegExpGlobalReplaceOptElemBase(rx, S, lengthS, replaceValue, elemBase);
                 return RegExpGlobalReplaceOptFunc(rx, S, lengthS, replaceValue);
+            }
             if (firstDollarIndex !== -1)
                 return RegExpGlobalReplaceOptSubst(rx, S, lengthS, replaceValue, firstDollarIndex);
             if (lengthS < 0x7fff)
                 return RegExpGlobalReplaceShortOpt(rx, S, lengthS, replaceValue);
             return RegExpGlobalReplaceOpt(rx, S, lengthS, replaceValue);
         }
 
         if (functionalReplace)
@@ -329,16 +333,17 @@ function RegExpReplaceSlowPath(rx, S, le
     return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
 }
 
 // ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
 // steps 14.g-k.
 // Calculates functional/substitution replaceement from match result.
 // Used in the following functions:
 //   * RegExpGlobalReplaceOptFunc
+//   * RegExpGlobalReplaceOptElemBase
 //   * RegExpGlobalReplaceOptSubst
 //   * RegExpLocalReplaceOptFunc
 //   * RegExpLocalReplaceOptSubst
 //   * RegExpReplaceSlowPath
 function RegExpGetComplexReplacement(result, matched, S, position,
                                      nCaptures, replaceValue,
                                      functionalReplace, firstDollarIndex)
 {
@@ -469,16 +474,25 @@ function RegExpGlobalReplaceShortOpt(rx,
 #define FUNC_NAME RegExpGlobalReplaceOptFunc
 #define FUNCTIONAL
 #include "RegExpGlobalReplaceOpt.h.js"
 #undef FUNCTIONAL
 #undef FUNC_NAME
 
 // Conditions:
 //   * global flag is true
+//   * replaceValue is a function that returns element of an object
+#define FUNC_NAME RegExpGlobalReplaceOptElemBase
+#define ELEMBASE
+#include "RegExpGlobalReplaceOpt.h.js"
+#undef ELEMBASE
+#undef FUNC_NAME
+
+// Conditions:
+//   * global flag is true
 //   * replaceValue is a string with "$"
 #define FUNC_NAME RegExpGlobalReplaceOptSubst
 #define SUBSTITUTION
 #include "RegExpGlobalReplaceOpt.h.js"
 #undef SUBSTITUTION
 #undef FUNC_NAME
 
 // Conditions:
--- a/js/src/builtin/RegExpGlobalReplaceOpt.h.js
+++ b/js/src/builtin/RegExpGlobalReplaceOpt.h.js
@@ -1,29 +1,35 @@
 // Function template for the following functions:
 //   * RegExpGlobalReplaceOpt
 //   * RegExpGlobalReplaceOptFunc
 //   * RegExpGlobalReplaceOptSubst
+//   * RegExpGlobalReplaceOptElemBase
 // Define the following macro and include this file to declare function:
 //   * FUNC_NAME     -- function name (required)
 //       e.g.
 //         #define FUNC_NAME RegExpGlobalReplaceOpt
 // Define the following macro (without value) to switch the code:
 //   * SUBSTITUTION     -- replaceValue is a string with "$"
 //   * FUNCTIONAL       -- replaceValue is a function
-//   * neither of above -- replaceValue is a string without "$"
+//   * ELEMBASE         -- replaceValue is a function that returns an element
+//                         of an object
+//   * none of above    -- replaceValue is a string without "$"
 
 // ES 2017 draft 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
 // steps 8-16.
 // Optimized path for @@replace with the following conditions:
 //   * global flag is true
 function FUNC_NAME(rx, S, lengthS, replaceValue
 #ifdef SUBSTITUTION
                    , firstDollarIndex
 #endif
+#ifdef ELEMBASE
+                   , elemBase
+#endif
                   )
 {
     // Step 8.a.
     var fullUnicode = !!rx.unicode;
 
     // Step 8.b.
     var lastIndex = 0;
     rx.lastIndex = 0;
@@ -38,22 +44,23 @@ function FUNC_NAME(rx, S, lengthS, repla
     while (true) {
         // Step 11.a.
         var result = RegExpMatcher(rx, S, lastIndex);
 
         // Step 11.b.
         if (result === null)
             break;
 
+        var nCaptures;
 #if defined(FUNCTIONAL) || defined(SUBSTITUTION)
         // Steps 14.a-b.
-        var nCaptures = std_Math_max(result.length - 1, 0);
+        nCaptures = std_Math_max(result.length - 1, 0);
 #endif
 
-        // Step 14.c.
+        // Step 14.c (reordered).
         var matched = result[0];
 
         // Step 14.d.
         var matchLength = matched.length;
 
         // Steps 14.e-f.
         var position = result.index;
         lastIndex = position + matchLength;
@@ -65,16 +72,34 @@ function FUNC_NAME(rx, S, lengthS, repla
 
                                                   nCaptures, replaceValue,
                                                   true, -1);
 #elif defined(SUBSTITUTION)
         replacement = RegExpGetComplexReplacement(result, matched, S, position,
 
                                                   nCaptures, replaceValue,
                                                   false, firstDollarIndex);
+#elif defined(ELEMBASE)
+        if (IsObject(elemBase)) {
+            var prop = GetStringDataProperty(elemBase, matched);
+            if (prop !== undefined)
+                replacement = prop;
+            else
+                elemBase = undefined;
+        }
+
+        if (!IsObject(elemBase)) {
+            // Steps 14.a-b (reordered).
+            nCaptures = std_Math_max(result.length - 1, 0);
+
+            replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+                                                      nCaptures, replaceValue,
+                                                      true, -1);
+        }
 #else
         replacement = replaceValue;
 #endif
 
         // Step 14.l.ii.
         accumulatedResult += Substring(S, nextSourcePosition,
                                        position - nextSourcePosition) + replacement;
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2561,16 +2561,18 @@ static const JSFunctionSpec intrinsic_fu
                     RegExpTester),
     JS_FN("RegExpCreate", intrinsic_RegExpCreate, 2,0),
     JS_INLINABLE_FN("RegExpPrototypeOptimizable", RegExpPrototypeOptimizable, 1,0,
                     RegExpPrototypeOptimizable),
     JS_INLINABLE_FN("RegExpInstanceOptimizable", RegExpInstanceOptimizable, 1,0,
                     RegExpInstanceOptimizable),
     JS_FN("RegExpGetSubstitution", intrinsic_RegExpGetSubstitution, 6,0),
     JS_FN("RegExpEscapeMetaChars", intrinsic_RegExpEscapeMetaChars, 1,0),
+    JS_FN("GetElemBaseForLambda", intrinsic_GetElemBaseForLambda, 1,0),
+    JS_FN("GetStringDataProperty", intrinsic_GetStringDataProperty, 2,0),
 
     JS_FN("FlatStringMatch", FlatStringMatch, 2,0),
     JS_FN("FlatStringSearch", FlatStringSearch, 2,0),
     JS_INLINABLE_FN("StringReplaceString", intrinsic_StringReplaceString, 3, 0,
                     IntrinsicStringReplaceString),
     JS_INLINABLE_FN("StringSplitString", intrinsic_StringSplitString, 2, 0,
                     IntrinsicStringSplitString),
     JS_FN("StringSplitStringLimit", intrinsic_StringSplitStringLimit, 3, 0),