Bug 1280046 - Search codepoints with same uppercase on ignoreCase match with non-unicode RegExp. r=jwalden
authorTooru Fujisawa <arai_a@mac.com>
Fri, 29 Jul 2016 13:55:25 +0900
changeset 307210 18642055568111041c5b099b66209824b330b74e
parent 307209 97b1b6e04bc92e1efa31b66603fe4f060d161aca
child 307211 f099a449488b2b277dc5488d9f8a37272c6cd792
push id30912
push usercbook@mozilla.com
push dateFri, 29 Jul 2016 10:38:29 +0000
treeherderautoland@4907812dbb41 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalden
bugs1280046
milestone50.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 1280046 - Search codepoints with same uppercase on ignoreCase match with non-unicode RegExp. r=jwalden
js/src/irregexp/RegExpEngine.cpp
js/src/tests/ecma_6/RegExp/ignoreCase-multiple.js
js/src/vm/Unicode.cpp
js/src/vm/Unicode.h
js/src/vm/make_unicode.py
--- a/js/src/irregexp/RegExpEngine.cpp
+++ b/js/src/irregexp/RegExpEngine.cpp
@@ -281,23 +281,16 @@ GetCaseIndependentLetters(char16_t chara
                           const char16_t* choices,
                           size_t choices_length,
                           char16_t* letters)
 {
     size_t count = 0;
     for (size_t i = 0; i < choices_length; i++) {
         char16_t c = choices[i];
 
-        // The standard requires that non-ASCII characters cannot have ASCII
-        // character codes in their equivalence class, even though this
-        // situation occurs multiple times in the unicode tables.
-        static const unsigned kMaxAsciiCharCode = 127;
-        if (!unicode && character > kMaxAsciiCharCode && c <= kMaxAsciiCharCode)
-            continue;
-
         // Skip characters that can't appear in one byte strings.
         if (!unicode && ascii_subject && c > kMaxOneByteCharCode)
             continue;
 
         // Watch for duplicates.
         bool found = false;
         for (size_t j = 0; j < count; j++) {
             if (letters[j] == c) {
@@ -327,20 +320,50 @@ GetCaseIndependentLetters(char16_t chara
             unicode::ReverseFoldCase1(character),
             unicode::ReverseFoldCase2(character),
             unicode::ReverseFoldCase3(character),
         };
         return GetCaseIndependentLetters(character, ascii_subject, unicode,
                                          choices, ArrayLength(choices), letters);
     }
 
+    char16_t upper = unicode::ToUpperCase(character);
+    unicode::CodepointsWithSameUpperCase others(character);
+    char16_t other1 = others.other1();
+    char16_t other2 = others.other2();
+    char16_t other3 = others.other3();
+
+    // ES 2017 draft 996af87b7072b3c3dd2b1def856c66f456102215 21.2.4.2
+    // step 3.g.
+    // The standard requires that non-ASCII characters cannot have ASCII
+    // character codes in their equivalence class, even though this
+    // situation occurs multiple times in the unicode tables.
+    static const unsigned kMaxAsciiCharCode = 127;
+    if (upper <= kMaxAsciiCharCode) {
+        if (character > kMaxAsciiCharCode) {
+            // If Canonicalize(character) == character, all other characters
+            // should be ignored.
+            return GetCaseIndependentLetters(character, ascii_subject, unicode,
+                                             &character, 1, letters);
+        }
+
+        if (other1 > kMaxAsciiCharCode)
+            other1 = character;
+        if (other2 > kMaxAsciiCharCode)
+            other2 = character;
+        if (other3 > kMaxAsciiCharCode)
+            other3 = character;
+    }
+
     const char16_t choices[] = {
         character,
-        unicode::ToLowerCase(character),
-        unicode::ToUpperCase(character)
+        upper,
+        other1,
+        other2,
+        other3
     };
     return GetCaseIndependentLetters(character, ascii_subject, unicode,
                                      choices, ArrayLength(choices), letters);
 }
 
 static char16_t
 ConvertNonLatin1ToLatin1(char16_t c, bool unicode)
 {
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/RegExp/ignoreCase-multiple.js
@@ -0,0 +1,71 @@
+var BUGNUMBER = 1280046;
+var summary = "ignoreCase match should perform Canonicalize both on input and pattern.";
+
+print(BUGNUMBER + ": " + summary);
+
+// Each element [code1, upper, code2] satisfies the following condition:
+//   ToUpperCase(code1) == upper
+//   ToUpperCase(code2) == upper
+var pairs =
+    [
+        // U+00B5: MICRO SIGN
+        // U+039C: GREEK CAPITAL LETTER MU
+        // U+03BC: GREEK SMALL LETTER MU
+        ["\u00B5", "\u039C", "\u03BC"],
+        // U+0345: COMBINING GREEK YPOGEGRAMMENI
+        // U+0399: GREEK CAPITAL LETTER IOTA
+        // U+03B9: GREEK SMALL LETTER IOTA
+        ["\u0345", "\u0399", "\u03B9"],
+        // U+03C2: GREEK SMALL LETTER FINAL SIGMA
+        // U+03A3: GREEK CAPITAL LETTER SIGMA
+        // U+03C3: GREEK SMALL LETTER SIGMA
+        ["\u03C2", "\u03A3", "\u03C3"],
+        // U+03D0: GREEK BETA SYMBOL
+        // U+0392: GREEK CAPITAL LETTER BETA
+        // U+03B2: GREEK SMALL LETTER BETA
+        ["\u03D0", "\u0392", "\u03B2"],
+        // U+03D1: GREEK THETA SYMBOL
+        // U+0398: GREEK CAPITAL LETTER THETA
+        // U+03B8: GREEK SMALL LETTER THETA
+        ["\u03D1", "\u0398", "\u03B8"],
+        // U+03D5: GREEK PHI SYMBOL
+        // U+03A6: GREEK CAPITAL LETTER PHI
+        // U+03C6: GREEK SMALL LETTER PHI
+        ["\u03D5", "\u03A6", "\u03C6"],
+        // U+03D6: GREEK PI SYMBOL
+        // U+03A0: GREEK CAPITAL LETTER PI
+        // U+03C0: GREEK SMALL LETTER PI
+        ["\u03D6", "\u03A0", "\u03C0"],
+        // U+03F0: GREEK KAPPA SYMBOL
+        // U+039A: GREEK CAPITAL LETTER KAPPA
+        // U+03BA: GREEK SMALL LETTER KAPPA
+        ["\u03F0", "\u039A", "\u03BA"],
+        // U+03F1: GREEK RHO SYMBOL
+        // U+03A1: GREEK CAPITAL LETTER RHO
+        // U+03C1: GREEK SMALL LETTER RHO
+        ["\u03F1", "\u03A1", "\u03C1"],
+        // U+03F5: GREEK LUNATE EPSILON SYMBOL
+        // U+0395: GREEK CAPITAL LETTER EPSILON
+        // U+03B5: GREEK SMALL LETTER EPSILON
+        ["\u03F5", "\u0395", "\u03B5"],
+        // U+1E9B: LATIN SMALL LETTER LONG S WITH DOT ABOVE
+        // U+1E60: LATIN CAPITAL LETTER S WITH DOT ABOVE
+        // U+1E61: LATIN SMALL LETTER S WITH DOT ABOVE
+        ["\u1E9B", "\u1E60", "\u1E61"],
+        // U+1FBE: GREEK PROSGEGRAMMENI
+        // U+0399: GREEK CAPITAL LETTER IOTA
+        // U+03B9: GREEK SMALL LETTER IOTA
+        ["\u1FBE", "\u0399", "\u03B9"],
+    ];
+
+for (var [code1, upper, code2] of pairs) {
+    assertEq(new RegExp(code1, "i").test(code2), true);
+    assertEq(new RegExp(code1, "i").test(upper), true);
+    assertEq(new RegExp(upper, "i").test(code1), true);
+    assertEq(new RegExp(upper, "i").test(code2), true);
+    assertEq(new RegExp(code2, "i").test(code1), true);
+    assertEq(new RegExp(code2, "i").test(upper), true);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/Unicode.cpp
+++ b/js/src/vm/Unicode.cpp
@@ -767,16 +767,332 @@ const uint8_t unicode::index2[] = {
       0,   0,   0,   0,   0,   0,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,
       5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,
       5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,   5,
       5,   5,   5,   5,   5,   5,   5,   5,   5,   0,   0,   0,   5,   5,   5,   5,   5,   5,
       0,   0,   5,   5,   5,   5,   5,   5,   0,   0,   5,   5,   5,   5,   5,   5,   0,   0,
       5,   5,   5,   0,   0,   0,
 };
 
+const CodepointsWithSameUpperCaseInfo unicode::js_codepoints_with_same_upper_info[] = {
+    {0, 0, 0},
+    {32, 0, 0},
+    {32, 232, 0},
+    {32, 300, 0},
+    {0, 200, 0},
+    {0, 268, 0},
+    {0, 775, 0},
+    {1, 0, 0},
+    {65336, 0, 0},
+    {65415, 0, 0},
+    {65268, 0, 0},
+    {210, 0, 0},
+    {206, 0, 0},
+    {205, 0, 0},
+    {79, 0, 0},
+    {202, 0, 0},
+    {203, 0, 0},
+    {207, 0, 0},
+    {211, 0, 0},
+    {209, 0, 0},
+    {213, 0, 0},
+    {214, 0, 0},
+    {218, 0, 0},
+    {217, 0, 0},
+    {219, 0, 0},
+    {1, 2, 0},
+    {0, 1, 0},
+    {65535, 0, 0},
+    {65439, 0, 0},
+    {65480, 0, 0},
+    {65406, 0, 0},
+    {10795, 0, 0},
+    {65373, 0, 0},
+    {10792, 0, 0},
+    {65341, 0, 0},
+    {69, 0, 0},
+    {71, 0, 0},
+    {0, 116, 7289},
+    {38, 0, 0},
+    {37, 0, 0},
+    {64, 0, 0},
+    {63, 0, 0},
+    {32, 62, 0},
+    {32, 96, 0},
+    {32, 57, 0},
+    {65452, 32, 7205},
+    {32, 86, 0},
+    {64793, 32, 0},
+    {32, 54, 0},
+    {32, 80, 0},
+    {31, 32, 0},
+    {32, 47, 0},
+    {0, 30, 0},
+    {0, 64, 0},
+    {0, 25, 0},
+    {65420, 0, 7173},
+    {0, 54, 0},
+    {64761, 0, 0},
+    {0, 22, 0},
+    {0, 48, 0},
+    {0, 15, 0},
+    {8, 0, 0},
+    {65506, 0, 0},
+    {65511, 0, 0},
+    {65521, 0, 0},
+    {65514, 0, 0},
+    {65482, 0, 0},
+    {65488, 0, 0},
+    {65472, 0, 0},
+    {65529, 0, 0},
+    {80, 0, 0},
+    {15, 0, 0},
+    {48, 0, 0},
+    {7264, 0, 0},
+    {1, 59, 0},
+    {0, 58, 0},
+    {65478, 0, 0},
+    {65528, 0, 0},
+    {65462, 0, 0},
+    {65527, 0, 0},
+    {58247, 58363, 0},
+    {65450, 0, 0},
+    {65436, 0, 0},
+    {65424, 0, 0},
+    {65408, 0, 0},
+    {65410, 0, 0},
+    {28, 0, 0},
+    {16, 0, 0},
+    {26, 0, 0},
+    {54793, 0, 0},
+    {61722, 0, 0},
+    {54809, 0, 0},
+    {54756, 0, 0},
+    {54787, 0, 0},
+    {54753, 0, 0},
+    {54754, 0, 0},
+    {54721, 0, 0},
+    {30204, 0, 0},
+    {23256, 0, 0},
+    {23228, 0, 0},
+};
+
+const uint8_t unicode::codepoints_with_same_upper_index1[] = {
+      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   0,   0,   0,  10,  11,  12,  13,  14,
+     15,  16,  17,  18,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  19,  20,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  21,  22,  23,  21,  24,  25,
+     26,  27,   0,   0,   0,   0,  28,  29,  30,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,  31,  32,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  33,  34,  21,  35,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  36,
+     37,   0,  38,  39,  40,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  41,   0,   0,   0,
+};
+
+const uint8_t unicode::codepoints_with_same_upper_index2[] = {
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,   1,
+      1,   2,   1,   1,   1,   1,   1,   1,   1,   1,   1,   3,   1,   1,   1,   1,   1,   1,
+      1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   4,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   5,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   6,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,
+      1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   0,
+      1,   1,   1,   1,   1,   1,   1,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   8,
+      7,   0,   7,   0,   7,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,
+      0,   7,   0,   7,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   9,   7,
+      0,   7,   0,   7,   0,  10,   0,  11,   7,   0,   7,   0,  12,   7,   0,  13,  13,   7,
+      0,   0,  14,  15,  16,   7,   0,  13,  17,   0,  18,  19,   7,   0,   0,   0,  18,  20,
+      0,  21,   7,   0,   7,   0,   7,   0,  22,   7,   0,  22,   0,   0,   7,   0,  22,   7,
+      0,  23,  23,   7,   0,   7,   0,  24,   7,   0,   0,   0,   7,   0,   0,   0,   0,   0,
+      0,   0,  25,  26,  27,  25,  26,  27,  25,  26,  27,   7,   0,   7,   0,   7,   0,   7,
+      0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,  25,  26,  27,   7,   0,  28,  29,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,  30,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,  31,   7,   0,  32,  33,   0,
+      0,   7,   0,  34,  35,  36,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  37,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   7,   0,   7,   0,   0,   0,   7,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,  38,   0,  39,  39,  39,   0,  40,   0,  41,  41,
+      0,   1,  42,   1,   1,  43,   1,   1,  44,  45,  46,   1,  47,   1,   1,   1,  48,  49,
+      0,  50,   1,   1,  51,   1,   1,   1,   1,   1,   0,   0,   0,   0,   0,   0,  52,   0,
+      0,  53,   0,   0,  54,  55,  56,   0,  57,   0,   0,   0,  58,  59,  26,  27,   0,   0,
+     60,   0,   0,   0,   0,   0,   0,   0,   0,  61,  62,  63,   0,   0,   0,  64,  65,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,  66,  67,   0,   0,   0,  68,   0,   7,   0,  69,   7,   0,
+      0,  30,  30,  30,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,  70,
+     70,  70,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+      1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,  71,   7,
+      0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,
+     73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,  73,
+     73,  73,  73,  73,  73,  73,  73,  73,   0,  73,   0,   0,   0,   0,   0,  73,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+     74,  75,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      0,   0,   0,   0,   0,  76,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,
+     77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,  77,  77,
+      0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,  77,  77,   0,   0,
+      0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,  77,   0,  77,   0,  77,   0,  77,   0,   0,   0,   0,   0,   0,
+      0,   0,  77,  77,  77,  77,  77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,
+     77,  77,  77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,
+     77,  77,  77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  77,  77,  77,  77,
+     77,  77,   0,   0,   0,   0,   0,   0,   0,   0,  77,  77,  78,  78,  79,   0,  80,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,  81,  81,  81,  81,  79,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  77,  77,  82,  82,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,  77,  77,  83,  83,  69,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,  84,  84,  85,  85,  79,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  86,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,  87,  87,
+     87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,  87,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,
+     88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,  88,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,
+     72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,  72,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,  89,  90,  91,   0,
+      0,   7,   0,   7,   0,   7,   0,  92,  93,  94,  95,   0,   7,   0,   0,   7,   0,   0,
+      0,   0,   0,   0,   0,   0,  96,  96,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,
+      0,   0,   0,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,
+      7,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   7,   0,   7,
+      0,  97,   7,   0,   7,   0,   7,   0,   7,   0,   7,   0,   0,   0,   0,   7,   0,  98,
+      0,   0,   7,   0,   7,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      7,   0,   7,   0,   7,   0,   7,   0,   7,   0,  99,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
+      0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   1,   1,   1,   1,   1,   1,   1,
+      1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,   1,
+      1,   0,   0,   0,   0,   0,
+};
+
 const FoldingInfo unicode::js_foldinfo[] = {
     {0, 0, 0, 0},
     {32, 0, 0, 0},
     {32, 8415, 0, 0},
     {32, 300, 0, 0},
     {0, 65504, 0, 0},
     {0, 65504, 8383, 0},
     {0, 65504, 268, 0},
--- a/js/src/vm/Unicode.h
+++ b/js/src/vm/Unicode.h
@@ -274,16 +274,86 @@ ToLowerCaseNonBMPTrail(char16_t lead, ch
     if (lead == LEAD && trail >= TRAIL_FROM && trail <= TRAIL_TO) \
         return trail + DIFF;
     FOR_EACH_NON_BMP_LOWERCASE(CALC_TRAIL)
 #undef CALL_TRAIL
 
     return trail;
 }
 
+/*
+ * For a codepoint C, CodepointsWithSameUpperCaseInfo stores three offsets
+ * from C to up to three codepoints with same uppercase (no codepoint in
+ * UnicodeData.txt has more than three such codepoints).
+ *
+ * To illustrate, consider the codepoint U+0399 GREEK CAPITAL LETTER IOTA, the
+ * uppercased form of these three codepoints:
+ *
+ *   U+03B9 GREEK SMALL LETTER IOTA
+ *   U+1FBE GREEK PROSGEGRAMMENI
+ *   U+0345 COMBINING GREEK YPOGEGRAMMENI
+ *
+ * For the CodepointsWithSameUpperCaseInfo corresponding to this codepoint,
+ * delta{1,2,3} are 16-bit modular deltas from 0x0399 to each respective
+ * codepoint:
+ *   uint16_t(0x03B9 - 0x0399),
+ *   uint16_t(0x1FBE - 0x0399),
+ *   uint16_t(0x0345 - 0x0399)
+ * in an unimportant order.
+ *
+ * If there are fewer than three other codepoints, some fields are zero.
+ * Consider the codepoint U+03B9 above, the other two codepoints U+1FBE and
+ * U+0345 have same uppercase (U+0399 is not).  For the
+ * CodepointsWithSameUpperCaseInfo corresponding to this codepoint,
+ * delta{1,2,3} are:
+ *   uint16_t(0x1FBE - 0x03B9),
+ *   uint16_t(0x0345 - 0x03B9),
+ *   uint16_t(0)
+ * in an unimportant order.
+ *
+ * Because multiple codepoints map to a single CodepointsWithSameUpperCaseInfo,
+ * a CodepointsWithSameUpperCaseInfo and its delta{1,2,3} have no meaning
+ * standing alone: they have meaning only with respect to a codepoint mapping
+ * to that CodepointsWithSameUpperCaseInfo.
+ */
+class CodepointsWithSameUpperCaseInfo
+{
+  public:
+    uint16_t delta1;
+    uint16_t delta2;
+    uint16_t delta3;
+};
+
+extern const uint8_t codepoints_with_same_upper_index1[];
+extern const uint8_t codepoints_with_same_upper_index2[];
+extern const CodepointsWithSameUpperCaseInfo js_codepoints_with_same_upper_info[];
+
+class CodepointsWithSameUpperCase
+{
+    const CodepointsWithSameUpperCaseInfo& info_;
+    const char16_t code_;
+
+    static const CodepointsWithSameUpperCaseInfo& computeInfo(char16_t code) {
+        const size_t shift = 6;
+        size_t index = codepoints_with_same_upper_index1[code >> shift];
+        index = codepoints_with_same_upper_index2[(index << shift) + (code & ((1 << shift) - 1))];
+        return js_codepoints_with_same_upper_info[index];
+    }
+
+  public:
+    explicit CodepointsWithSameUpperCase(char16_t code)
+      : info_(computeInfo(code)),
+        code_(code)
+    {}
+
+    char16_t other1() const { return uint16_t(code_) + info_.delta1; }
+    char16_t other2() const { return uint16_t(code_) + info_.delta2; }
+    char16_t other3() const { return uint16_t(code_) + info_.delta3; }
+};
+
 class FoldingInfo {
   public:
     uint16_t folding;
     uint16_t reverse1;
     uint16_t reverse2;
     uint16_t reverse3;
 };
 
--- a/js/src/vm/make_unicode.py
+++ b/js/src/vm/make_unicode.py
@@ -140,16 +140,21 @@ def make_non_bmp_convert_macro(out_file,
 def generate_unicode_stuff(unicode_data, case_folding,
                            data_file, non_bmp_file,
                            test_mapping, test_non_bmp_mapping,
                            test_space, test_icase):
     dummy = (0, 0, 0)
     table = [dummy]
     cache = {dummy: 0}
     index = [0] * (MAX + 1)
+    same_upper_map = {}
+    same_upper_dummy = (0, 0, 0)
+    same_upper_table = [same_upper_dummy]
+    same_upper_cache = {same_upper_dummy: 0}
+    same_upper_index = [0] * (MAX + 1)
     folding_map = {}
     rev_folding_map = {}
     folding_dummy = (0, 0, 0, 0)
     folding_table = [folding_dummy]
     folding_cache = {folding_dummy: 0}
     folding_index = [0] * (MAX + 1)
     test_table = {}
     test_space_table = []
@@ -167,16 +172,21 @@ def generate_unicode_stuff(unicode_data,
         category = row[2]
         alias = row[-5]
         uppercase = row[-3]
         lowercase = row[-2]
         flags = 0
 
         if uppercase:
             upper = int(uppercase, 16)
+
+            if upper not in same_upper_map:
+                same_upper_map[upper] = [code]
+            else:
+                same_upper_map[upper].append(code)
         else:
             upper = code
 
         if lowercase:
             lower = int(lowercase, 16)
         else:
             lower = code
 
@@ -211,16 +221,46 @@ def generate_unicode_stuff(unicode_data,
 
         i = cache.get(item)
         if i is None:
             assert item not in table
             cache[item] = i = len(table)
             table.append(item)
         index[code] = i
 
+    for code in range(0, MAX + 1):
+        entry = test_table.get(code)
+
+        if not entry:
+            continue
+
+        (upper, lower, name, alias) = entry
+
+        if upper not in same_upper_map:
+            continue
+
+        same_upper_ds = [v - code for v in same_upper_map[upper]]
+
+        assert len(same_upper_ds) <= 3
+        assert all([v > -65535 and v < 65535 for v in same_upper_ds])
+
+        same_upper = [v & 0xffff for v in same_upper_ds]
+        same_upper_0 = same_upper[0] if len(same_upper) >= 1 else 0
+        same_upper_1 = same_upper[1] if len(same_upper) >= 2 else 0
+        same_upper_2 = same_upper[2] if len(same_upper) >= 3 else 0
+
+        item = (same_upper_0, same_upper_1, same_upper_2)
+
+        i = same_upper_cache.get(item)
+        if i is None:
+            assert item not in same_upper_table
+            same_upper_cache[item] = i = len(same_upper_table)
+            same_upper_table.append(item)
+        same_upper_index[code] = i
+
     for row in read_case_folding(case_folding):
         code = row[0]
         mapping = row[2]
         folding_map[code] = mapping
 
         if code > MAX:
             non_bmp_folding_map[code] = mapping
             non_bmp_rev_folding_map[mapping] = code
@@ -305,17 +345,17 @@ def generate_unicode_stuff(unicode_data,
 
     test_mapping.write('/* Generated by make_unicode.py DO NOT MODIFY */\n')
     test_mapping.write(public_domain)
     test_mapping.write('var mapping = [\n')
     for code in range(0, MAX + 1):
         entry = test_table.get(code)
 
         if entry:
-            upper, lower, name, alias = entry
+            (upper, lower, name, alias) = entry
             test_mapping.write('  [' + hex(upper) + ', ' + hex(lower) + '], /* ' +
                        name + (' (' + alias + ')' if alias else '') + ' */\n')
         else:
             test_mapping.write('  [' + hex(code) + ', ' + hex(code) + '],\n')
     test_mapping.write('];')
     test_mapping.write("""
 assertEq(mapping.length, 0x10000);
 for (var i = 0; i <= 0xffff; i++) {
@@ -384,31 +424,45 @@ if (typeof reportCompare === "function")
     reportCompare(true, true);
 """)
 
     index1, index2, shift = splitbins(index)
 
     # Don't forget to update CharInfo in Unicode.cpp if you need to change this
     assert shift == 5
 
+    same_upper_index1, same_upper_index2, same_upper_shift = splitbins(same_upper_index)
+
+    # Don't forget to update CharInfo in Unicode.cpp if you need to change this
+    assert same_upper_shift == 6
+
     folding_index1, folding_index2, folding_shift = splitbins(folding_index)
 
     # Don't forget to update CharInfo in Unicode.cpp if you need to change this
     assert folding_shift == 6
 
     # verify correctness
     for char in index:
         test = table[index[char]]
 
         idx = index1[char >> shift]
         idx = index2[(idx << shift) + (char & ((1 << shift) - 1))]
 
         assert test == table[idx]
 
     # verify correctness
+    for char in same_upper_index:
+        test = same_upper_table[same_upper_index[char]]
+
+        idx = same_upper_index1[char >> same_upper_shift]
+        idx = same_upper_index2[(idx << same_upper_shift) + (char & ((1 << same_upper_shift) - 1))]
+
+        assert test == same_upper_table[idx]
+
+    # verify correctness
     for char in folding_index:
         test = folding_table[folding_index[char]]
 
         idx = folding_index1[char >> folding_shift]
         idx = folding_index2[(idx << folding_shift) + (char & ((1 << folding_shift) - 1))]
 
         assert test == folding_table[idx]
 
@@ -492,16 +546,29 @@ if (typeof reportCompare === "function")
         file.write('\n'.join(lines))
         file.write('\n};\n')
 
     dump(index1, 'index1', data_file)
     data_file.write('\n')
     dump(index2, 'index2', data_file)
     data_file.write('\n')
 
+    data_file.write('const CodepointsWithSameUpperCaseInfo unicode::js_codepoints_with_same_upper_info[] = {\n')
+    for d in same_upper_table:
+        data_file.write('    {')
+        data_file.write(', '.join((str(e) for e in d)))
+        data_file.write('},\n')
+    data_file.write('};\n')
+    data_file.write('\n')
+
+    dump(same_upper_index1, 'codepoints_with_same_upper_index1', data_file)
+    data_file.write('\n')
+    dump(same_upper_index2, 'codepoints_with_same_upper_index2', data_file)
+    data_file.write('\n')
+
     data_file.write('const FoldingInfo unicode::js_foldinfo[] = {\n')
     for d in folding_table:
         data_file.write('    {')
         data_file.write(', '.join((str(e) for e in d)))
         data_file.write('},\n')
     data_file.write('};\n')
     data_file.write('\n')