diff --git a/test/built-ins/RegExp/named-groups/functional-replace-global.js b/test/built-ins/RegExp/named-groups/functional-replace-global.js
new file mode 100644
index 0000000000000000000000000000000000000000..2fb5d49c8211ca13e065837e2ffbf9ca1d64e26c
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/functional-replace-global.js
@@ -0,0 +1,56 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+  Function argument to String.prototype.replace gets groups as the last argument
+esid: sec-regexp.prototype-@@replace
+features: [regexp-named-groups]
+info: >
+  RegExp.prototype [ @@replace ] ( string, replaceValue )
+    14. Repeat, for each result in results,
+      j. Let namedCaptures be ? Get(result, "groups").
+      k. If functionalReplace is true, then
+        iv. If namedCaptures is not undefined,
+          1. Append namedCaptures as the last element of replacerArgs.
+---*/
+
+let source = "(?<fst>.)(?<snd>.)";
+let alternateSource = "(?<fst>.)|(?<snd>.)";
+
+for (let flags of ["g", "gu"]) {
+  let i = 0;
+  let re = new RegExp(source, flags);
+  let result = "abcd".replace(re,
+      (match, fst, snd, offset, str, groups) => {
+    if (i == 0) {
+      assert.sameValue("ab", match);
+      assert.sameValue("a", groups.fst);
+      assert.sameValue("b", groups.snd);
+      assert.sameValue("a", fst);
+      assert.sameValue("b", snd);
+      assert.sameValue(0, offset);
+      assert.sameValue("abcd", str);
+    } else if (i == 1) {
+      assert.sameValue("cd", match);
+      assert.sameValue("c", groups.fst);
+      assert.sameValue("d", groups.snd);
+      assert.sameValue("c", fst);
+      assert.sameValue("d", snd);
+      assert.sameValue(2, offset);
+      assert.sameValue("abcd", str);
+    } else {
+      assertUnreachable();
+    }
+    i++;
+    return `${groups.snd}${groups.fst}`;
+  });
+  assert.sameValue("badc", result);
+  assert.sameValue(i, 2);
+
+  let re2 = new RegExp(alternateSource, flags);
+  assert.sameValue("undefinedundefinedundefinedundefined",
+      "abcd".replace(re2,
+            (match, fst, snd, offset, str, groups) => groups.snd));
+}
+
diff --git a/test/built-ins/RegExp/named-groups/functional-replace-non-global.js b/test/built-ins/RegExp/named-groups/functional-replace-non-global.js
new file mode 100644
index 0000000000000000000000000000000000000000..5186a88fa2b255798176a5c9ea838ead7297cc3d
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/functional-replace-non-global.js
@@ -0,0 +1,43 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+  Function argument to String.prototype.replace gets groups as the last argument
+esid: sec-regexp.prototype-@@replace
+features: [regexp-named-groups]
+info: >
+  RegExp.prototype [ @@replace ] ( string, replaceValue )
+    14. Repeat, for each result in results,
+      j. Let namedCaptures be ? Get(result, "groups").
+      k. If functionalReplace is true, then
+        iv. If namedCaptures is not undefined,
+          1. Append namedCaptures as the last element of replacerArgs.
+---*/
+
+let source = "(?<fst>.)(?<snd>.)";
+let alternateSource = "(?<fst>.)|(?<snd>.)";
+
+for (let flags of ["", "u"]) {
+  let i = 0;
+  let re = new RegExp(source, flags);
+  let result = "abcd".replace(re,
+      (match, fst, snd, offset, str, groups) => {
+    assert.sameValue(i++, 0);
+    assert.sameValue("ab", match);
+    assert.sameValue("a", groups.fst);
+    assert.sameValue("b", groups.snd);
+    assert.sameValue("a", fst);
+    assert.sameValue("b", snd);
+    assert.sameValue(0, offset);
+    assert.sameValue("abcd", str);
+    return `${groups.snd}${groups.fst}`;
+  });
+  assert.sameValue("bacd", result);
+  assert.sameValue(i, 1);
+
+  let re2 = new RegExp(alternateSource, flags);
+  assert.sameValue("undefinedbcd",
+      "abcd".replace(re2,
+            (match, fst, snd, offset, str, groups) => groups.snd));
+}
diff --git a/test/built-ins/RegExp/named-groups/groups-properties.js b/test/built-ins/RegExp/named-groups/groups-properties.js
new file mode 100644
index 0000000000000000000000000000000000000000..6d7b46ec63549a88e0264bb57a6c69cb91994415
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/groups-properties.js
@@ -0,0 +1,39 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Properties of the groups object are created with CreateDataProperty
+includes: [compareArray.js, propertyHelper.js]
+esid: sec-regexpbuiltinexec
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: RegExpBuiltinExec ( R, S )
+    25. For each integer i such that i > 0 and i ≤ n
+      f. If the ith capture of R was defined with a GroupName,
+        i. Let s be the StringValue of the corresponding RegExpIdentifierName.
+        ii. Perform ! CreateDataProperty(groups, s, capturedValue).
+---*/
+
+// Properties created on result.groups in textual order.
+assert(compareArray(["fst", "snd"],
+             Object.getOwnPropertyNames(
+                 /(?<fst>.)|(?<snd>.)/u.exec("abcd").groups)));
+
+// Properties are created with Define, not Set
+let counter = 0;
+Object.defineProperty(Object.prototype, 'x', {set() { counter++; }});
+let match = /(?<x>.)/.exec('a');
+let groups = match.groups;
+assert.sameValue(counter, 0);
+
+// Properties are writable, enumerable and configurable
+// (from CreateDataProperty)
+verifyWritable(groups, "x");
+verifyEnumerable(groups, "x");
+verifyConfigurable(groups, "x");
+
+// The '__proto__' property on the groups object is not special,
+// and does not affect the [[Prototype]] of the resulting groups object.
+groups = /(?<__proto__>a)/u.exec("a").groups;
+assert.sameValue("a", groups.__proto__);
+assert.sameValue(null, Object.getPrototypeOf(groups));
diff --git a/test/built-ins/RegExp/named-groups/lookbehind.js b/test/built-ins/RegExp/named-groups/lookbehind.js
new file mode 100644
index 0000000000000000000000000000000000000000..0009bc5008f727d344b0074c589beaea46339621
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/lookbehind.js
@@ -0,0 +1,46 @@
+// Copyright 2018 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Named groups can be used in conjunction with lookbehind
+esid: pending
+features: [regexp-named-groups, regexp-lookbehind]
+includes: [compareArray.js]
+---*/
+
+// Unicode mode
+assert(compareArray(["f", "c"], "abcdef".match(/(?<=(?<a>\w){3})f/u)));
+assert.sameValue("c", "abcdef".match(/(?<=(?<a>\w){3})f/u).groups.a);
+assert.sameValue("b", "abcdef".match(/(?<=(?<a>\w){4})f/u).groups.a);
+assert.sameValue("a", "abcdef".match(/(?<=(?<a>\w)+)f/u).groups.a);
+assert.sameValue(null, "abcdef".match(/(?<=(?<a>\w){6})f/u));
+
+assert(compareArray(["f", ""], "abcdef".match(/((?<=\w{3}))f/u)));
+assert(compareArray(["f", ""], "abcdef".match(/(?<a>(?<=\w{3}))f/u)));
+
+assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\d){3})f/u)));
+assert.sameValue(null, "abcdef".match(/(?<!(?<a>\D){3})f/u));
+
+assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\D){3})f|f/u)));
+assert(compareArray(["f", undefined], "abcdef".match(/(?<a>(?<!\D{3}))f|f/u)));
+
+// Non-Unicode mode
+assert(compareArray(["f", "c"], "abcdef".match(/(?<=(?<a>\w){3})f/)));
+assert.sameValue("c", "abcdef".match(/(?<=(?<a>\w){3})f/).groups.a);
+assert.sameValue("b", "abcdef".match(/(?<=(?<a>\w){4})f/).groups.a);
+assert.sameValue("a", "abcdef".match(/(?<=(?<a>\w)+)f/).groups.a);
+assert.sameValue(null, "abcdef".match(/(?<=(?<a>\w){6})f/));
+
+assert(compareArray(["f", ""], "abcdef".match(/((?<=\w{3}))f/)));
+assert(compareArray(["f", ""], "abcdef".match(/(?<a>(?<=\w{3}))f/)));
+
+assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\d){3})f/)));
+assert.sameValue(null, "abcdef".match(/(?<!(?<a>\D){3})f/));
+
+assert(compareArray(["f", undefined], "abcdef".match(/(?<!(?<a>\D){3})f|f/)));
+assert(compareArray(["f", undefined], "abcdef".match(/(?<a>(?<!\D{3}))f|f/)));
+
+// Even within a lookbehind, properties are created in left to right order
+assert(compareArray(["fst", "snd"],
+             Object.getOwnPropertyNames(
+                 /(?<=(?<fst>.)|(?<snd>.))/u.exec("abcd").groups)));
diff --git a/test/built-ins/RegExp/named-groups/non-unicode-malformed.js b/test/built-ins/RegExp/named-groups/non-unicode-malformed.js
new file mode 100644
index 0000000000000000000000000000000000000000..6a6d7bd9ecfbf2c66d55ed4932d063ef226b9cc2
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/non-unicode-malformed.js
@@ -0,0 +1,44 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+  Named groups in Unicode RegExps have some syntax errors and some
+  compatibility escape fallback behavior.
+esid: pending
+features: [regexp-named-groups, regexp-lookbehind]
+includes: [compareArray.js]
+---*/
+
+assert.throws(SyntaxError, () => eval("/(?<>a)/"));
+assert.throws(SyntaxError, () => eval("/(?<aa)/"));
+assert.throws(SyntaxError, () => eval("/(?<42a>a)/"));
+assert.throws(SyntaxError, () => eval("/(?<:a>a)/"));
+assert.throws(SyntaxError, () => eval("/(?<a:>a)/"));
+assert.throws(SyntaxError, () => eval("/(?<a>a)(?<a>a)/"));
+assert.throws(SyntaxError, () => eval("/(?<a>a)(?<b>b)(?<a>a)/"));
+assert(/\k<a>/.test("k<a>"));
+assert(/\k<4>/.test("k<4>"));
+assert(/\k<a/.test("k<a"));
+assert(/\k/.test("k"));
+assert.throws(SyntaxError, () => eval("/(?<a>.)\\k/"));
+assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<a/"));
+assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<b>/"));
+assert.throws(SyntaxError, () => eval("/(?<a>a)\\k<ab>/"));
+assert.throws(SyntaxError, () => eval("/(?<ab>a)\\k<a>/"));
+assert.throws(SyntaxError, () => eval("/\\k<a>(?<ab>a)/"));
+assert.throws(SyntaxError, () => eval("/\\k<a(?<a>a)/"));
+assert(/(?<a>\a)/.test("a"));
+
+assert(compareArray(["k<a>"], "xxxk<a>xxx".match(/\k<a>/)));
+assert(compareArray(["k<a"], "xxxk<a>xxx".match(/\k<a/)));
+
+// A couple of corner cases around '\k' as named back-references vs. identity
+// escapes.
+assert(/\k<a>(?<=>)a/.test("k<a>a"));
+assert(/\k<a>(?<!a)a/.test("k<a>a"));
+assert(/\k<a>(<a>x)/.test("k<a><a>x"));
+assert(/\k<a>(?<a>x)/.test("x"));
+assert.throws(SyntaxError, () => eval("/\\k<a>(?<b>x)/"));
+assert.throws(SyntaxError, () => eval("/\\k<a(?<a>.)/"));
+assert.throws(SyntaxError, () => eval("/\\k(?<a>.)/"));
diff --git a/test/built-ins/RegExp/named-groups/non-unicode-match.js b/test/built-ins/RegExp/named-groups/non-unicode-match.js
new file mode 100644
index 0000000000000000000000000000000000000000..4e29bb24a4638bf91fa3dfb636ec9877fe20ca27
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/non-unicode-match.js
@@ -0,0 +1,43 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Basic matching cases with non-Unicode groups
+esid: pending
+features: [regexp-named-groups]
+includes: [compareArray.js]
+---*/
+
+assert(compareArray(["a", "a"], "bab".match(/(?<a>a)/)));
+assert(compareArray(["a", "a"], "bab".match(/(?<a42>a)/)));
+assert(compareArray(["a", "a"], "bab".match(/(?<_>a)/)));
+assert(compareArray(["a", "a"], "bab".match(/(?<$>a)/)));
+assert(compareArray(["bab", "a"], "bab".match(/.(?<$>a)./)));
+assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(.)/)));
+assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(?<b>.)/)));
+assert(compareArray(["bab", "ab"], "bab".match(/.(?<a>\w\w)/)));
+assert(compareArray(["bab", "bab"], "bab".match(/(?<a>\w\w\w)/)));
+assert(compareArray(["bab", "ba", "b"], "bab".match(/(?<a>\w\w)(?<b>\w)/)));
+
+let {a, b, c} = /(?<a>.)(?<b>.)(?<c>.)\k<c>\k<b>\k<a>/.exec("abccba").groups;
+assert.sameValue(a, "a");
+assert.sameValue(b, "b");
+assert.sameValue(c, "c");
+
+assert(compareArray("bab".match(/(a)/), "bab".match(/(?<a>a)/)));
+assert(compareArray("bab".match(/(a)/), "bab".match(/(?<a42>a)/)));
+assert(compareArray("bab".match(/(a)/), "bab".match(/(?<_>a)/)));
+assert(compareArray("bab".match(/(a)/), "bab".match(/(?<$>a)/)));
+assert(compareArray("bab".match(/.(a)./), "bab".match(/.(?<$>a)./)));
+assert(compareArray("bab".match(/.(a)(.)/), "bab".match(/.(?<a>a)(.)/)));
+assert(compareArray("bab".match(/.(a)(.)/), "bab".match(/.(?<a>a)(?<b>.)/)));
+assert(compareArray("bab".match(/.(\w\w)/), "bab".match(/.(?<a>\w\w)/)));
+assert(compareArray("bab".match(/(\w\w\w)/), "bab".match(/(?<a>\w\w\w)/)));
+assert(compareArray("bab".match(/(\w\w)(\w)/), "bab".match(/(?<a>\w\w)(?<b>\w)/)));
+
+assert(compareArray(["bab", "b"], "bab".match(/(?<b>b).\1/)));
+assert(compareArray(["baba", "b", "a"], "baba".match(/(.)(?<a>a)\1\2/)));
+assert(compareArray(["baba", "b", "a", "b", "a"],
+    "baba".match(/(.)(?<a>a)(?<b>\1)(\2)/)));
+assert(compareArray(["<a", "<"], "<a".match(/(?<lt><)a/)));
+assert(compareArray([">a", ">"], ">a".match(/(?<gt>>)a/)));
diff --git a/test/built-ins/RegExp/named-groups/non-unicode-property-names.js b/test/built-ins/RegExp/named-groups/non-unicode-property-names.js
new file mode 100644
index 0000000000000000000000000000000000000000..fea50314a429920f857367b933bc22be034fc3f6
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/non-unicode-property-names.js
@@ -0,0 +1,45 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Exotic named group names in non-Unicode RegExps
+esid: pending
+features: [regexp-named-groups]
+includes: [compareArray.js]
+---*/
+
+assert.sameValue("a", /(?<Ï€>a)/.exec("bab").groups.Ï€);
+assert.throws(SyntaxError, () => eval('/(?<\\u{03C0}>a)/'), "\\u{} escapes allowed only in Unicode mode");
+assert.sameValue("a", /(?<Ï€>a)/.exec("bab").groups.\u03C0);
+assert.sameValue("a", /(?<$>a)/.exec("bab").groups.$);
+assert.sameValue("a", /(?<_>a)/.exec("bab").groups._);
+assert.throws(SyntaxError, () => eval('/(?<$𐒤>a)/'), "Individual surrogates not in ID_Continue);
+assert.sameValue("a", /(?<_\u200C>a)/.exec("bab").groups._\u200C);
+assert.sameValue("a", /(?<_\u200D>a)/.exec("bab").groups._\u200D);
+assert.sameValue("a", /(?<ಠ_ಠ>a)/.exec("bab").groups.ಠ_ಠ);
+assert.throws(SyntaxError, () => eval('/(?<❤>a)/'));
+assert.throws(SyntaxError, () => eval('/(?<𐒤>a)/'), "Individual surrogate not in ID_Start.");
+
+// Unicode escapes in capture names.
+assert.throws(SyntaxError, () => eval("/(?<a\\uD801\uDCA4>.)/"));
+assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/"));
+assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/"));
+assert(/(?<\u0041>.)/.test("a"));
+assert.throws(SyntaxError, () => eval("/(?<a\\u{104A4}>.)/"));
+assert.throws(SyntaxError, () => eval("/(?<a\\u{10FFFF}>.)/"));
+assert.throws(SyntaxError, () => eval("/(?<a\uD801>.)/"), "Lea");
+assert.throws(SyntaxError, () => eval("/(?<a\uDCA4>.)/"), "Trai");
+assert(RegExp("(?<\u{0041}>.)").test("a"), "Non-surrogate");
+assert(RegExp("(?<a\u{104A4}>.)").test("a"), "Surrogate, ID_Continue");
+
+// Bracketed escapes are not allowed;
+// 4-char escapes must be the proper ID_Start/ID_Continue
+assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/"), "Lead");
+assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/"), "Trail");
+assert.throws(SyntaxError, () => eval("/(?<\\u{0041}>.)/"), "Non-surrogate");
+assert.throws(SyntaxError, () => eval("/(?<a\\u{104A4}>.)/"), "Surrogate, ID_Continue");
+assert(RegExp("(?<\\u0041>.)").test("a"), "Non-surrogate");
+
+// Backslash is not allowed as ID_Start and ID_Continue
+assert.throws(SyntaxError, () => eval("/(?<\\>.)/"), "'\' misclassified as ID_Start");
+assert.throws(SyntaxError, () => eval("/(?<a\\>.)/"), "'\' misclassified as ID_Continue");
diff --git a/test/built-ins/RegExp/named-groups/non-unicode-references.js b/test/built-ins/RegExp/named-groups/non-unicode-references.js
new file mode 100644
index 0000000000000000000000000000000000000000..bbf7a0fa5aa0ac762cea9f1eebe36cf4e98faa2b
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/non-unicode-references.js
@@ -0,0 +1,34 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Named backreferences in non-Unicode RegExps
+esid: pending
+features: [regexp-named-groups]
+includes: [compareArray.js]
+---*/
+
+// Named references.
+assert(compareArray(["bab", "b"], "bab".match(/(?<b>.).\k<b>/)));
+assert.sameValue(null, "baa".match(/(?<b>.).\k<b>/));
+
+// Reference inside group.
+assert(compareArray(["bab", "b"], "bab".match(/(?<a>\k<a>\w)../)));
+assert.sameValue("b", "bab".match(/(?<a>\k<a>\w)../).groups.a);
+
+// Reference before group.
+assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/)));
+assert.sameValue("b", "bab".match(/\k<a>(?<a>b)\w\k<a>/).groups.a);
+assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/)));
+let {a, b} = "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/).groups;
+assert.sameValue(a, "a");
+assert.sameValue(b, "b");
+
+assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/)));
+assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/)));
+
+// Reference properties.
+assert.sameValue("a", /(?<a>a)(?<b>b)\k<a>/.exec("aba").groups.a);
+assert.sameValue("b", /(?<a>a)(?<b>b)\k<a>/.exec("aba").groups.b);
+assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>/.exec("aba").groups.c);
+assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>|(?<c>c)/.exec("aba").groups.c);
diff --git a/test/built-ins/RegExp/named-groups/string-replace-get.js b/test/built-ins/RegExp/named-groups/string-replace-get.js
new file mode 100644
index 0000000000000000000000000000000000000000..2234d995122e35e03ca0c9bee9ea54994ddfb764
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/string-replace-get.js
@@ -0,0 +1,29 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Named substitutions are found by getting the property from the groups object
+esid: sec-getsubstitution
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement )
+
+  Table: Replacement Text Symbol Substitutions
+
+  Unicode Characters: $<
+  Replacement text:
+    2. Otherwise,
+      c. Let capture be ? Get(namedCaptures, groupName).
+      d. If capture is undefined, replace the text through > with the empty string.
+      e. Otherwise, replace the text through this following > with ? ToString(capture).
+---*/
+
+let source = "(?<fst>.)(?<snd>.)|(?<thd>x)";
+for (let flags of ["g", "gu"]) {
+  let re = new RegExp(source, flags);
+  assert.sameValue("badc", "abcd".replace(re, "$<snd>$<fst>"));
+}
+for (let flags of ["", "u"]) {
+  let re = new RegExp(source, flags);
+  assert.sameValue("bacd", "abcd".replace(re, "$<snd>$<fst>"));
+}
diff --git a/test/built-ins/RegExp/named-groups/string-replace-missing.js b/test/built-ins/RegExp/named-groups/string-replace-missing.js
new file mode 100644
index 0000000000000000000000000000000000000000..6d253568c24fcfd1ba3bdcf3ccf3e433c886d9a8
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/string-replace-missing.js
@@ -0,0 +1,25 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: SyntaxError is thrown for malformed replacements
+esid: sec-getsubstitution
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement )
+
+  Table: Replacement Text Symbol Substitutions
+
+  Unicode Characters: $<
+  Replacement text:
+    2. Otherwise,
+      b. If ? HasProperty(namedCaptures, groupName) is false, throw a SyntaxError exception.
+---*/
+
+let source = "(?<fst>.)(?<snd>.)|(?<thd>x)";
+for (let flags of ["", "u", "g", "gu"]) {
+  let re = new RegExp(source, flags);
+  assert.throws(SyntaxError, () => "abcd".replace(re, "$<42$1>"));
+  assert.throws(SyntaxError, () => "abcd".replace(re, "$<fth>"));
+  assert.throws(SyntaxError, () => "abcd".replace(re, "$<$1>"));
+}
diff --git a/test/built-ins/RegExp/named-groups/string-replace-nocaptures.js b/test/built-ins/RegExp/named-groups/string-replace-nocaptures.js
new file mode 100644
index 0000000000000000000000000000000000000000..03fccdc87af38578577c205a5addbfeb4cba90e4
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/string-replace-nocaptures.js
@@ -0,0 +1,31 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: If there are no named captures, don't replace $<>
+esid: sec-getsubstitution
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement )
+
+  Table: Replacement Text Symbol Substitutions
+
+  Unicode Characters: $<
+  Replacement text:
+    1. If namedCaptures is undefined, the replacement text is the literal string $<.
+---*/
+
+// @@replace with a string replacement argument (no named captures).
+
+let source = "(.)(.)|(x)";
+for (let flags of ["", "u", "g", "gu"]) {
+  let re = new RegExp(source, flags);
+  assert.sameValue("$<snd>$<fst>cd", "abcd".replace(re, "$<snd>$<fst>"));
+  assert.sameValue("bacd", "abcd".replace(re, "$2$1"));
+  assert.sameValue("cd", "abcd".replace(re, "$3"));
+  assert.sameValue("$<sndcd", "abcd".replace(re, "$<snd"));
+  assert.sameValue("$<42a>cd", "abcd".replace(re, "$<42$1>"));
+  assert.sameValue("$<fth>cd", "abcd".replace(re, "$<fth>"));
+  assert.sameValue("$<a>cd", "abcd".replace(re, "$<$1>"));
+}
+
diff --git a/test/built-ins/RegExp/named-groups/string-replace-numbered.js b/test/built-ins/RegExp/named-groups/string-replace-numbered.js
new file mode 100644
index 0000000000000000000000000000000000000000..0750bd124c2905678d28e14e9cece41d41b2dae4
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/string-replace-numbered.js
@@ -0,0 +1,29 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Named groups may be accessed in their replacement string by number
+esid: sec-getsubstitution
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement )
+
+  Table: Replacement Text Symbol Substitutions
+
+  Unicode Characters: $n
+  Replacement text:
+    The nth element of captures, where n is a single digit in the range 1 to 9. If
+    n≤m and the nth element of captures is undefined, use the empty String instead.
+    If n>m, the result is implementation-defined.
+---*/
+
+let source = "(?<fst>.)(?<snd>.)|(?<thd>x)";
+for (let flags of ["g", "gu"]) {
+  let re = new RegExp(source, flags);
+  assert.sameValue("badc", "abcd".replace(re, "$2$1"));
+}
+for (let flags of ["", "u"]) {
+  let re = new RegExp(source, flags);
+  assert.sameValue("bacd", "abcd".replace(re, "$2$1"));
+}
+
diff --git a/test/built-ins/RegExp/named-groups/string-replace-unclosed.js b/test/built-ins/RegExp/named-groups/string-replace-unclosed.js
new file mode 100644
index 0000000000000000000000000000000000000000..588bc3ed8571bbc250b325f490235e0d973a1220
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/string-replace-unclosed.js
@@ -0,0 +1,24 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: SyntaxError is thrown for malformed replacements
+esid: sec-getsubstitution
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement )
+
+  Table: Replacement Text Symbol Substitutions
+
+  Unicode Characters: $<
+  Replacement text:
+    2. Otherwise,
+      a. Scan until the next >, throwing a SyntaxError exception if one is not found, and let the enclosed substring be groupName.
+---*/
+
+let source = "(?<fst>.)(?<snd>.)|(?<thd>x)";
+for (let flags of ["", "u", "g", "gu"]) {
+  let re = new RegExp(source, flags);
+  assert.throws(SyntaxError, () => "abcd".replace(re, "$<snd"),
+                "unclosed named group in replacement should throw a SyntaxError");
+}
diff --git a/test/built-ins/RegExp/named-groups/string-replace-undefined.js b/test/built-ins/RegExp/named-groups/string-replace-undefined.js
new file mode 100644
index 0000000000000000000000000000000000000000..5806669ba8705570229bc23e4e5febad01b93d99
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/string-replace-undefined.js
@@ -0,0 +1,28 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: If a named group was not reached, it is replaced by the empty string
+esid: sec-getsubstitution
+features: [regexp-named-groups]
+info: >
+  Runtime Semantics: GetSubstitution( matched, str, position, captures, namedCaptures, replacement )
+
+  Table: Replacement Text Symbol Substitutions
+
+  Unicode Characters: $<
+  Replacement text:
+    2. Otherwise,
+      c. Let capture be ? Get(namedCaptures, groupName).
+      d. If capture is undefined, replace the text through > with the empty string.
+---*/
+
+let source = "(?<fst>.)(?<snd>.)|(?<thd>x)";
+for (let flags of ["g", "gu"]) {
+  let re = new RegExp(source, flags);
+  assert.sameValue("", "abcd".replace(re, "$<thd>"));
+}
+for (let flags of ["", "u"]) {
+  let re = new RegExp(source, flags);
+  assert.sameValue("cd", "abcd".replace(re, "$<thd>"));
+}
diff --git a/test/built-ins/RegExp/named-groups/unicode-malformed.js b/test/built-ins/RegExp/named-groups/unicode-malformed.js
new file mode 100644
index 0000000000000000000000000000000000000000..f0f08a8ed4529e0b4ebc29a73f5f50c39f9280c9
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/unicode-malformed.js
@@ -0,0 +1,27 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Various syntax errors for Unicode RegExps containing named groups
+esid: pending
+features: [regexp-named-groups]
+---*/
+
+assert.throws(SyntaxError, () => eval("/(?<>a)/u"), "Empty name");
+assert.throws(SyntaxError, () => eval("/(?<aa)/u"), "Unterminated name");
+assert.throws(SyntaxError, () => eval("/(?<42a>a)/u"), "Name starting with digits");
+assert.throws(SyntaxError, () => eval("/(?<:a>a)/u"), "Name starting with invalid char");
+assert.throws(SyntaxError, () => eval("/(?<a:>a)/u"), "Name containing with invalid char");
+assert.throws(SyntaxError, () => eval("/(?<a>a)(?<a>a)/u"), "Duplicate name");
+assert.throws(SyntaxError, () => eval("/(?<a>a)(?<b>b)(?<a>a)/u"), "Duplicate name");
+assert.throws(SyntaxError, () => eval("/\\k<a>/u"), "Invalid reference");
+assert.throws(SyntaxError, () => eval("/\\k<a/u"), "Unterminated reference");
+assert.throws(SyntaxError, () => eval("/\\k/u"), "Lone \k");
+assert.throws(SyntaxError, () => eval("/(?<a>.)\\k/u"), "Lone \k");
+assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<a/u"), "Unterminated reference");
+assert.throws(SyntaxError, () => eval("/(?<a>.)\\k<b>/u"), "Invalid reference");
+assert.throws(SyntaxError, () => eval("/(?<a>a)\\k<ab>/u"), "Invalid reference");
+assert.throws(SyntaxError, () => eval("/(?<ab>a)\\k<a>/u"), "Invalid reference");
+assert.throws(SyntaxError, () => eval("/\\k<a>(?<ab>a)/u"), "Invalid reference");
+assert.throws(SyntaxError, () => eval("/(?<a>\\a)/u"), "Identity escape in capture");
+
diff --git a/test/built-ins/RegExp/named-groups/unicode-match.js b/test/built-ins/RegExp/named-groups/unicode-match.js
new file mode 100644
index 0000000000000000000000000000000000000000..f840d4cfe2f9eaa05c14bc175eb80843dc9e33bf
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/unicode-match.js
@@ -0,0 +1,48 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Basic matching cases with Unicode groups
+esid: pending
+features: [regexp-named-groups]
+includes: [compareArray.js]
+---*/
+
+assert(compareArray(["a", "a"], "bab".match(/(?<a>a)/u)));
+assert(compareArray(["a", "a"], "bab".match(/(?<a42>a)/u)));
+assert(compareArray(["a", "a"], "bab".match(/(?<_>a)/u)));
+assert(compareArray(["a", "a"], "bab".match(/(?<$>a)/u)));
+assert(compareArray(["bab", "a"], "bab".match(/.(?<$>a)./u)));
+assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(.)/u)));
+assert(compareArray(["bab", "a", "b"], "bab".match(/.(?<a>a)(?<b>.)/u)));
+assert(compareArray(["bab", "ab"], "bab".match(/.(?<a>\w\w)/u)));
+assert(compareArray(["bab", "bab"], "bab".match(/(?<a>\w\w\w)/u)));
+assert(compareArray(["bab", "ba", "b"], "bab".match(/(?<a>\w\w)(?<b>\w)/u)));
+
+let {a, b, c} = /(?<a>.)(?<b>.)(?<c>.)\k<c>\k<b>\k<a>/u.exec("abccba").groups;
+assert.sameValue(a, "a");
+assert.sameValue(b, "b");
+assert.sameValue(c, "c");
+
+assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<a>a)/u)));
+assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<a42>a)/u)));
+assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<_>a)/u)));
+assert(compareArray("bab".match(/(a)/u), "bab".match(/(?<$>a)/u)));
+assert(compareArray("bab".match(/.(a)./u), "bab".match(/.(?<$>a)./u)));
+assert(compareArray("bab".match(/.(a)(.)/u), "bab".match(/.(?<a>a)(.)/u)));
+assert(compareArray("bab".match(/.(a)(.)/u), "bab".match(/.(?<a>a)(?<b>.)/u)));
+assert(compareArray("bab".match(/.(\w\w)/u), "bab".match(/.(?<a>\w\w)/u)));
+assert(compareArray("bab".match(/(\w\w\w)/u), "bab".match(/(?<a>\w\w\w)/u)));
+assert(compareArray("bab".match(/(\w\w)(\w)/u), "bab".match(/(?<a>\w\w)(?<b>\w)/u)));
+
+assert(compareArray(["bab", "b"], "bab".match(/(?<b>b).\1/u)));
+assert(compareArray(["baba", "b", "a"], "baba".match(/(.)(?<a>a)\1\2/u)));
+assert(compareArray(["baba", "b", "a", "b", "a"],
+    "baba".match(/(.)(?<a>a)(?<b>\1)(\2)/u)));
+assert(compareArray(["<a", "<"], "<a".match(/(?<lt><)a/u)));
+assert(compareArray([">a", ">"], ">a".match(/(?<gt>>)a/u)));
+
+// Nested groups.
+assert(compareArray(["bab", "bab", "ab", "b"], "bab".match(/(?<a>.(?<b>.(?<c>.)))/u)));
+assert(compareArray({a: "bab", b: "ab", c: "b"},
+             "bab".match(/(?<a>.(?<b>.(?<c>.)))/u).groups));
diff --git a/test/built-ins/RegExp/named-groups/unicode-property-names.js b/test/built-ins/RegExp/named-groups/unicode-property-names.js
new file mode 100644
index 0000000000000000000000000000000000000000..149322bb2d0f345c6e563b8505abadd7b7f6a77b
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/unicode-property-names.js
@@ -0,0 +1,46 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Exotic named group names in Unicode RegExps
+esid: pending
+features: [regexp-named-groups]
+---*/
+
+assert.sameValue("a", /(?<Ï€>a)/u.exec("bab").groups.Ï€);
+assert.sameValue("a", /(?<\u{03C0}>a)/u.exec("bab").groups.Ï€);
+assert.sameValue("a", /(?<Ï€>a)/u.exec("bab").groups.\u03C0);
+assert.sameValue("a", /(?<\u{03C0}>a)/u.exec("bab").groups.\u03C0);
+assert.sameValue("a", /(?<$>a)/u.exec("bab").groups.$);
+assert.sameValue("a", /(?<_>a)/u.exec("bab").groups._);
+assert.sameValue("a", /(?<$𐒤>a)/u.exec("bab").groups.$𐒤);
+assert.sameValue("a", /(?<_\u200C>a)/u.exec("bab").groups._\u200C);
+assert.sameValue("a", /(?<_\u200D>a)/u.exec("bab").groups._\u200D);
+assert.sameValue("a", /(?<ಠ_ಠ>a)/u.exec("bab").groups.ಠ_ಠ);
+assert.throws(SyntaxError, () => eval('/(?<❤>a)/u'));
+assert.throws(SyntaxError, () => eval('/(?<𐒤>a)/u'), "ID_Continue but not ID_Start.");
+
+// Unicode escapes in capture names.
+assert(/(?<a\uD801\uDCA4>.)/u.test("a"), "\\u Lead \\u Trail");
+assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/u"), "\\u Lea");
+assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/u"), "\\u Trai");
+assert(/(?<\u0041>.)/u.test("a"), "\\u NonSurrogate");
+assert(/(?<\u{0041}>.)/u.test("a"), "\\u{ Non-surrogate }");
+assert(/(?<a\u{104A4}>.)/u.test("a"), "\\u{ Surrogate, ID_Continue }");
+assert.throws(SyntaxError, () => eval("/(?<a\\u{110000}>.)/u"), "\\u{ Out-of-bounds ");
+assert.throws(SyntaxError, () => eval("/(?<a\uD801>.)/u"), "Lea");
+assert.throws(SyntaxError, () => eval("/(?<a\uDCA4>.)/u"), "Trai");
+assert(RegExp("(?<\u{0041}>.)", "u").test("a"), "Non-surrogate");
+assert(RegExp("(?<a\u{104A4}>.)", "u").test("a"), "Surrogate,ID_Continue");
+
+// Bracketed escapes are not allowed;
+// 4-char escapes must be the proper ID_Start/ID_Continue
+assert.throws(SyntaxError, () => eval("/(?<a\\uD801>.)/u"), "Lead");
+assert.throws(SyntaxError, () => eval("/(?<a\\uDCA4>.)/u"), "Trail");
+assert((/(?<\u{0041}>.)/u).test("a"), "Non-surrogate");
+assert(/(?<a\u{104A4}>.)/u.test("a"), "Surrogate, ID_Continue");
+assert(RegExp("(?<\\u0041>.)", "u").test("a"), "Non-surrogate");
+
+// Backslash is not allowed as ID_Start and ID_Continue
+assert.throws(SyntaxError, () => eval("/(?<\\>.)/u"), "'\' misclassified as ID_Start");
+assert.throws(SyntaxError, () => eval("/(?<a\\>.)/u"), "'\' misclassified as ID_Continue");
diff --git a/test/built-ins/RegExp/named-groups/unicode-references.js b/test/built-ins/RegExp/named-groups/unicode-references.js
new file mode 100644
index 0000000000000000000000000000000000000000..080bda0bebb25cf095102aed0a5b073f2fc04697
--- /dev/null
+++ b/test/built-ins/RegExp/named-groups/unicode-references.js
@@ -0,0 +1,47 @@
+// Copyright 2017 the V8 project authors. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Named backreferences in Unicode RegExps
+esid: sec-atomescape
+info: >
+  The production AtomEscape :: [+N] k GroupName evaluates as follows:
+
+    1. Search the enclosing RegExp for an instance of a GroupSpecifier for an
+       RegExpIdentifierName which has a StringValue equal to the StringValue
+       of the RegExpIdentifierName contained in GroupName.
+    2. Assert: A unique such GroupSpecifier is found.
+    3. Let parenIndex be the number of left capturing parentheses in the entire
+       regular expression that occur to the left of the located GroupSpecifier.
+       This is the total number of times the Atom::(GroupSpecifierDisjunction)
+       production is expanded prior to that production's Term plus the total
+       number of Atom :: (GroupSpecifierDisjunction) productions enclosing this Term.
+    4. Call BackreferenceMatcher(parenIndex) and return its Matcher result.
+features: [regexp-named-groups]
+includes: [compareArray.js]
+---*/
+
+// Named references.
+assert(compareArray(["bab", "b"], "bab".match(/(?<b>.).\k<b>/u)));
+assert.sameValue(null, "baa".match(/(?<b>.).\k<b>/u));
+
+// Reference inside group.
+assert(compareArray(["bab", "b"], "bab".match(/(?<a>\k<a>\w)../u)));
+assert.sameValue("b", "bab".match(/(?<a>\k<a>\w)../u).groups.a);
+
+// Reference before group.
+assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/u)));
+assert.sameValue("b", "bab".match(/\k<a>(?<a>b)\w\k<a>/u).groups.a);
+assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/u)));
+let {a, b} = "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/u).groups;
+assert.sameValue(a, "a");
+assert.sameValue(b, "b");
+
+assert(compareArray(["bab", "b"], "bab".match(/\k<a>(?<a>b)\w\k<a>/)));
+assert(compareArray(["bab", "b", "a"], "bab".match(/(?<b>b)\k<a>(?<a>a)\k<b>/)));
+
+// Reference properties.
+assert.sameValue("a", /(?<a>a)(?<b>b)\k<a>/u.exec("aba").groups.a);
+assert.sameValue("b", /(?<a>a)(?<b>b)\k<a>/u.exec("aba").groups.b);
+assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>/u.exec("aba").groups.c);
+assert.sameValue(undefined, /(?<a>a)(?<b>b)\k<a>|(?<c>c)/u.exec("aba").groups.c);