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 294459 b9ac34e99e3048648c839de22de82f97eb4ce3a7
parent 294458 4307f2ac86811069f1ca9fa21b0200fcae3f4fe2
child 294460 b12d4b8634f3e1997dcaca3ac742c5ea3ea12df1
push id75550
push userarai_a@mac.com
push dateFri, 22 Apr 2016 12:31:05 +0000
treeherdermozilla-inbound@b9ac34e99e30 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
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 - 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'
 ]