Bug 1264264 - Add optimized path for RegExp.prototype[@@replace] with functional replace and substitution. r=till
authorTooru Fujisawa <arai_a@mac.com>
Fri, 22 Apr 2016 21:30:41 +0900
changeset 318318 b9ac34e99e3048648c839de22de82f97eb4ce3a7
parent 318317 4307f2ac86811069f1ca9fa21b0200fcae3f4fe2
child 318319 b12d4b8634f3e1997dcaca3ac742c5ea3ea12df1
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)
reviewerstill
bugs1264264
milestone48.0a1
Bug 1264264 - Add optimized path for RegExp.prototype[@@replace] with functional replace and substitution. r=till
js/src/builtin/RegExp.js
js/src/builtin/RegExpGlobalReplaceOpt.h.js
js/src/builtin/RegExpLocalReplaceOpt.h.js
js/src/builtin/embedjs.py
js/src/moz.build
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -157,17 +157,17 @@ function IsRegExpMethodOptimizable(rx) {
     var RegExpProto = GetBuiltinPrototype("RegExp");
     // If RegExpPrototypeOptimizable and RegExpInstanceOptimizable succeed,
     // `RegExpProto.exec` is guaranteed to be data properties.
     return RegExpPrototypeOptimizable(RegExpProto) &&
            RegExpInstanceOptimizable(rx, RegExpProto) &&
            RegExpProto.exec === RegExp_prototype_Exec;
 }
 
-// ES 2016 draft Mar 25, 2016 21.2.5.8.
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8.
 function RegExpReplace(string, replaceValue) {
     // Step 1.
     var rx = this;
 
     // Step 2.
     if (!IsObject(rx))
         ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, rx === null ? "null" : typeof rx);
 
@@ -191,26 +191,47 @@ function RegExpReplace(string, replaceVa
         // substitution.
         if (replaceValue.length > 1)
             firstDollarIndex = callFunction(std_String_indexOf, replaceValue, "$");
     }
 
     // Step 7.
     var global = !!rx.global;
 
-    // Optimized paths for simple cases.
-    if (!functionalReplace && firstDollarIndex === -1 && IsRegExpMethodOptimizable(rx)) {
+    // Optimized paths.
+    if (IsRegExpMethodOptimizable(rx)) {
+        // Steps 8-16.
         if (global) {
+            if (functionalReplace)
+                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)
+            return RegExpLocalReplaceOptFunc(rx, S, lengthS, replaceValue);
+        if (firstDollarIndex !== -1)
+            return RegExpLocalReplaceOptSubst(rx, S, lengthS, replaceValue, firstDollarIndex);
         return RegExpLocalReplaceOpt(rx, S, lengthS, replaceValue);
     }
 
+    // Steps 8-16.
+    return RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
+                                 functionalReplace, firstDollarIndex, global);
+}
+
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 8-16.
+// Slow path for @@replace.
+function RegExpReplaceSlowPath(rx, S, lengthS, replaceValue,
+                               functionalReplace, firstDollarIndex, global)
+{
     // Step 8.
     var fullUnicode = false;
     if (global) {
         // Step 8.a.
         fullUnicode = !!rx.unicode;
 
         // Step 8.b.
         rx.lastIndex = 0;
@@ -265,74 +286,21 @@ function RegExpReplace(string, replaceVa
         // Step 14.d.
         var matchLength = matched.length;
 
         // Steps 14.e-f.
         var position = std_Math_max(std_Math_min(ToInteger(result.index), lengthS), 0);
 
         var n, capN, replacement;
         if (functionalReplace || firstDollarIndex !== -1) {
-            // Step 14.h.
-            var captures = [];
-            var capturesLength = 0;
-
-            // Step 14.j.i (reordered).
-            // For nCaptures <= 4 case, call replaceValue directly, otherwise
-            // use std_Function_apply with all arguments stored in captures.
-            // In latter case, store matched as the first element here, to
-            // avoid unshift later.
-            if (functionalReplace && nCaptures > 4)
-                _DefineDataProperty(captures, capturesLength++, matched);
-
-            // Step 14.g, 14.i, 14.i.iv.
-            for (n = 1; n <= nCaptures; n++) {
-                // Step 14.i.i.
-                capN = result[n];
-
-                // Step 14.i.ii.
-                if (capN !== undefined)
-                    capN = ToString(capN);
-
-                // Step 14.i.iii.
-                _DefineDataProperty(captures, capturesLength++, capN);
-            }
+            // Steps 14.g-j.
+            replacement = RegExpGetComplexReplacement(result, matched, S, position,
 
-            // Step 14.j.
-            if (functionalReplace) {
-                switch (nCaptures) {
-                  case 0:
-                    replacement = ToString(replaceValue(matched, position, S));
-                    break;
-                  case 1:
-                    replacement = ToString(replaceValue(matched, captures[0], position, S));
-                    break;
-                  case 2:
-                    replacement = ToString(replaceValue(matched, captures[0], captures[1],
-                                                        position, S));
-                    break;
-                  case 3:
-                    replacement = ToString(replaceValue(matched, captures[0], captures[1],
-                                                        captures[2], position, S));
-                    break;
-                  case 4:
-                    replacement = ToString(replaceValue(matched, captures[0], captures[1],
-                                                        captures[2], captures[3], position, S));
-                    break;
-                  default:
-                    // Steps 14.j.ii-v.
-                    _DefineDataProperty(captures, capturesLength++, position);
-                    _DefineDataProperty(captures, capturesLength++, S);
-                    replacement = ToString(callFunction(std_Function_apply, replaceValue, null,
-                                                        captures));
-                }
-            } else {
-                // Steps 14.k.i.
-                replacement = RegExpGetSubstitution(matched, S, position, captures, replaceValue,
-                                                    firstDollarIndex);
-            }
+                                                      nCaptures, replaceValue,
+                                                      functionalReplace, firstDollarIndex);
         } else {
             // Step 14.g, 14.i, 14.i.iv.
             // We don't need captures array, but ToString is visible to script.
             for (n = 1; n <= nCaptures; n++) {
                 // Step 14.i.i-ii.
                 capN = result[n];
 
                 // Step 14.i.ii.
@@ -356,21 +324,92 @@ function RegExpReplace(string, replaceVa
     // Step 15.
     if (nextSourcePosition >= lengthS)
         return accumulatedResult;
 
     // Step 16.
     return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
 }
 
-// ES 2016 draft Mar 25, 2016 21.2.5.8 steps 8.a-16.
-// Optimized path for @@replace with global flag, short string.
+// 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
+//   * RegExpGlobalReplaceOptSubst
+//   * RegExpLocalReplaceOptFunc
+//   * RegExpLocalReplaceOptSubst
+//   * RegExpReplaceSlowPath
+function RegExpGetComplexReplacement(result, matched, S, position,
+                                     nCaptures, replaceValue,
+                                     functionalReplace, firstDollarIndex)
+{
+    // Step 14.h.
+    var captures = [];
+    var capturesLength = 0;
+
+    // Step 14.j.i (reordered).
+    // For `nCaptures` <= 4 case, call `replaceValue` directly, otherwise
+    // use `std_Function_apply` with all arguments stored in `captures`.
+    // In latter case, store `matched` as the first element here, to
+    // avoid unshift later.
+    if (functionalReplace && nCaptures > 4)
+        _DefineDataProperty(captures, capturesLength++, matched);
+
+    // Step 14.g, 14.i, 14.i.iv.
+    for (var n = 1; n <= nCaptures; n++) {
+        // Step 14.i.i.
+        var capN = result[n];
+
+        // Step 14.i.ii.
+        if (capN !== undefined)
+            capN = ToString(capN);
+
+        // Step 14.i.iii.
+        _DefineDataProperty(captures, capturesLength++, capN);
+    }
+
+    // Step 14.j.
+    if (functionalReplace) {
+        switch (nCaptures) {
+          case 0:
+            return ToString(replaceValue(matched, position, S));
+         case 1:
+            return ToString(replaceValue(matched, captures[0], position, S));
+          case 2:
+            return ToString(replaceValue(matched, captures[0], captures[1],
+                                         position, S));
+          case 3:
+            return ToString(replaceValue(matched, captures[0], captures[1],
+                                         captures[2], position, S));
+          case 4:
+            return  ToString(replaceValue(matched, captures[0], captures[1],
+                                          captures[2], captures[3], position, S));
+          default:
+            // Steps 14.j.ii-v.
+            _DefineDataProperty(captures, capturesLength++, position);
+            _DefineDataProperty(captures, capturesLength++, S);
+            return ToString(callFunction(std_Function_apply, replaceValue, null, captures));
+        }
+    }
+
+    // Steps 14.k.i.
+    return RegExpGetSubstitution(matched, S, position, captures, replaceValue,
+                                 firstDollarIndex);
+}
+
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 8-16.
+// Optimized path for @@replace with the following conditions:
+//   * global flag is true
+//   * S is a short string (lengthS < 0x7fff)
+//   * replaceValue is a string without "$"
 function RegExpGlobalReplaceShortOpt(rx, S, lengthS, replaceValue)
 {
-   // Step 8.a.
+    // Step 8.a.
     var fullUnicode = !!rx.unicode;
 
     // Step 8.b.
     var lastIndex = 0;
     rx.lastIndex = 0;
 
     // Step 12 (reordered).
     var accumulatedResult = "";
@@ -411,135 +450,69 @@ function RegExpGlobalReplaceShortOpt(rx,
     // Step 15.
     if (nextSourcePosition >= lengthS)
         return accumulatedResult;
 
     // Step 16.
     return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
 }
 
-// ES 2016 draft Mar 25, 2016 21.2.5.8 steps 8.a-16.
-// Optimized path for @@replace with global flag.
-function RegExpGlobalReplaceOpt(rx, S, lengthS, replaceValue)
-{
-   // Step 8.a.
-    var fullUnicode = !!rx.unicode;
-
-    // Step 8.b.
-    var lastIndex = 0;
-    rx.lastIndex = 0;
-
-    // Step 12 (reordered).
-    var accumulatedResult = "";
-
-    // Step 13 (reordered).
-    var nextSourcePosition = 0;
-
-    var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
-    var sticky = !!(flags & REGEXP_STICKY_FLAG);
-
-    // Step 11.
-    while (true) {
-        // Step 11.a.
-        var result = RegExpMatcher(rx, S, lastIndex, sticky);
+// ES 2017 draft rev 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 8-16.
+// Optimized path for @@replace.
 
-        // Step 11.b.
-        if (result === null)
-            break;
-
-        // Step 11.c.iii.1.
-        var matchStr = result[0];
-
-        // Step 14.c.
-        var matched = result[0];
-
-        // Step 14.d.
-        var matchLength = matched.length;
+// Conditions:
+//   * global flag is true
+//   * replaceValue is a string without "$"
+#define FUNC_NAME RegExpGlobalReplaceOpt
+#include "RegExpGlobalReplaceOpt.h.js"
+#undef FUNC_NAME
 
-        // Steps 14.e-f.
-        var position = result.index;
-        lastIndex = position + matchLength;
-
-        // Step 14.l.ii.
-        accumulatedResult += Substring(S, nextSourcePosition,
-                                       position - nextSourcePosition) + replaceValue;
-
-        // Step 14.l.iii.
-        nextSourcePosition = lastIndex;
-
-        // Step 11.c.iii.2.
-        if (matchLength === 0) {
-            lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
-            if (lastIndex > lengthS)
-                break;
-        }
-    }
+// Conditions:
+//   * global flag is true
+//   * replaceValue is a function
+#define FUNC_NAME RegExpGlobalReplaceOptFunc
+#define FUNCTIONAL
+#include "RegExpGlobalReplaceOpt.h.js"
+#undef FUNCTIONAL
+#undef FUNC_NAME
 
-    // Step 15.
-    if (nextSourcePosition >= lengthS)
-        return accumulatedResult;
-
-    // Step 16.
-    return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
-}
+// 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
 
-// ES 2016 draft Mar 25, 2016 21.2.5.8 steps 11.a-16.
-// Optimized path for @@replace without global flag.
-function RegExpLocalReplaceOpt(rx, S, lengthS, replaceValue)
-{
-    var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
-    var sticky = !!(flags & REGEXP_STICKY_FLAG);
-
-    var lastIndex;
-    if (sticky) {
-        lastIndex = ToLength(rx.lastIndex);
-        if (lastIndex > lengthS) {
-            rx.lastIndex = 0;
-            return S;
-        }
-    } else {
-        lastIndex = 0;
-    }
-
-    // Step 11.a.
-    var result = RegExpMatcher(rx, S, lastIndex, sticky);
+// Conditions:
+//   * global flag is false
+//   * replaceValue is a string without "$"
+#define FUNC_NAME RegExpLocalReplaceOpt
+#include "RegExpLocalReplaceOpt.h.js"
+#undef FUNC_NAME
 
-    // Step 11.b.
-    if (result === null) {
-        rx.lastIndex = 0;
-        return S;
-    }
-
-    // Steps 11.c, 12-13, 14.a-b (skipped).
-
-    // Step 14.c.
-    var matched = result[0];
-
-    // Step 14.d.
-    var matchLength = matched.length;
-
-    // Step 14.e-f.
-    var position = result.index;
+// Conditions:
+//   * global flag is false
+//   * replaceValue is a function
+#define FUNC_NAME RegExpLocalReplaceOptFunc
+#define FUNCTIONAL
+#include "RegExpLocalReplaceOpt.h.js"
+#undef FUNCTIONAL
+#undef FUNC_NAME
 
-    // Step 14.l.ii.
-    var accumulatedResult = Substring(S, 0, position) + replaceValue;
-
-    // Step 14.l.iii.
-    var nextSourcePosition = position + matchLength;
-
-   if (sticky)
-       rx.lastIndex = nextSourcePosition;
-
-    // Step 15.
-    if (nextSourcePosition >= lengthS)
-        return accumulatedResult;
-
-    // Step 16.
-    return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
-}
+// Conditions:
+//   * global flag is false
+//   * replaceValue is a string with "$"
+#define FUNC_NAME RegExpLocalReplaceOptSubst
+#define SUBSTITUTION
+#include "RegExpLocalReplaceOpt.h.js"
+#undef SUBSTITUTION
+#undef FUNC_NAME
 
 // ES 2016 draft Mar 25, 2016 21.2.5.9.
 function RegExpSearch(string) {
     // Step 1.
     var rx = this;
 
     // Step 2.
     if (!IsObject(rx))
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/RegExpGlobalReplaceOpt.h.js
@@ -0,0 +1,101 @@
+// Function template for the following functions:
+//   * RegExpGlobalReplaceOpt
+//   * RegExpGlobalReplaceOptFunc
+//   * RegExpGlobalReplaceOptSubst
+// 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 "$"
+
+// 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
+                  )
+{
+    // Step 8.a.
+    var fullUnicode = !!rx.unicode;
+
+    // Step 8.b.
+    var lastIndex = 0;
+    rx.lastIndex = 0;
+
+    // Step 12 (reordered).
+    var accumulatedResult = "";
+
+    // Step 13 (reordered).
+    var nextSourcePosition = 0;
+
+    var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
+    var sticky = !!(flags & REGEXP_STICKY_FLAG);
+
+    // Step 11.
+    while (true) {
+        // Step 11.a.
+        var result = RegExpMatcher(rx, S, lastIndex, sticky);
+
+        // Step 11.b.
+        if (result === null)
+            break;
+
+#if defined(FUNCTIONAL) || defined(SUBSTITUTION)
+        // Steps 14.a-b.
+        var nCaptures = std_Math_max(result.length - 1, 0);
+#endif
+
+        // Step 14.c.
+        var matched = result[0];
+
+        // Step 14.d.
+        var matchLength = matched.length;
+
+        // Steps 14.e-f.
+        var position = result.index;
+        lastIndex = position + matchLength;
+
+        // Steps g-j.
+        var replacement;
+#if defined(FUNCTIONAL)
+        replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+                                                  nCaptures, replaceValue,
+                                                  true, -1);
+#elif defined(SUBSTITUTION)
+        replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+                                                  nCaptures, replaceValue,
+                                                  false, firstDollarIndex);
+#else
+        replacement = replaceValue;
+#endif
+
+        // Step 14.l.ii.
+        accumulatedResult += Substring(S, nextSourcePosition,
+                                       position - nextSourcePosition) + replacement;
+
+        // Step 14.l.iii.
+        nextSourcePosition = lastIndex;
+
+        // Step 11.c.iii.2.
+        if (matchLength === 0) {
+            lastIndex = fullUnicode ? AdvanceStringIndex(S, lastIndex) : lastIndex + 1;
+            if (lastIndex > lengthS)
+                break;
+        }
+    }
+
+    // Step 15.
+    if (nextSourcePosition >= lengthS)
+        return accumulatedResult;
+
+    // Step 16.
+    return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/builtin/RegExpLocalReplaceOpt.h.js
@@ -0,0 +1,95 @@
+// Function template for the following functions:
+//   * RegExpLocalReplaceOpt
+//   * RegExpLocalReplaceOptFunc
+//   * RegExpLocalReplaceOptSubst
+// Define the following macro and include this file to declare function:
+//   * FUNC_NAME     -- function name (required)
+//       e.g.
+//         #define FUNC_NAME RegExpLocalReplaceOpt
+// 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 "$"
+
+// ES 2017 draft 03bfda119d060aca4099d2b77cf43f6d4f11cfa2 21.2.5.8
+// steps 11.a-16.
+// Optimized path for @@replace with the following conditions:
+//   * global flag is false
+function FUNC_NAME(rx, S, lengthS, replaceValue
+#ifdef SUBSTITUTION
+                   , firstDollarIndex
+#endif
+                  )
+{
+    var flags = UnsafeGetInt32FromReservedSlot(rx, REGEXP_FLAGS_SLOT);
+    var sticky = !!(flags & REGEXP_STICKY_FLAG);
+
+    var lastIndex;
+    if (sticky) {
+        lastIndex = ToLength(rx.lastIndex);
+        if (lastIndex > lengthS) {
+            rx.lastIndex = 0;
+            return S;
+        }
+    } else {
+        lastIndex = 0;
+    }
+
+    // Step 11.a.
+    var result = RegExpMatcher(rx, S, lastIndex, sticky);
+
+    // Step 11.b.
+    if (result === null) {
+        rx.lastIndex = 0;
+        return S;
+    }
+
+    // Steps 11.c, 12-13, 14.a-b (skipped).
+
+#if defined(FUNCTIONAL) || defined(SUBSTITUTION)
+    // Steps 14.a-b.
+    var nCaptures = std_Math_max(result.length - 1, 0);
+#endif
+
+    // Step 14.c.
+    var matched = result[0];
+
+    // Step 14.d.
+    var matchLength = matched.length;
+
+    // Step 14.e-f.
+    var position = result.index;
+
+    // Step 14.l.iii (reordered)
+    // To set rx.lastIndex before RegExpGetComplexReplacement.
+    var nextSourcePosition = position + matchLength;
+
+    if (sticky)
+       rx.lastIndex = nextSourcePosition;
+
+    var replacement;
+    // Steps g-j.
+#if defined(FUNCTIONAL)
+    replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+                                              nCaptures, replaceValue,
+                                              true, -1);
+#elif defined(SUBSTITUTION)
+    replacement = RegExpGetComplexReplacement(result, matched, S, position,
+
+                                              nCaptures, replaceValue,
+                                              false, firstDollarIndex);
+#else
+    replacement = replaceValue;
+#endif
+
+    // Step 14.l.ii.
+    var accumulatedResult = Substring(S, 0, position) + replacement;
+
+    // Step 15.
+    if (nextSourcePosition >= lengthS)
+        return accumulatedResult;
+
+    // Step 16.
+    return accumulatedResult + Substring(S, nextSourcePosition, lengthS - nextSourcePosition);
+}
--- a/js/src/builtin/embedjs.py
+++ b/js/src/builtin/embedjs.py
@@ -134,18 +134,18 @@ def get_config_defines(buildconfig):
   # Collect defines equivalent to ACDEFINES and add MOZ_DEBUG_DEFINES.
   env = {key: value for key, value in buildconfig.defines.iteritems()
          if key not in buildconfig.non_global_defines}
   for define in buildconfig.substs['MOZ_DEBUG_DEFINES']:
     env[define] = 1
   return env
 
 def process_inputs(namespace, c_out, msg_file, inputs):
-  deps = [path for path in inputs if path.endswith(".h")]
-  sources = [path for path in inputs if path.endswith(".js")]
+  deps = [path for path in inputs if path.endswith(".h") or path.endswith(".h.js")]
+  sources = [path for path in inputs if path.endswith(".js") and not path.endswith(".h.js")]
   assert len(deps) + len(sources) == len(inputs)
   cxx = shlex.split(buildconfig.substs['CXX'])
   cxx_option = buildconfig.substs['PREPROCESS_OPTION']
   env = get_config_defines(buildconfig)
   js_path = re.sub(r"\.out\.h$", "", c_out.name) + ".js"
   msgs = messages(msg_file)
   with open(js_path, 'w') as js_out:
     embed(cxx, cxx_option, msgs, sources, c_out, js_out, namespace, env)
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -737,16 +737,18 @@ selfhosted.inputs = [
     'builtin/IntlData.js',
     'builtin/Iterator.js',
     'builtin/Map.js',
     'builtin/Module.js',
     'builtin/Number.js',
     'builtin/Object.js',
     'builtin/Reflect.js',
     'builtin/RegExp.js',
+    'builtin/RegExpGlobalReplaceOpt.h.js',
+    'builtin/RegExpLocalReplaceOpt.h.js',
     'builtin/String.js',
     'builtin/Set.js',
     'builtin/Sorting.js',
     'builtin/TypedArray.js',
     'builtin/TypedObject.js',
     'builtin/WeakSet.js'
 ]