Bug 813836: Optimize comparisons of single-element strings. r=jandem
authorAndré Bargull <andre.bargull@gmail.com>
Fri, 10 Aug 2018 08:56:47 -0700
changeset 486547 78d5bc33afd08d7fa8acedcdf074de4ddc6f0bf0
parent 486546 5589d9ec31aabd93c3b1b20f2517b96ec78ef752
child 486548 823dbd89637cf9c0847de7957932220bcc6905a0
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs813836
milestone63.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 813836: Optimize comparisons of single-element strings. r=jandem
js/public/TrackedOptimizationInfo.h
js/src/jit-test/tests/ion/compare-char.js
js/src/jit/IonBuilder.cpp
js/src/jit/IonBuilder.h
--- a/js/public/TrackedOptimizationInfo.h
+++ b/js/public/TrackedOptimizationInfo.h
@@ -68,16 +68,17 @@ namespace JS {
     _(NewObject_SharedCache)                            \
     _(NewObject_Call)                                   \
                                                         \
     _(Compare_SpecializedTypes)                         \
     _(Compare_Bitwise)                                  \
     _(Compare_SpecializedOnBaselineTypes)               \
     _(Compare_SharedCache)                              \
     _(Compare_Call)                                     \
+    _(Compare_Character)                                \
                                                         \
     _(Call_Inline)
 
 
 // Ordering is important below. All outcomes before GenericSuccess will be
 // considered failures, and all outcomes after GenericSuccess will be
 // considered successes.
 #define TRACKED_OUTCOME_LIST(_)                                         \
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/compare-char.js
@@ -0,0 +1,147 @@
+function IsASCIIAlphaString_CharCodeAt(s) {
+    for (var i = 0; i < s.length; i++) {
+        var c = s.charCodeAt(i);
+        if (!((0x41 <= c && c <= 0x5A) || (0x61 <= c && c <= 0x7A)))
+            return false;
+    }
+    return true;
+}
+
+function IsASCIIAlphaString_CharAt(s) {
+    for (var i = 0; i < s.length; i++) {
+        var c = s.charAt(i);
+        if (!(("A" <= c && c <= "Z") || ("a" <= c && c <= "z")))
+            return false;
+    }
+    return true;
+}
+
+function IsASCIIAlphaString_GetElem(s) {
+    for (var i = 0; i < s.length; i++) {
+        var c = s[i];
+        if (!(("A" <= c && c <= "Z") || ("a" <= c && c <= "z")))
+            return false;
+    }
+    return true;
+}
+
+function IsGreekOrCyrillicString_CharCodeAt(s) {
+    // U+0370 (GREEK CAPITAL LETTER HETA)
+    // U+03FF (GREEK CAPITAL REVERSED DOTTED LUNATE SIGMA SYMBOL)
+    // U+0400 (CYRILLIC CAPITAL LETTER IE WITH GRAVE)
+    // U+052F (CYRILLIC SMALL LETTER EL WITH DESCENDER)
+    for (var i = 0; i < s.length; i++) {
+        var c = s.charCodeAt(i);
+        if (!((0x0370 <= c && c <= 0x03FF) || (0x400 <= c && c <= 0x052F)))
+            return false;
+    }
+    return true;
+}
+
+function IsGreekOrCyrillicString_CharAt(s) {
+    for (var i = 0; i < s.length; i++) {
+        var c = s.charAt(i);
+        if (!(("Ͱ" <= c && c <= "Ͽ") || ("Ѐ" <= c && c <= "ԯ")))
+            return false;
+    }
+    return true;
+}
+
+function IsGreekOrCyrillicString_GetElem(s) {
+    for (var i = 0; i < s.length; i++) {
+        var c = s[i];
+        if (!(("Ͱ" <= c && c <= "Ͽ") || ("Ѐ" <= c && c <= "ԯ")))
+            return false;
+    }
+    return true;
+}
+
+function main() {
+    function compareLatin1() {
+        var strings = ["ABCABCABC", "abcabcabc"];
+        var q = 0;
+        for (var i = 0; i < 200; ++i) {
+            var str = strings[i & 1];
+            for (var j = 0; j < str.length; ++j) {
+                if (str[j] === "a")
+                    q++;
+                if ("A" == str[j])
+                    q++;
+                if (str[j] != "b")
+                    q++;
+                if ("D" !== str[j])
+                    q++;
+            }
+        }
+        assertEq(q, 100*3 + 100*3 + 100*15 + 100*18);
+    }
+    function compareTwoByte() {
+        var strings = ["āĉœāĉœāĉœ", "abcabcabc"];
+        var q = 0;
+        for (var i = 0; i < 200; ++i) {
+            var str = strings[i & 1];
+            for (var j = 0; j < str.length; ++j) {
+                if ("œ" === str[j])
+                    q++;
+                if (str[j] == "ĉ")
+                    q++;
+                if ("ā" != str[j])
+                    q++;
+                if (str[j] !== "Ɖ")
+                    q++;
+            }
+        }
+        assertEq(q, 100*3 + 100*3 + 100*15 + 100*18);
+    }
+    function compareRangeLatin1() {
+        var strings = [
+            "ABCABCABC", // all upper
+            "abcabcabc", // all lower
+            "abcABCabc", // lower and upper
+            "abcabc123", // characters below limit
+            "abc[_]ABC", // characters between limit
+            "ABC{|}abc", // characters above limit
+            "!#$456_~ÿ", // no matches at all
+            "aBcZyyZUU", // -
+        ];
+        for (var i = 0; i < 200; ++i) {
+            var str = strings[i & 7];
+            var resultCharCodeAt = IsASCIIAlphaString_CharCodeAt(str);
+            var resultCharAt = IsASCIIAlphaString_CharAt(str);
+            var resultGetElem = IsASCIIAlphaString_GetElem(str);
+
+            assertEq(resultCharAt, resultCharCodeAt);
+            assertEq(resultGetElem, resultCharCodeAt);
+        }
+    }
+    function compareRangeTwoByte() {
+        var strings = [
+            "αβγΑΒΓαβγ", // all Greek
+            "АБВабвАБВ", // all Cyrillic
+            "αβγабвАБΓ", // Greek and Cyrillic
+            "αβγāēōАБВ", // characters below limit
+            "αβγԱԲԳАБВ", // characters above limit
+            "abcāēōԱԲԳ", // no matches at all
+            "𝐀𝐁𝐂𝐀𝐁𝐂𝐀𝐁𝐂", // (non-BMP)
+            "abcabcabc", // -
+        ];
+        for (var i = 0; i < 200; ++i) {
+            var str = strings[i & 7];
+            var resultCharCodeAt = IsGreekOrCyrillicString_CharCodeAt(str);
+            var resultCharAt = IsGreekOrCyrillicString_CharAt(str);
+            var resultGetElem = IsGreekOrCyrillicString_GetElem(str);
+
+            assertEq(resultCharAt, resultCharCodeAt);
+            assertEq(resultGetElem, resultCharCodeAt);
+        }
+    }
+
+    compareLatin1();
+    compareTwoByte();
+    compareRangeLatin1();
+    compareRangeTwoByte();
+}
+
+for (var i = 0; i < 15; ++i) {
+    main();
+}
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -5820,16 +5820,19 @@ IonBuilder::jsop_compare(JSOp op, MDefin
     // tracking when that happens.
     bool canTrackOptimization = !IsCallPC(pc);
 
     bool emitted = false;
     if (canTrackOptimization)
         startTrackingOptimizations();
 
     if (!forceInlineCaches()) {
+        MOZ_TRY(compareTryCharacter(&emitted, op, left, right));
+        if (emitted)
+            return Ok();
         MOZ_TRY(compareTrySpecialized(&emitted, op, left, right));
         if (emitted)
             return Ok();
         MOZ_TRY(compareTryBitwise(&emitted, op, left, right));
         if (emitted)
             return Ok();
         MOZ_TRY(compareTrySpecializedOnBaselineInspector(&emitted, op, left, right));
         if (emitted)
@@ -5852,16 +5855,80 @@ IonBuilder::jsop_compare(JSOp op, MDefin
     if (ins->isEffectful())
         MOZ_TRY(resumeAfter(ins));
 
     if (canTrackOptimization)
         trackOptimizationSuccess();
     return Ok();
 }
 
+AbortReasonOr<Ok>
+IonBuilder::compareTryCharacter(bool* emitted, JSOp op, MDefinition* left, MDefinition* right)
+{
+    MOZ_ASSERT(*emitted == false);
+
+    // TODO: Support tracking optimizations for inlining a call and regular
+    // optimization tracking at the same time. Currently just drop optimization
+    // tracking when that happens.
+    bool canTrackOptimization = !IsCallPC(pc);
+    if (canTrackOptimization)
+        trackOptimizationAttempt(TrackedStrategy::Compare_Character);
+
+    // Try to optimize |MConstant(string) <compare> (MFromCharCode MCharCodeAt)|
+    // as |MConstant(charcode) <compare> MCharCodeAt|.
+
+    MConstant* constant;
+    MDefinition* operand;
+    if (left->isConstant()) {
+        constant = left->toConstant();
+        operand = right;
+    } else if (right->isConstant()) {
+        constant = right->toConstant();
+        operand = left;
+    } else {
+        return Ok();
+    }
+
+    if (constant->type() != MIRType::String || constant->toString()->length() != 1)
+        return Ok();
+
+    if (!operand->isFromCharCode() || !operand->toFromCharCode()->input()->isCharCodeAt())
+        return Ok();
+
+    char16_t charCode = constant->toString()->asAtom().latin1OrTwoByteChar(0);
+    constant->setImplicitlyUsedUnchecked();
+
+    MConstant* charCodeConst = MConstant::New(alloc(), Int32Value(charCode));
+    current->add(charCodeConst);
+
+    MDefinition* charCodeAt = operand->toFromCharCode()->input();
+    operand->setImplicitlyUsedUnchecked();
+
+    if (left == constant) {
+        left = charCodeConst;
+        right = charCodeAt;
+    } else {
+        left = charCodeAt;
+        right = charCodeConst;
+    }
+
+    MCompare* ins = MCompare::New(alloc(), left, right, op);
+    ins->setCompareType(MCompare::Compare_Int32);
+    ins->cacheOperandMightEmulateUndefined(constraints());
+
+    current->add(ins);
+    current->push(ins);
+
+    MOZ_ASSERT(!ins->isEffectful());
+    if (canTrackOptimization)
+        trackOptimizationSuccess();
+    *emitted = true;
+    return Ok();
+}
+
 static bool
 ObjectOrSimplePrimitive(MDefinition* op)
 {
     // Return true if op is either undefined/null/boolean/int32/symbol or an object.
     return !op->mightBeType(MIRType::String)
         && !op->mightBeType(MIRType::Double)
         && !op->mightBeType(MIRType::Float32)
         && !op->mightBeType(MIRType::MagicOptimizedArguments)
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -328,16 +328,18 @@ class IonBuilder
     AbortReasonOr<Ok> compareTrySpecialized(bool* emitted, JSOp op, MDefinition* left,
                                             MDefinition* right);
     AbortReasonOr<Ok> compareTryBitwise(bool* emitted, JSOp op, MDefinition* left,
                                         MDefinition* right);
     AbortReasonOr<Ok> compareTrySpecializedOnBaselineInspector(bool* emitted, JSOp op,
                                                                MDefinition* left,
                                                                MDefinition* right);
     AbortReasonOr<Ok> compareTryBinaryStub(bool* emitted, MDefinition* left, MDefinition* right);
+    AbortReasonOr<Ok> compareTryCharacter(bool* emitted, JSOp op, MDefinition* left,
+                                          MDefinition* right);
 
     // jsop_newarray helpers.
     AbortReasonOr<Ok> newArrayTryTemplateObject(bool* emitted, JSObject* templateObject,
                                                 uint32_t length);
     AbortReasonOr<Ok> newArrayTryVM(bool* emitted, JSObject* templateObject, uint32_t length);
 
     // jsop_newobject helpers.
     AbortReasonOr<Ok> newObjectTrySharedStub(bool* emitted);