Title: [280452] trunk
Revision
280452
Author
ysuz...@apple.com
Date
2021-07-29 15:26:13 -0700 (Thu, 29 Jul 2021)

Log Message

[JSC] Yarr should perform BoyerMoore search
https://bugs.webkit.org/show_bug.cgi?id=228301

Reviewed by Saam Barati.

JSTests:

* microbenchmarks/jquery-todomvc-regexp.js:
* stress/regexp--bm-search-long-character.js: Added.
(shouldBe):
* stress/regexp--bm-search-long-map.js: Added.
(shouldBe):
* stress/regexp-bitvector-reuse.js: Added.
(shouldBe):
* stress/regexp-non-ascii-bm-search-character.js: Added.
(shouldBe):
* stress/regexp-non-ascii-bm-search-map.js: Added.
(shouldBe):

Source/_javascript_Core:

This patch emits skipping fast-path at the beginning of body alternatives with a large stride. So we can quickly discard unrelated characters
and attempt to find possibly related sequence in the long sequence. The method is derived from V8's implementation (with some extensions).

If we have a searching pattern /abcdef/, then we can check the 6th character against a set of {a, b, c, d, e, f}.
If it does not match, we can shift 6 characters. We use this strategy since this way can be extended easily to support
disjunction, character-class, and ignore-cases. For example, in the case of /(?:abc|def)/, we can check 3rd character
against {a, b, c, d, e, f} and shift 3 characters if it does not match.

Then, the best way to perform the above shifting is that finding the longest character sequence which does not have
many candidates. In the case of /[a-z]aaaaaaa[a-z]/, we can extract "aaaaaaa" sequence and check 8th character against {a}.
If it does not match, then we can shift 7 characters (length of "aaaaaaa"). This shifting is better than using "[a-z]aaaaaaa[a-z]"
sequence and {a-z} set since {a-z} set will almost always match.

We first collect possible characters for each character position. Then, apply heuristics to extract good character sequence from
that and construct fast searching with long stride.

Microbenchmark which performs RegExp ops in Speedometer2/jQuery-TodoMVC shows 25% improvement.

                                      ToT                     Patched

    jquery-todomvc-regexp      723.9739+-1.3997     ^    579.1698+-1.2505        ^ definitely 1.2500x faster

This improves Speedometer2/jQuery-TodoMVC by 3%.

    ----------------------------------------------------------------------------------------------------------------------------------
    |               subtest                |     ms      |     ms      |  b / a   | pValue (significance using False Discovery Rate) |
    ----------------------------------------------------------------------------------------------------------------------------------
    | Elm-TodoMVC                          |123.365625   |123.456250   |1.000735  | 0.804077                                         |
    | VueJS-TodoMVC                        |26.912500    |26.925000    |1.000464  | 0.969603                                         |
    | EmberJS-TodoMVC                      |127.540625   |127.562500   |1.000172  | 0.960474                                         |
    | BackboneJS-TodoMVC                   |50.606250    |50.518750    |0.998271  | 0.670313                                         |
    | Preact-TodoMVC                       |21.018750    |20.850000    |0.991971  | 0.563818                                         |
    | AngularJS-TodoMVC                    |136.943750   |137.271875   |1.002396  | 0.531513                                         |
    | Vanilla-ES2015-TodoMVC               |68.521875    |68.593750    |1.001049  | 0.701376                                         |
    | Inferno-TodoMVC                      |65.559375    |65.803125    |1.003718  | 0.414418                                         |
    | Flight-TodoMVC                       |77.284375    |76.715625    |0.992641  | 0.219870                                         |
    | Angular2-TypeScript-TodoMVC          |40.725000    |40.318750    |0.990025  | 0.281212                                         |
    | VanillaJS-TodoMVC                    |55.209375    |54.715625    |0.991057  | 0.056921                                         |
    | jQuery-TodoMVC                       |266.396875   |258.471875   |0.970251  | 0.000000 (significant)                           |
    | EmberJS-Debug-TodoMVC                |341.550000   |341.856250   |1.000897  | 0.618140                                         |
    | React-TodoMVC                        |88.731250    |88.871875    |1.001585  | 0.512407                                         |
    | React-Redux-TodoMVC                  |150.340625   |150.065625   |0.998171  | 0.412940                                         |
    | Vanilla-ES2015-Babel-Webpack-TodoMVC |65.390625    |65.362500    |0.999570  | 0.834760                                         |
    ----------------------------------------------------------------------------------------------------------------------------------
    a mean = 245.96997
    b mean = 246.86366
    pValue = 0.0061448402
    (Bigger means are better.)
    1.004 times better
    Results ARE significant

* runtime/OptionsList.h:
* yarr/YarrJIT.cpp:
(JSC::Yarr::BoyerMooreInfo::BoyerMooreInfo):
(JSC::Yarr::BoyerMooreInfo::length const):
(JSC::Yarr::BoyerMooreInfo::set):
(JSC::Yarr::BoyerMooreInfo::index const):
(JSC::Yarr::BoyerMooreInfo::setIndex):
(JSC::Yarr::BoyerMooreInfo::create):
(JSC::Yarr::BoyerMooreInfo::findBestCharacterSequence const):
(JSC::Yarr::BoyerMooreInfo::findWorthwhileCharacterSequenceForLookahead const):
(JSC::Yarr::BoyerMooreInfo::createCandidateBitmap const):
* yarr/YarrJIT.h:
(JSC::Yarr::BoyerMooreBitmap::count const):
(JSC::Yarr::BoyerMooreBitmap::map const):
(JSC::Yarr::BoyerMooreBitmap::isMaskEffective const):
(JSC::Yarr::BoyerMooreBitmap::add):
(JSC::Yarr::BoyerMooreByteVector::BoyerMooreByteVector):
(JSC::Yarr::YarrCodeBlock::set8BitCode):
(JSC::Yarr::YarrCodeBlock::set16BitCode):
(JSC::Yarr::YarrCodeBlock::set8BitCodeMatchOnly):
(JSC::Yarr::YarrCodeBlock::set16BitCodeMatchOnly):
(JSC::Yarr::YarrCodeBlock::clear):
(JSC::Yarr::YarrCodeBlock::findSameVector const):

Source/WTF:

* wtf/BitVector.cpp:
(WTF::BitVector::dump const):
* wtf/Bitmap.h:
(WTF::WordType>::dump const):
* wtf/UniqueRef.h:
(WTF::makeUniqueRefFromNonNullUniquePtr):
(WTF::UniqueRef::UniqueRef):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (280451 => 280452)


--- trunk/JSTests/ChangeLog	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/JSTests/ChangeLog	2021-07-29 22:26:13 UTC (rev 280452)
@@ -1,3 +1,22 @@
+2021-07-28  Yusuke Suzuki  <ysuz...@apple.com>
+
+        [JSC] Yarr should perform BoyerMoore search
+        https://bugs.webkit.org/show_bug.cgi?id=228301
+
+        Reviewed by Saam Barati.
+
+        * microbenchmarks/jquery-todomvc-regexp.js:
+        * stress/regexp--bm-search-long-character.js: Added.
+        (shouldBe):
+        * stress/regexp--bm-search-long-map.js: Added.
+        (shouldBe):
+        * stress/regexp-bitvector-reuse.js: Added.
+        (shouldBe):
+        * stress/regexp-non-ascii-bm-search-character.js: Added.
+        (shouldBe):
+        * stress/regexp-non-ascii-bm-search-map.js: Added.
+        (shouldBe):
+
 2021-07-25  Alexey Shvayka  <shvaikal...@gmail.com>
 
         Partly implement Function.prototype.{caller,arguments} reflection proposal

Modified: trunk/JSTests/microbenchmarks/jquery-todomvc-regexp.js (280451 => 280452)


--- trunk/JSTests/microbenchmarks/jquery-todomvc-regexp.js	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/JSTests/microbenchmarks/jquery-todomvc-regexp.js	2021-07-29 22:26:13 UTC (rev 280452)
@@ -84209,7 +84209,8 @@
     regExp$16.lastIndex = 0;
     results = regExp$16.exec(string$215);
 }
-noInline(test);
+if (typeof noInline !== "undefined")
+    noInline(test);
 regExps.push(/avoid constant folding/gi);
 strings.push(/avoid constant folding/gi);
 for (let i = 0; i < 20; ++i)

Added: trunk/JSTests/stress/regexp--bm-search-long-character.js (0 => 280452)


--- trunk/JSTests/stress/regexp--bm-search-long-character.js	                        (rev 0)
+++ trunk/JSTests/stress/regexp--bm-search-long-character.js	2021-07-29 22:26:13 UTC (rev 280452)
@@ -0,0 +1,14 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+var regexp = /aaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbb/i;
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(regexp.test("abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbccccddddaaaabbbbccccdddaaaabbbbccccddddfffff"), false);
+    shouldBe(regexp.test("abcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaabbabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbccccddddaaaabbbbccccdddaaaabbbbccccddddaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbfffff"), true);
+    shouldBe(RegExp.leftContext, "abcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaaaabbbbaabbabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbccccddddaaaabbbbccccdddaaaabbbbccccdddd");
+    shouldBe(RegExp.rightContext, "fffff");
+    shouldBe(regexp.test("abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbccccddddaaaabbbbccccddfffffffffffffffffffffff"), false);
+}

Added: trunk/JSTests/stress/regexp--bm-search-long-map.js (0 => 280452)


--- trunk/JSTests/stress/regexp--bm-search-long-map.js	                        (rev 0)
+++ trunk/JSTests/stress/regexp--bm-search-long-map.js	2021-07-29 22:26:13 UTC (rev 280452)
@@ -0,0 +1,13 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+var regexp = /aaaabbbbccccddddaaaabbbbccccdddaaaabbbbccccdddd/i;
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(regexp.test("abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbccccddddaaaabbbbccccdddaaaabbbbccccddddfffff"), true);
+    shouldBe(RegExp.leftContext, "abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefg");
+    shouldBe(RegExp.rightContext, "fffff");
+    shouldBe(regexp.test("abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgaaaabbbbccccddddaaaabbbbccccddfffffffffffffffffffffff"), false);
+}

Added: trunk/JSTests/stress/regexp-bitvector-reuse.js (0 => 280452)


--- trunk/JSTests/stress/regexp-bitvector-reuse.js	                        (rev 0)
+++ trunk/JSTests/stress/regexp-bitvector-reuse.js	2021-07-29 22:26:13 UTC (rev 280452)
@@ -0,0 +1,11 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+var regexp = /script/;
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(/script/.test("testtestscript"), true);
+    shouldBe(RegExp.leftContext, "testtest");
+    shouldBe(/script/.test($vm.make16BitStringIfPossible("testtestscript")), true);
+    shouldBe(RegExp.leftContext, "testtest");
+}

Added: trunk/JSTests/stress/regexp-non-ascii-bm-search-character.js (0 => 280452)


--- trunk/JSTests/stress/regexp-non-ascii-bm-search-character.js	                        (rev 0)
+++ trunk/JSTests/stress/regexp-non-ascii-bm-search-character.js	2021-07-29 22:26:13 UTC (rev 280452)
@@ -0,0 +1,13 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+var regexp = /今日/i;
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(regexp.test("昨日遅くに寝たので今日はまだ眠い。"), true);
+    shouldBe(RegExp.leftContext, "昨日遅くに寝たので");
+    shouldBe(RegExp.rightContext, "はまだ眠い。");
+    shouldBe(regexp.test("Because I went to bed late last night, I'm still sleepy"), false);
+}

Added: trunk/JSTests/stress/regexp-non-ascii-bm-search-map.js (0 => 280452)


--- trunk/JSTests/stress/regexp-non-ascii-bm-search-map.js	                        (rev 0)
+++ trunk/JSTests/stress/regexp-non-ascii-bm-search-map.js	2021-07-29 22:26:13 UTC (rev 280452)
@@ -0,0 +1,13 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+var regexp = /今日|明日/i;
+
+for (var i = 0; i < 1e2; ++i) {
+    shouldBe(regexp.test("昨日遅くに寝たので今日はまだ眠い。"), true);
+    shouldBe(RegExp.leftContext, "昨日遅くに寝たので");
+    shouldBe(RegExp.rightContext, "はまだ眠い。");
+    shouldBe(regexp.test("Because I went to bed late last night, I'm still sleepy"), false);
+}

Modified: trunk/Source/_javascript_Core/ChangeLog (280451 => 280452)


--- trunk/Source/_javascript_Core/ChangeLog	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/_javascript_Core/ChangeLog	2021-07-29 22:26:13 UTC (rev 280452)
@@ -1,5 +1,87 @@
 2021-07-28  Yusuke Suzuki  <ysuz...@apple.com>
 
+        [JSC] Yarr should perform BoyerMoore search
+        https://bugs.webkit.org/show_bug.cgi?id=228301
+
+        Reviewed by Saam Barati.
+
+        This patch emits skipping fast-path at the beginning of body alternatives with a large stride. So we can quickly discard unrelated characters
+        and attempt to find possibly related sequence in the long sequence. The method is derived from V8's implementation (with some extensions).
+
+        If we have a searching pattern /abcdef/, then we can check the 6th character against a set of {a, b, c, d, e, f}.
+        If it does not match, we can shift 6 characters. We use this strategy since this way can be extended easily to support
+        disjunction, character-class, and ignore-cases. For example, in the case of /(?:abc|def)/, we can check 3rd character
+        against {a, b, c, d, e, f} and shift 3 characters if it does not match.
+
+        Then, the best way to perform the above shifting is that finding the longest character sequence which does not have
+        many candidates. In the case of /[a-z]aaaaaaa[a-z]/, we can extract "aaaaaaa" sequence and check 8th character against {a}.
+        If it does not match, then we can shift 7 characters (length of "aaaaaaa"). This shifting is better than using "[a-z]aaaaaaa[a-z]"
+        sequence and {a-z} set since {a-z} set will almost always match.
+
+        We first collect possible characters for each character position. Then, apply heuristics to extract good character sequence from
+        that and construct fast searching with long stride.
+
+        Microbenchmark which performs RegExp ops in Speedometer2/jQuery-TodoMVC shows 25% improvement.
+
+                                              ToT                     Patched
+
+            jquery-todomvc-regexp      723.9739+-1.3997     ^    579.1698+-1.2505        ^ definitely 1.2500x faster
+
+        This improves Speedometer2/jQuery-TodoMVC by 3%.
+
+            ----------------------------------------------------------------------------------------------------------------------------------
+            |               subtest                |     ms      |     ms      |  b / a   | pValue (significance using False Discovery Rate) |
+            ----------------------------------------------------------------------------------------------------------------------------------
+            | Elm-TodoMVC                          |123.365625   |123.456250   |1.000735  | 0.804077                                         |
+            | VueJS-TodoMVC                        |26.912500    |26.925000    |1.000464  | 0.969603                                         |
+            | EmberJS-TodoMVC                      |127.540625   |127.562500   |1.000172  | 0.960474                                         |
+            | BackboneJS-TodoMVC                   |50.606250    |50.518750    |0.998271  | 0.670313                                         |
+            | Preact-TodoMVC                       |21.018750    |20.850000    |0.991971  | 0.563818                                         |
+            | AngularJS-TodoMVC                    |136.943750   |137.271875   |1.002396  | 0.531513                                         |
+            | Vanilla-ES2015-TodoMVC               |68.521875    |68.593750    |1.001049  | 0.701376                                         |
+            | Inferno-TodoMVC                      |65.559375    |65.803125    |1.003718  | 0.414418                                         |
+            | Flight-TodoMVC                       |77.284375    |76.715625    |0.992641  | 0.219870                                         |
+            | Angular2-TypeScript-TodoMVC          |40.725000    |40.318750    |0.990025  | 0.281212                                         |
+            | VanillaJS-TodoMVC                    |55.209375    |54.715625    |0.991057  | 0.056921                                         |
+            | jQuery-TodoMVC                       |266.396875   |258.471875   |0.970251  | 0.000000 (significant)                           |
+            | EmberJS-Debug-TodoMVC                |341.550000   |341.856250   |1.000897  | 0.618140                                         |
+            | React-TodoMVC                        |88.731250    |88.871875    |1.001585  | 0.512407                                         |
+            | React-Redux-TodoMVC                  |150.340625   |150.065625   |0.998171  | 0.412940                                         |
+            | Vanilla-ES2015-Babel-Webpack-TodoMVC |65.390625    |65.362500    |0.999570  | 0.834760                                         |
+            ----------------------------------------------------------------------------------------------------------------------------------
+            a mean = 245.96997
+            b mean = 246.86366
+            pValue = 0.0061448402
+            (Bigger means are better.)
+            1.004 times better
+            Results ARE significant
+
+        * runtime/OptionsList.h:
+        * yarr/YarrJIT.cpp:
+        (JSC::Yarr::BoyerMooreInfo::BoyerMooreInfo):
+        (JSC::Yarr::BoyerMooreInfo::length const):
+        (JSC::Yarr::BoyerMooreInfo::set):
+        (JSC::Yarr::BoyerMooreInfo::index const):
+        (JSC::Yarr::BoyerMooreInfo::setIndex):
+        (JSC::Yarr::BoyerMooreInfo::create):
+        (JSC::Yarr::BoyerMooreInfo::findBestCharacterSequence const):
+        (JSC::Yarr::BoyerMooreInfo::findWorthwhileCharacterSequenceForLookahead const):
+        (JSC::Yarr::BoyerMooreInfo::createCandidateBitmap const):
+        * yarr/YarrJIT.h:
+        (JSC::Yarr::BoyerMooreBitmap::count const):
+        (JSC::Yarr::BoyerMooreBitmap::map const):
+        (JSC::Yarr::BoyerMooreBitmap::isMaskEffective const):
+        (JSC::Yarr::BoyerMooreBitmap::add):
+        (JSC::Yarr::BoyerMooreByteVector::BoyerMooreByteVector):
+        (JSC::Yarr::YarrCodeBlock::set8BitCode):
+        (JSC::Yarr::YarrCodeBlock::set16BitCode):
+        (JSC::Yarr::YarrCodeBlock::set8BitCodeMatchOnly):
+        (JSC::Yarr::YarrCodeBlock::set16BitCodeMatchOnly):
+        (JSC::Yarr::YarrCodeBlock::clear):
+        (JSC::Yarr::YarrCodeBlock::findSameVector const):
+
+2021-07-28  Yusuke Suzuki  <ysuz...@apple.com>
+
         [JSC] load/store with BaseIndex is inefficient in ARM64
         https://bugs.webkit.org/show_bug.cgi?id=228543
 

Modified: trunk/Source/_javascript_Core/runtime/OptionsList.h (280451 => 280452)


--- trunk/Source/_javascript_Core/runtime/OptionsList.h	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/_javascript_Core/runtime/OptionsList.h	2021-07-29 22:26:13 UTC (rev 280452)
@@ -452,6 +452,7 @@
     v(Unsigned, prototypeHitCountForLLIntCaching, 2, Normal, "Number of prototype property hits before caching a prototype in the LLInt. A count of 0 means never cache.") \
     \
     v(Bool, dumpCompiledRegExpPatterns, false, Normal, nullptr) \
+    v(Bool, verboseRegExpCompilation, false, Normal, nullptr) \
     \
     v(Bool, dumpModuleRecord, false, Normal, nullptr) \
     v(Bool, dumpModuleLoadingState, false, Normal, nullptr) \

Modified: trunk/Source/_javascript_Core/runtime/RegExp.cpp (280451 => 280452)


--- trunk/Source/_javascript_Core/runtime/RegExp.cpp	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/_javascript_Core/runtime/RegExp.cpp	2021-07-29 22:26:13 UTC (rev 280452)
@@ -361,7 +361,7 @@
     m_state = NotCompiled;
 #if ENABLE(YARR_JIT)
     if (m_regExpJITCode)
-        m_regExpJITCode->clear();
+        m_regExpJITCode->clear(locker);
 #endif
     m_regExpBytecode = nullptr;
 }

Modified: trunk/Source/_javascript_Core/yarr/YarrJIT.cpp (280451 => 280452)


--- trunk/Source/_javascript_Core/yarr/YarrJIT.cpp	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/_javascript_Core/yarr/YarrJIT.cpp	2021-07-29 22:26:13 UTC (rev 280452)
@@ -1,5 +1,6 @@
 /*
- * Copyright (C) 2009-2018 Apple Inc. All rights reserved.
+ * Copyright (C) 2009-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2019 the V8 project authors. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -34,6 +35,7 @@
 #include "YarrDisassembler.h"
 #include "YarrMatchingContextHolder.h"
 #include <wtf/ASCIICType.h>
+#include <wtf/HexNumber.h>
 #include <wtf/Threading.h>
 
 
@@ -40,11 +42,120 @@
 #if ENABLE(YARR_JIT)
 
 namespace JSC { namespace Yarr {
+namespace YarrJITInternal {
+static constexpr bool verbose = false;
+}
 
 #if CPU(ARM64E)
 JSC_ANNOTATE_JIT_OPERATION(_JITTarget_vmEntryToYarrJITAfter, vmEntryToYarrJITAfter);
 #endif
 
+class BoyerMooreInfo {
+    WTF_MAKE_NONCOPYABLE(BoyerMooreInfo);
+    WTF_MAKE_FAST_ALLOCATED(BoyerMooreInfo);
+public:
+    static constexpr unsigned maxLength = 32;
+
+    explicit BoyerMooreInfo(unsigned length)
+        : m_characters(length)
+    {
+        ASSERT(this->length() <= maxLength);
+    }
+
+    unsigned length() const { return m_characters.size(); }
+
+    void set(unsigned index, UChar32 character)
+    {
+        m_characters[index].add(character);
+    }
+
+    unsigned index() const { return m_index; }
+    void setIndex(unsigned index)
+    {
+        m_index = index;
+    }
+
+    static UniqueRef<BoyerMooreInfo> create(unsigned length)
+    {
+        return makeUniqueRef<BoyerMooreInfo>(length);
+    }
+
+    std::optional<std::tuple<unsigned, unsigned>> findWorthwhileCharacterSequenceForLookahead() const;
+    std::tuple<BoyerMooreBitmap::Map, bool> createCandidateBitmap(unsigned begin, unsigned end) const;
+
+private:
+    std::tuple<int, unsigned, unsigned> findBestCharacterSequence(unsigned numberOfCandidatesLimit) const;
+
+    Vector<BoyerMooreBitmap> m_characters;
+    unsigned m_index { 0 };
+};
+
+std::tuple<int, unsigned, unsigned> BoyerMooreInfo::findBestCharacterSequence(unsigned numberOfCandidatesLimit) const
+{
+    int biggestPoint = 0;
+    unsigned beginResult = 0;
+    unsigned endResult = 0;
+    for (unsigned index = 0; index < length();) {
+        while (index < length() && m_characters[index].count() > numberOfCandidatesLimit)
+            ++index;
+        if (index == length())
+            break;
+        unsigned begin = index;
+        BoyerMooreBitmap::Map map { };
+        for (; index < length() && m_characters[index].count() <= numberOfCandidatesLimit; ++index)
+            map.merge(m_characters[index].map());
+
+        // If map has many candidates, then point of this sequence is low since it will match too many things.
+        // And if the sequence is longer, then the point of this sequence is higher since it can skip many characters.
+        // FIXME: Currently we are handling all characters equally. But we should have weight per character since e.g. 'e' should appear more frequently than '\v'.
+        // https://bugs.webkit.org/show_bug.cgi?id=228610
+        int frequency = map.count();
+        int matchingProbability = BoyerMooreBitmap::mapSize - frequency;
+        int point = (index - begin) * matchingProbability;
+        if (point > biggestPoint) {
+            biggestPoint = point;
+            beginResult = begin;
+            endResult = index;
+        }
+    }
+    return std::tuple { biggestPoint, beginResult, endResult };
+}
+
+std::optional<std::tuple<unsigned, unsigned>> BoyerMooreInfo::findWorthwhileCharacterSequenceForLookahead() const
+{
+    // If candiates-per-character becomes larger, then sequence is not profitable since this sequence will match against
+    // too many characters. But if we limit candiates-per-character smaller, it is possible that we only find very short
+    // character sequence. We start with low limit, then enlarging the limit to find more and more profitable
+    // character sequence.
+    int biggestPoint = 0;
+    unsigned begin = 0;
+    unsigned end = 0;
+    constexpr unsigned maxCandidatesPerCharacter = 32;
+    for (unsigned limit = 4; limit < maxCandidatesPerCharacter; limit *= 2) {
+        auto [newPoint, newBegin, newEnd] = findBestCharacterSequence(limit);
+        if (newPoint > biggestPoint) {
+            biggestPoint = newPoint;
+            begin = newBegin;
+            end = newEnd;
+        }
+    }
+    if (!biggestPoint)
+        return std::nullopt;
+    return std::tuple { begin, end };
+}
+
+std::tuple<BoyerMooreBitmap::Map, bool> BoyerMooreInfo::createCandidateBitmap(unsigned begin, unsigned end) const
+{
+    BoyerMooreBitmap::Map map { };
+    bool isMaskEffective = false;
+    for (unsigned index = begin; index < end; ++index) {
+        auto& bmBitmap = m_characters[index];
+        map.merge(bmBitmap.map());
+        isMaskEffective |= bmBitmap.isMaskEffective();
+    }
+    return std::tuple { map, isMaskEffective };
+}
+
 class YarrGenerator final : public YarrJITInfo, private MacroAssembler {
 
 #if CPU(ARM_THUMB2)
@@ -783,13 +894,11 @@
         explicit YarrOp(PatternTerm* term)
             : m_term(term)
             , m_op(YarrOpCode::Term)
-            , m_isDeadCode(false)
         {
         }
 
         explicit YarrOp(YarrOpCode op)
             : m_op(op)
-            , m_isDeadCode(false)
         {
         }
 
@@ -819,7 +928,7 @@
 
         // This flag is used to null out the second pattern character, when
         // two are fused to match a pair together.
-        bool m_isDeadCode;
+        bool m_isDeadCode { false };
 
         // Currently used in the case of some of the more complex management of
         // 'm_checkedOffset', to cache the offset used in this alternative, to avoid
@@ -830,6 +939,8 @@
         // value that will be pushed into the pattern's frame to return to,
         // upon backtracking back into the disjunction.
         DataLabelPtr m_returnAddress;
+
+        BoyerMooreInfo* m_bmInfo { nullptr };
     };
 
     // BacktrackingState
@@ -1410,7 +1521,7 @@
 
             allCharacters |= (static_cast<uint64_t>(currentCharacter) << shiftAmount);
 
-            if ((m_pattern.ignoreCase()) && (isASCIIAlpha(currentCharacter)))
+            if (m_pattern.ignoreCase() && isASCIIAlpha(currentCharacter))
                 ignoreCaseMask |= 32ULL << shiftAmount;
         }
 
@@ -2256,11 +2367,68 @@
                 // Upon entry at the head of the set of alternatives, check if input is available
                 // to run the first alternative. (This progresses the input position).
                 op.m_jumps.append(jumpIfNoAvailableInput(alternative->m_minimumSize));
+                m_checkedOffset += alternative->m_minimumSize;
+
                 // We will reenter after the check, and assume the input position to have been
                 // set as appropriate to this alternative.
                 op.m_reentry = label();
 
-                m_checkedOffset += alternative->m_minimumSize;
+                // Emit fast skip path with stride if we have BoyerMooreInfo.
+                if (op.m_bmInfo) {
+                    auto range = op.m_bmInfo->findWorthwhileCharacterSequenceForLookahead();
+                    if (range) {
+                        auto [beginIndex, endIndex] = *range;
+                        ASSERT(endIndex <= alternative->m_minimumSize);
+
+                        auto [map, isMaskEffective] = op.m_bmInfo->createCandidateBitmap(beginIndex, endIndex);
+                        unsigned mapCount = map.count();
+                        // If candiate characters are <= 2, checking each is better than using vector.
+                        if (mapCount <= 2) {
+                            UChar32 character1 = map.findBit(0, true);
+                            ASSERT(character1 != BoyerMooreBitmap::Map::size());
+                            UChar32 character2 = 0xff;
+                            if (mapCount == 2) {
+                                character2 = map.findBit(character1 + 1, true);
+                                ASSERT(character2 != BoyerMooreBitmap::Map::size());
+                            }
+                            dataLogLnIf(Options::verboseRegExpCompilation(), "Found 1-or-2 characters lookahead character:(0x", hex(character1), "),character2:(", hex(character2), "),isMaskEffective:(", isMaskEffective,"),range:[", beginIndex, ", ", endIndex, ")");
+
+                            JumpList matched;
+                            auto loopHead = label();
+                            readCharacter(m_checkedOffset - endIndex + 1, regT0);
+                            if (isMaskEffective)
+                                and32(TrustedImm32(BoyerMooreBitmap::mapMask), regT0, regT0);
+                            matched.append(branch32(Equal, regT0, TrustedImm32(character1)));
+                            if (mapCount == 2)
+                                matched.append(branch32(Equal, regT0, TrustedImm32(character2)));
+                            op.m_jumps.append(jumpIfNoAvailableInput(endIndex - beginIndex));
+                            jump().linkTo(loopHead, this);
+                            matched.link(this);
+                        } else {
+                            const uint8_t* pointer = getBoyerMooreByteVector(op.m_bmInfo->index(), map);
+                            dataLogLnIf(Options::verboseRegExpCompilation(), "Found bitmap lookahead count:(", mapCount, "),range:[", beginIndex, ", ", endIndex, ")");
+
+                            move(TrustedImmPtr(pointer), regT1);
+                            auto loopHead = label();
+                            readCharacter(m_checkedOffset - endIndex + 1, regT0);
+                            and32(TrustedImm32(BoyerMooreBitmap::mapMask), regT0, regT0);
+                            auto matched = branchTest32(NonZero, BaseIndex(regT1, regT0, TimesOne));
+                            op.m_jumps.append(jumpIfNoAvailableInput(endIndex - beginIndex));
+                            jump().linkTo(loopHead, this);
+                            matched.link(this);
+                        }
+
+                        // If the pattern size is not fixed, then store the start index for use if we match.
+                        if (!m_pattern.m_body->m_hasFixedSize) {
+                            if (alternative->m_minimumSize) {
+                                move(index, regT0);
+                                sub32(Imm32(alternative->m_minimumSize), regT0);
+                                setMatchStart(regT0);
+                            } else
+                                setMatchStart(index);
+                        }
+                    }
+                }
                 break;
             }
             case YarrOpCode::BodyAlternativeNext:
@@ -2780,6 +2948,7 @@
                     break;
                 }
                 YarrOp& endOp = m_ops[op.m_nextOp];
+                ASSERT(endOp.m_op == YarrOpCode::BodyAlternativeEnd);
 
                 YarrOp* beginOp = &op;
                 while (beginOp->m_op != YarrOpCode::BodyAlternativeBegin) {
@@ -2797,7 +2966,7 @@
                 if (onceThrough)
                     m_backtrackingState.linkTo(endOp.m_reentry, this);
                 else {
-                    if (m_pattern.sticky() && m_ops[op.m_nextOp].m_op == YarrOpCode::BodyAlternativeEnd) {
+                    if (m_pattern.sticky()) {
                         // It is a sticky pattern and the last alternative failed, jump to the end.
                         m_backtrackingState.takeBacktracksToJumpList(lastStickyAlternativeFailures, this);
                     } else if (m_pattern.m_body->m_hasFixedSize
@@ -2813,41 +2982,41 @@
                         // around to the first alternative.
                         m_backtrackingState.link(this);
 
-                        // No need to advance and retry for a sticky pattern.
-                        if (!m_pattern.sticky()) {
-                            // If the pattern size is not fixed, then store the start index for use if we match.
-                            if (!m_pattern.m_body->m_hasFixedSize) {
-                                if (alternative->m_minimumSize == 1)
-                                    setMatchStart(index);
-                                else {
-                                    move(index, regT0);
-                                    if (alternative->m_minimumSize)
-                                        sub32(Imm32(alternative->m_minimumSize - 1), regT0);
-                                    else
-                                        add32(TrustedImm32(1), regT0);
-                                    setMatchStart(regT0);
-                                }
+                        // No need to advance and retry for a sticky pattern. And it is already handled before this branch.
+                        ASSERT(!m_pattern.sticky());
+
+                        // If the pattern size is not fixed, then store the start index for use if we match.
+                        if (!m_pattern.m_body->m_hasFixedSize) {
+                            if (alternative->m_minimumSize == 1)
+                                setMatchStart(index);
+                            else {
+                                move(index, regT0);
+                                if (alternative->m_minimumSize)
+                                    sub32(Imm32(alternative->m_minimumSize - 1), regT0);
+                                else
+                                    add32(TrustedImm32(1), regT0);
+                                setMatchStart(regT0);
                             }
+                        }
 
-                            // Generate code to loop. Check whether the last alternative is longer than the
-                            // first (e.g. /a|xy/ or /a|xyz/).
-                            if (alternative->m_minimumSize > beginOp->m_alternative->m_minimumSize) {
-                                // We want to loop, and increment input position. If the delta is 1, it is
-                                // already correctly incremented, if more than one then decrement as appropriate.
-                                unsigned delta = alternative->m_minimumSize - beginOp->m_alternative->m_minimumSize;
-                                ASSERT(delta);
-                                if (delta != 1)
-                                    sub32(Imm32(delta - 1), index);
-                                jump(beginOp->m_reentry);
-                            } else {
-                                // If the first alternative has minimum size 0xFFFFFFFFu, then there cannot
-                                // be sufficent input available to handle this, so just fall through.
-                                unsigned delta = beginOp->m_alternative->m_minimumSize - alternative->m_minimumSize;
-                                if (delta != 0xFFFFFFFFu) {
-                                    // We need to check input because we are incrementing the input.
-                                    add32(Imm32(delta + 1), index);
-                                    checkInput().linkTo(beginOp->m_reentry, this);
-                                }
+                        // Generate code to loop. Check whether the last alternative is longer than the
+                        // first (e.g. /a|xy/ or /a|xyz/).
+                        if (alternative->m_minimumSize > beginOp->m_alternative->m_minimumSize) {
+                            // We want to loop, and increment input position. If the delta is 1, it is
+                            // already correctly incremented, if more than one then decrement as appropriate.
+                            unsigned delta = alternative->m_minimumSize - beginOp->m_alternative->m_minimumSize;
+                            ASSERT(delta);
+                            if (delta != 1)
+                                sub32(Imm32(delta - 1), index);
+                            jump(beginOp->m_reentry);
+                        } else {
+                            // If the first alternative has minimum size 0xFFFFFFFFu, then there cannot
+                            // be sufficent input available to handle this, so just fall through.
+                            unsigned delta = beginOp->m_alternative->m_minimumSize - alternative->m_minimumSize;
+                            if (delta != 0xFFFFFFFFu) {
+                                // We need to check input because we are incrementing the input.
+                                add32(Imm32(delta + 1), index);
+                                checkInput().linkTo(beginOp->m_reentry, this);
                             }
                         }
                     }
@@ -3643,6 +3812,21 @@
         size_t repeatLoop = m_ops.size();
         m_ops.append(YarrOp(YarrOpCode::BodyAlternativeBegin));
         m_ops.last().m_previousOp = notFound;
+        // Collect BoyerMooreInfo if it is possible and profitable. BoyerMooreInfo will be used to emit fast skip path with large stride
+        // at the beginning of the body alternatives.
+        // We do not emit these fast path when RegExp has sticky or unicode flag. Sticky case does not need this since
+        // it fails when the body alternatives fail to match with the current offset.
+        // FIXME: Support unicode flag.
+        // https://bugs.webkit.org/show_bug.cgi?id=228611
+        if (disjunction->m_minimumSize && disjunction->m_hasFixedSize && !m_pattern.sticky() && !m_pattern.unicode()) {
+            auto bmInfo = BoyerMooreInfo::create(std::min<unsigned>(disjunction->m_minimumSize, BoyerMooreInfo::maxLength));
+            if (collectBoyerMooreInfo(disjunction, currentAlternativeIndex, bmInfo.get())) {
+                m_ops.last().m_bmInfo = bmInfo.ptr();
+                bmInfo->setIndex(m_bmInfos.size());
+                m_bmInfos.append(WTFMove(bmInfo));
+            }
+        }
+
         do {
             size_t lastOpIndex = m_ops.size() - 1;
             PatternAlternative* alternative = alternatives[currentAlternativeIndex].get();
@@ -3668,6 +3852,82 @@
         lastOp.m_nextOp = repeatLoop;
     }
 
+    bool collectBoyerMooreInfo(PatternDisjunction* disjunction, size_t currentAlternativeIndex, BoyerMooreInfo& bmInfo)
+    {
+        // If we have a searching pattern /abcdef/, then we can check the 6th character against a set of {a, b, c, d, e, f}.
+        // If it does not match, we can shift 6 characters. We use this strategy since this way can be extended easily to support
+        // disjunction, character-class, and ignore-cases. For example, in the case of /(?:abc|def)/, we can check 3rd character
+        // against {a, b, c, d, e, f} and shift 3 characters if it does not match.
+        //
+        // Then, the best way to perform the above shifting is that finding the longest character sequence which does not have
+        // many candidates. In the case of /[a-z]aaaaaaa[a-z]/, we can extract "aaaaaaa" sequence and check 8th character against {a}.
+        // If it does not match, then we can shift 7 characters (length of "aaaaaaa"). This shifting is better than using "[a-z]aaaaaaa[a-z]"
+        // sequence and {a-z} set since {a-z} set will almost always match.
+        //
+        // We first collect possible characters for each character position. Then, apply heuristics to extract good character sequence from
+        // that and construct fast searching with long stride.
+
+        ASSERT(disjunction->m_hasFixedSize); // We only support fixed-sized lookahead for BoyerMoore search.
+        ASSERT(disjunction->m_minimumSize);
+
+        // FIXME: Support nested disjunctions (e.g. /(?:abc|def|g(?:hi|jk))/).
+        // https://bugs.webkit.org/show_bug.cgi?id=228614
+        // FIXME: Support character-class (e.g. /[\d]test/).
+        // https://bugs.webkit.org/show_bug.cgi?id=228613
+        // FIXME: Support non-fixed-sized lookahead (e.g. /.*abc/ and extract "abc" sequence).
+        // https://bugs.webkit.org/show_bug.cgi?id=228612
+        auto& alternatives = disjunction->m_alternatives;
+        for (; currentAlternativeIndex < alternatives.size(); ++currentAlternativeIndex) {
+            unsigned cursor = 0;
+            PatternAlternative* alternative = alternatives[currentAlternativeIndex].get();
+            for (unsigned index = 0; index < alternative->m_terms.size() && cursor < bmInfo.length(); ++index) {
+                PatternTerm& term = alternative->m_terms[index];
+                switch (term.type) {
+                case PatternTerm::Type::AssertionBOL:
+                case PatternTerm::Type::AssertionEOL:
+                case PatternTerm::Type::AssertionWordBoundary:
+                case PatternTerm::Type::CharacterClass:
+                case PatternTerm::Type::BackReference:
+                case PatternTerm::Type::ForwardReference:
+                case PatternTerm::Type::ParenthesesSubpattern:
+                case PatternTerm::Type::ParentheticalAssertion:
+                case PatternTerm::Type::DotStarEnclosure:
+                    return false;
+                case PatternTerm::Type::PatternCharacter: {
+                    if (term.quantityType != QuantifierType::FixedCount || term.quantityMaxCount != 1)
+                        return false;
+                    if (term.inputPosition != index)
+                        return false;
+                    if (U16_LENGTH(term.patternCharacter) != 1 && m_decodeSurrogatePairs)
+                        return false;
+                    // For case-insesitive compares, non-ascii characters that have different
+                    // upper & lower case representations are already converted to a character class.
+                    ASSERT(!m_pattern.ignoreCase() || isASCIIAlpha(term.patternCharacter) || isCanonicallyUnique(term.patternCharacter, m_canonicalMode));
+                    if (m_pattern.ignoreCase() && isASCIIAlpha(term.patternCharacter)) {
+                        bmInfo.set(cursor, toASCIIUpper(term.patternCharacter));
+                        bmInfo.set(cursor, toASCIILower(term.patternCharacter));
+                    } else
+                        bmInfo.set(cursor, term.patternCharacter);
+                    ++cursor;
+                    break;
+                }
+                }
+            }
+        }
+        dataLogLnIf(YarrJITInternal::verbose, "Characters collected");
+        return true;
+    }
+
+    const uint8_t* getBoyerMooreByteVector(unsigned index, const BoyerMooreBitmap::Map& map)
+    {
+        auto vector = makeUniqueRef<BoyerMooreByteVector>(map);
+        if (const auto* existingPointer = m_codeBlock.tryReuseBoyerMooreByteVector(index, vector.get()))
+           return existingPointer;
+        const uint8_t* pointer = vector->data();
+        m_bmVector.append(WTFMove(vector));
+        return pointer;
+    }
+
     void generateTryReadUnicodeCharacterHelper()
     {
 #ifdef JIT_UNICODE_EXPRESSIONS
@@ -3952,14 +4212,14 @@
 
         if (m_compileMode == JITCompileMode::MatchOnly) {
             if (m_charSize == CharSize::Char8)
-                codeBlock.set8BitCodeMatchOnly(FINALIZE_REGEXP_CODE(linkBuffer, YarrMatchOnly8BitPtrTag, "Match-only 8-bit regular _expression_"));
+                codeBlock.set8BitCodeMatchOnly(FINALIZE_REGEXP_CODE(linkBuffer, YarrMatchOnly8BitPtrTag, "Match-only 8-bit regular _expression_"), WTFMove(m_bmVector));
             else
-                codeBlock.set16BitCodeMatchOnly(FINALIZE_REGEXP_CODE(linkBuffer, YarrMatchOnly16BitPtrTag, "Match-only 16-bit regular _expression_"));
+                codeBlock.set16BitCodeMatchOnly(FINALIZE_REGEXP_CODE(linkBuffer, YarrMatchOnly16BitPtrTag, "Match-only 16-bit regular _expression_"), WTFMove(m_bmVector));
         } else {
             if (m_charSize == CharSize::Char8)
-                codeBlock.set8BitCode(FINALIZE_REGEXP_CODE(linkBuffer, Yarr8BitPtrTag, "8-bit regular _expression_"));
+                codeBlock.set8BitCode(FINALIZE_REGEXP_CODE(linkBuffer, Yarr8BitPtrTag, "8-bit regular _expression_"), WTFMove(m_bmVector));
             else
-                codeBlock.set16BitCode(FINALIZE_REGEXP_CODE(linkBuffer, Yarr16BitPtrTag, "16-bit regular _expression_"));
+                codeBlock.set16BitCode(FINALIZE_REGEXP_CODE(linkBuffer, Yarr16BitPtrTag, "16-bit regular _expression_"), WTFMove(m_bmVector));
         }
         if (m_failureReason)
             codeBlock.setFallBackWithFailureReason(*m_failureReason);
@@ -4197,6 +4457,8 @@
 
     // The regular _expression_ expressed as a linear sequence of operations.
     Vector<YarrOp, 128> m_ops;
+    Vector<UniqueRef<BoyerMooreInfo>, 4> m_bmInfos;
+    Vector<UniqueRef<BoyerMooreByteVector>> m_bmVector;
 
     // This records the current input offset being applied due to the current
     // set of alternatives we are nested within. E.g. when matching the

Modified: trunk/Source/_javascript_Core/yarr/YarrJIT.h (280451 => 280452)


--- trunk/Source/_javascript_Core/yarr/YarrJIT.h	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/_javascript_Core/yarr/YarrJIT.h	2021-07-29 22:26:13 UTC (rev 280452)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2009-2021 Apple Inc. All rights reserved.
+ * Copyright (C) 2019 the V8 project authors. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -32,6 +33,9 @@
 #include "VM.h"
 #include "Yarr.h"
 #include "YarrPattern.h"
+#include <wtf/Bitmap.h>
+#include <wtf/FixedVector.h>
+#include <wtf/UniqueRef.h>
 
 #define YARR_CALL
 
@@ -56,6 +60,48 @@
     ExecutableMemoryAllocationFailure,
 };
 
+class BoyerMooreBitmap {
+    WTF_MAKE_FAST_ALLOCATED(BoyerMooreBitmap);
+public:
+    static constexpr unsigned mapSize = 128;
+    static constexpr unsigned mapMask = 128 - 1;
+    using Map = Bitmap<mapSize>;
+
+    BoyerMooreBitmap() = default;
+
+    unsigned count() const { return m_count; }
+    const Map& map() const { return m_map; }
+    bool isMaskEffective() const { return m_isMaskEffective; }
+
+    void add(UChar32 character)
+    {
+        unsigned position = character & mapMask;
+        if (position != static_cast<unsigned>(character))
+            m_isMaskEffective = true;
+        if (!m_map.get(position)) {
+            m_map.set(position);
+            ++m_count;
+        }
+    }
+
+private:
+    Map m_map { };
+    unsigned m_count { 0 };
+    bool m_isMaskEffective { false };
+};
+
+class BoyerMooreByteVector : public std::array<uint8_t, BoyerMooreBitmap::mapSize> {
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    BoyerMooreByteVector(const BoyerMooreBitmap::Map& map)
+    {
+        fill(0);
+        map.forEachSetBit([&](unsigned index) {
+            (*this)[index] = 1;
+        });
+    }
+};
+
 #if CPU(ARM64E)
 extern "C" EncodedMatchResult vmEntryToYarrJIT(const void* input, unsigned start, unsigned length, int* output, MatchingContextHolder* matchingContext, const void* codePtr);
 extern "C" void vmEntryToYarrJITAfter(void);
@@ -78,13 +124,37 @@
 
     bool has8BitCode() { return m_ref8.size(); }
     bool has16BitCode() { return m_ref16.size(); }
-    void set8BitCode(MacroAssemblerCodeRef<Yarr8BitPtrTag> ref) { m_ref8 = ref; }
-    void set16BitCode(MacroAssemblerCodeRef<Yarr16BitPtrTag> ref) { m_ref16 = ref; }
+    void set8BitCode(MacroAssemblerCodeRef<Yarr8BitPtrTag> ref, Vector<UniqueRef<BoyerMooreByteVector>> maps)
+    {
+        m_ref8 = ref;
+        m_maps.reserveCapacity(m_maps.size() + maps.size());
+        for (unsigned index = 0; index < maps.size(); ++index)
+            m_maps.uncheckedAppend(WTFMove(maps[index]));
+    }
+    void set16BitCode(MacroAssemblerCodeRef<Yarr16BitPtrTag> ref, Vector<UniqueRef<BoyerMooreByteVector>> maps)
+    {
+        m_ref16 = ref;
+        m_maps.reserveCapacity(m_maps.size() + maps.size());
+        for (unsigned index = 0; index < maps.size(); ++index)
+            m_maps.uncheckedAppend(WTFMove(maps[index]));
+    }
 
     bool has8BitCodeMatchOnly() { return m_matchOnly8.size(); }
     bool has16BitCodeMatchOnly() { return m_matchOnly16.size(); }
-    void set8BitCodeMatchOnly(MacroAssemblerCodeRef<YarrMatchOnly8BitPtrTag> matchOnly) { m_matchOnly8 = matchOnly; }
-    void set16BitCodeMatchOnly(MacroAssemblerCodeRef<YarrMatchOnly16BitPtrTag> matchOnly) { m_matchOnly16 = matchOnly; }
+    void set8BitCodeMatchOnly(MacroAssemblerCodeRef<YarrMatchOnly8BitPtrTag> matchOnly, Vector<UniqueRef<BoyerMooreByteVector>> maps)
+    {
+        m_matchOnly8 = matchOnly;
+        m_maps.reserveCapacity(m_maps.size() + maps.size());
+        for (unsigned index = 0; index < maps.size(); ++index)
+            m_maps.uncheckedAppend(WTFMove(maps[index]));
+    }
+    void set16BitCodeMatchOnly(MacroAssemblerCodeRef<YarrMatchOnly16BitPtrTag> matchOnly, Vector<UniqueRef<BoyerMooreByteVector>> maps)
+    {
+        m_matchOnly16 = matchOnly;
+        m_maps.reserveCapacity(m_maps.size() + maps.size());
+        for (unsigned index = 0; index < maps.size(); ++index)
+            m_maps.uncheckedAppend(WTFMove(maps[index]));
+    }
 
     bool usesPatternContextBuffer() { return m_usesPatternContextBuffer; }
 #if ENABLE(YARR_JIT_ALL_PARENS_EXPRESSIONS)
@@ -170,20 +240,31 @@
         return m_ref8.size() + m_ref16.size() + m_matchOnly8.size() + m_matchOnly16.size();
     }
 
-    void clear()
+    void clear(const AbstractLocker&)
     {
         m_ref8 = MacroAssemblerCodeRef<Yarr8BitPtrTag>();
         m_ref16 = MacroAssemblerCodeRef<Yarr16BitPtrTag>();
         m_matchOnly8 = MacroAssemblerCodeRef<YarrMatchOnly8BitPtrTag>();
         m_matchOnly16 = MacroAssemblerCodeRef<YarrMatchOnly16BitPtrTag>();
+        m_maps.clear();
         m_failureReason = std::nullopt;
     }
 
+    const uint8_t* tryReuseBoyerMooreByteVector(unsigned index, BoyerMooreByteVector& vector) const
+    {
+        if (index < m_maps.size()) {
+            if (m_maps[index].get() == vector)
+                return m_maps[index]->data();
+        }
+        return nullptr;
+    }
+
 private:
     MacroAssemblerCodeRef<Yarr8BitPtrTag> m_ref8;
     MacroAssemblerCodeRef<Yarr16BitPtrTag> m_ref16;
     MacroAssemblerCodeRef<YarrMatchOnly8BitPtrTag> m_matchOnly8;
     MacroAssemblerCodeRef<YarrMatchOnly16BitPtrTag> m_matchOnly16;
+    Vector<UniqueRef<BoyerMooreByteVector>> m_maps;
     bool m_usesPatternContextBuffer { false };
     std::optional<JITFailureReason> m_failureReason;
 };

Modified: trunk/Source/WTF/ChangeLog (280451 => 280452)


--- trunk/Source/WTF/ChangeLog	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/WTF/ChangeLog	2021-07-29 22:26:13 UTC (rev 280452)
@@ -1,3 +1,18 @@
+2021-07-28  Yusuke Suzuki  <ysuz...@apple.com>
+
+        [JSC] Yarr should perform BoyerMoore search
+        https://bugs.webkit.org/show_bug.cgi?id=228301
+
+        Reviewed by Saam Barati.
+
+        * wtf/BitVector.cpp:
+        (WTF::BitVector::dump const):
+        * wtf/Bitmap.h:
+        (WTF::WordType>::dump const):
+        * wtf/UniqueRef.h:
+        (WTF::makeUniqueRefFromNonNullUniquePtr):
+        (WTF::UniqueRef::UniqueRef):
+
 2021-07-29  Kate Cheney  <katherine_che...@apple.com>
 
         REGRESSION (r278877) [Cocoa] WebAuthn stopped working for non-Safari browsers 

Modified: trunk/Source/WTF/wtf/BitVector.cpp (280451 => 280452)


--- trunk/Source/WTF/wtf/BitVector.cpp	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/WTF/wtf/BitVector.cpp	2021-07-29 22:26:13 UTC (rev 280452)
@@ -262,12 +262,8 @@
 
 void BitVector::dump(PrintStream& out) const
 {
-    for (size_t i = 0; i < size(); ++i) {
-        if (get(i))
-            out.printf("1");
-        else
-            out.printf("-");
-    }
+    for (size_t i = 0; i < size(); ++i)
+        out.print(get(i) ? "1" : "-");
 }
 
 } // namespace WTF

Modified: trunk/Source/WTF/wtf/Bitmap.h (280451 => 280452)


--- trunk/Source/WTF/wtf/Bitmap.h	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/WTF/wtf/Bitmap.h	2021-07-29 22:26:13 UTC (rev 280452)
@@ -23,6 +23,7 @@
 #include <wtf/Atomics.h>
 #include <wtf/HashFunctions.h>
 #include <wtf/MathExtras.h>
+#include <wtf/PrintStream.h>
 #include <wtf/StdIntExtras.h>
 #include <string.h>
 #include <type_traits>
@@ -130,6 +131,8 @@
 
     unsigned hash() const;
 
+    void dump(PrintStream& out) const;
+
 private:
     static constexpr unsigned wordSize = sizeof(WordType) * 8;
     static constexpr unsigned words = (bitmapSize + wordSize - 1) / wordSize;
@@ -507,6 +510,13 @@
     return result;
 }
 
+template<size_t bitmapSize, typename WordType>
+inline void Bitmap<bitmapSize, WordType>::dump(PrintStream& out) const
+{
+    for (size_t i = 0; i < size(); ++i)
+        out.print(get(i) ? "1" : "-");
+}
+
 } // namespace WTF
 
 using WTF::Bitmap;

Modified: trunk/Source/WTF/wtf/UniqueRef.h (280451 => 280452)


--- trunk/Source/WTF/wtf/UniqueRef.h	2021-07-29 22:12:16 UTC (rev 280451)
+++ trunk/Source/WTF/wtf/UniqueRef.h	2021-07-29 22:26:13 UTC (rev 280452)
@@ -46,9 +46,9 @@
 }
 
 template<typename T>
-UniqueRef<T> makeUniqueRefFromNonNullUniquePtr(std::unique_ptr<T> ptr)
+UniqueRef<T> makeUniqueRefFromNonNullUniquePtr(std::unique_ptr<T>&& ptr)
 {
-    return UniqueRef<T>(*ptr.release());
+    return UniqueRef<T>(WTFMove(ptr));
 }
 
 template<typename T>
@@ -67,6 +67,9 @@
         ASSERT(m_ref);
     }
 
+    T* ptr() RETURNS_NONNULL { ASSERT(m_ref); return m_ref.get(); }
+    T* ptr() const RETURNS_NONNULL { ASSERT(m_ref); return m_ref.get(); }
+
     T& get() { ASSERT(m_ref); return *m_ref; }
     const T& get() const { ASSERT(m_ref); return *m_ref; }
 
@@ -83,9 +86,15 @@
 
 private:
     template<class U, class... Args> friend UniqueRef<U> makeUniqueRefWithoutFastMallocCheck(Args&&...);
-    template<class U> friend UniqueRef<U> makeUniqueRefFromNonNullUniquePtr(std::unique_ptr<U>);
+    template<class U> friend UniqueRef<U> makeUniqueRefFromNonNullUniquePtr(std::unique_ptr<U>&&);
     template<class U> friend class UniqueRef;
 
+    explicit UniqueRef(std::unique_ptr<T>&& ptr)
+        : m_ref(WTFMove(ptr))
+    {
+        ASSERT(m_ref);
+    }
+
     std::unique_ptr<T> m_ref;
 };
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to