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 318361 8b08faebf408b352fde6504d33b3faaf6149a62f
parent 318360 86d33031bbd3f7173d8efbc1d5da8bb22352c53f
child 318362 b10037459c7a33e60291063807bf47a07f5a0997
push id9480
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 17:12:58 +0000
treeherdermozilla-aurora@0d6a91c76a9e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersh4writer
bugs1264264
milestone48.0a1
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),