⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 280570 in webkit


Ignore:
Timestamp:
Aug 2, 2021, 4:43:16 PM (5 years ago)
Author:
ysuzuki@apple.com
Message:

[JSC] Yarr BoyerMoore search should support character-class
​https://bugs.webkit.org/show_bug.cgi?id=228613

Reviewed by Saam Barati.

JSTests:

  • stress/regexp-bm-search-character-non-fixed-size.js: Added.

(shouldBe):

  • stress/regexp-bm-search-many-candidate-zero-length.js: Added.

(shouldBe):
(regexp.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.0.1.2.3.4.5.6.7.8.9.t.v.n.r):

  • stress/regexp-bm-search-non-fixed-size.js: Added.

(shouldBe):

Source/JavaScriptCore:

This patch adds character-class support for BoyerMoore lookahead search in Yarr.
Currently, we only support fixed-sized character-class. We can extend it for repeat cases in the future.

To apply this character-class thing to jQuery's RegExp, we also allow non-fixed-sized disjunction.
For example, /aaaa.*|bbbb/'s disjunction is not fixed-sized. But still we can use (aaaa|bbbb) prefix since
this part is fixed-sized and we know minimum-size of this disjunction is 4.

Plus, instead of giving up BoyerMoore search when we found non-supported terms, we shorten BoyerMoore search
length not to include this term so that we can still have a chance to leverage BoyerMoore search. In the case
of /aaaa|bbbb|ccc(d|e|f)/, we previously gave up since it finds (d|e|f). But now, instead we shorten the length
from 4 to 3, and construct search pattern with aaa|bbb|ccc.

This patch improves jquery-todomvc-regexp by 20%.

ToT Patched

jquery-todomvc-regexp 545.3561+-0.6968 451.6117+-0.4613 definitely 1.2076x faster

This improves Speedometer2/jQuery-TodoMVC by 2%.

----------------------------------------------------------------------------------------------------------------------------------
| subtest | ms | ms | b / a | pValue (significance using False Discovery Rate) |
----------------------------------------------------------------------------------------------------------------------------------
| Elm-TodoMVC |123.470833 |123.550000 |1.000641 | 0.841600 |
| VueJS-TodoMVC |26.883333 |26.950000 |1.002480 | 0.846732 |
| EmberJS-TodoMVC |127.708333 |127.754167 |1.000359 | 0.934206 |
| BackboneJS-TodoMVC |50.545833 |50.445833 |0.998022 | 0.679610 |
| Preact-TodoMVC |20.879167 |20.791667 |0.995809 | 0.796541 |
| AngularJS-TodoMVC |137.479167 |137.275000 |0.998515 | 0.729817 |
| Vanilla-ES2015-TodoMVC |69.079167 |68.912500 |0.997587 | 0.524325 |
| Inferno-TodoMVC |65.604167 |66.120833 |1.007876 | 0.145549 |
| Flight-TodoMVC |77.029167 |76.708333 |0.995835 | 0.518562 |
| Angular2-TypeScript-TodoMVC |40.516667 |40.812500 |1.007302 | 0.513386 |
| VanillaJS-TodoMVC |54.762500 |54.895833 |1.002435 | 0.647381 |
| jQuery-TodoMVC |255.950000 |250.425000 |0.978414 | 0.000000 (significant) |
| EmberJS-Debug-TodoMVC |341.745833 |342.804167 |1.003097 | 0.219937 |
| React-TodoMVC |88.854167 |88.700000 |0.998265 | 0.568405 |
| React-Redux-TodoMVC |151.266667 |150.804167 |0.996942 | 0.256403 |
| Vanilla-ES2015-Babel-Webpack-TodoMVC |65.783333 |65.645833 |0.997910 | 0.437464 |
----------------------------------------------------------------------------------------------------------------------------------
a mean = 246.52898
b mean = 246.85128
pValue = 0.3927330278
(Bigger means are better.)
1.001 times better
Results ARE NOT significant

  • yarr/YarrJIT.cpp:

(JSC::Yarr::BoyerMooreInfo::shortenLength):
(JSC::Yarr::BoyerMooreInfo::setAll):
(JSC::Yarr::BoyerMooreInfo::addCharacters):
(JSC::Yarr::BoyerMooreInfo::addRanges):

  • yarr/YarrJIT.h:

(JSC::Yarr::BoyerMooreBitmap::add):
(JSC::Yarr::BoyerMooreBitmap::addCharacters):
(JSC::Yarr::BoyerMooreBitmap::addRanges):
(JSC::Yarr::BoyerMooreBitmap::setAll):
(JSC::Yarr::BoyerMooreBitmap::isAllSet const):

Location:
trunk
Files:
3 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/JSTests/ChangeLog

    r280546 r280570  
     12021-08-02  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Yarr BoyerMoore search should support character-class
     4        https://bugs.webkit.org/show_bug.cgi?id=228613
     5
     6        Reviewed by Saam Barati.
     7
     8        * stress/regexp-bm-search-character-non-fixed-size.js: Added.
     9        (shouldBe):
     10        * stress/regexp-bm-search-many-candidate-zero-length.js: Added.
     11        (shouldBe):
     12        (regexp.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.0.1.2.3.4.5.6.7.8.9.t.v.n.r):
     13        * stress/regexp-bm-search-non-fixed-size.js: Added.
     14        (shouldBe):
     15
    1162021-08-02  Yusuke Suzuki  <ysuzuki@apple.com>
    217
  • trunk/Source/JavaScriptCore/ChangeLog

    r280569 r280570  
     12021-08-02  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Yarr BoyerMoore search should support character-class
     4        https://bugs.webkit.org/show_bug.cgi?id=228613
     5
     6        Reviewed by Saam Barati.
     7
     8        This patch adds character-class support for BoyerMoore lookahead search in Yarr.
     9        Currently, we only support fixed-sized character-class. We can extend it for repeat cases in the future.
     10
     11        To apply this character-class thing to jQuery's RegExp, we also allow non-fixed-sized disjunction.
     12        For example, /aaaa.*|bbbb/'s disjunction is not fixed-sized. But still we can use (aaaa|bbbb) prefix since
     13        this part is fixed-sized and we know minimum-size of this disjunction is 4.
     14
     15        Plus, instead of giving up BoyerMoore search when we found non-supported terms, we shorten BoyerMoore search
     16        length not to include this term so that we can still have a chance to leverage BoyerMoore search. In the case
     17        of /aaaa|bbbb|ccc(d|e|f)/, we previously gave up since it finds `(d|e|f)`. But now, instead we shorten the length
     18        from 4 to 3, and construct search pattern with `aaa|bbb|ccc`.
     19
     20        This patch improves jquery-todomvc-regexp by 20%.
     21
     22                                              ToT                     Patched
     23
     24            jquery-todomvc-regexp      545.3561+-0.6968     ^    451.6117+-0.4613        ^ definitely 1.2076x faster
     25
     26        This improves Speedometer2/jQuery-TodoMVC by 2%.
     27
     28            ----------------------------------------------------------------------------------------------------------------------------------
     29            |               subtest                |     ms      |     ms      |  b / a   | pValue (significance using False Discovery Rate) |
     30            ----------------------------------------------------------------------------------------------------------------------------------
     31            | Elm-TodoMVC                          |123.470833   |123.550000   |1.000641  | 0.841600                                         |
     32            | VueJS-TodoMVC                        |26.883333    |26.950000    |1.002480  | 0.846732                                         |
     33            | EmberJS-TodoMVC                      |127.708333   |127.754167   |1.000359  | 0.934206                                         |
     34            | BackboneJS-TodoMVC                   |50.545833    |50.445833    |0.998022  | 0.679610                                         |
     35            | Preact-TodoMVC                       |20.879167    |20.791667    |0.995809  | 0.796541                                         |
     36            | AngularJS-TodoMVC                    |137.479167   |137.275000   |0.998515  | 0.729817                                         |
     37            | Vanilla-ES2015-TodoMVC               |69.079167    |68.912500    |0.997587  | 0.524325                                         |
     38            | Inferno-TodoMVC                      |65.604167    |66.120833    |1.007876  | 0.145549                                         |
     39            | Flight-TodoMVC                       |77.029167    |76.708333    |0.995835  | 0.518562                                         |
     40            | Angular2-TypeScript-TodoMVC          |40.516667    |40.812500    |1.007302  | 0.513386                                         |
     41            | VanillaJS-TodoMVC                    |54.762500    |54.895833    |1.002435  | 0.647381                                         |
     42            | jQuery-TodoMVC                       |255.950000   |250.425000   |0.978414  | 0.000000 (significant)                           |
     43            | EmberJS-Debug-TodoMVC                |341.745833   |342.804167   |1.003097  | 0.219937                                         |
     44            | React-TodoMVC                        |88.854167    |88.700000    |0.998265  | 0.568405                                         |
     45            | React-Redux-TodoMVC                  |151.266667   |150.804167   |0.996942  | 0.256403                                         |
     46            | Vanilla-ES2015-Babel-Webpack-TodoMVC |65.783333    |65.645833    |0.997910  | 0.437464                                         |
     47            ----------------------------------------------------------------------------------------------------------------------------------
     48            a mean = 246.52898
     49            b mean = 246.85128
     50            pValue = 0.3927330278
     51            (Bigger means are better.)
     52            1.001 times better
     53            Results ARE NOT significant
     54
     55        * yarr/YarrJIT.cpp:
     56        (JSC::Yarr::BoyerMooreInfo::shortenLength):
     57        (JSC::Yarr::BoyerMooreInfo::setAll):
     58        (JSC::Yarr::BoyerMooreInfo::addCharacters):
     59        (JSC::Yarr::BoyerMooreInfo::addRanges):
     60        * yarr/YarrJIT.h:
     61        (JSC::Yarr::BoyerMooreBitmap::add):
     62        (JSC::Yarr::BoyerMooreBitmap::addCharacters):
     63        (JSC::Yarr::BoyerMooreBitmap::addRanges):
     64        (JSC::Yarr::BoyerMooreBitmap::setAll):
     65        (JSC::Yarr::BoyerMooreBitmap::isAllSet const):
     66
    1672021-08-02  Stephan Szabo  <stephan.szabo@sony.com>
    268
  • trunk/Source/JavaScriptCore/yarr/YarrJIT.cpp

    r280544 r280570  
    6464
    6565    unsigned length() const { return m_characters.size(); }
     66    void shortenLength(unsigned length)
     67    {
     68        ASSERT(length <= this->length());
     69        m_characters.shrink(length);
     70    }
    6671
    6772    void set(unsigned index, UChar32 character)
    6873    {
    6974        m_characters[index].add(character);
     75    }
     76
     77    void setAll(unsigned index)
     78    {
     79        m_characters[index].setAll();
     80    }
     81
     82    void addCharacters(unsigned index, const Vector<UChar32>& characters)
     83    {
     84        m_characters[index].addCharacters(characters);
     85    }
     86
     87    void addRanges(unsigned index, const Vector<CharacterRange>& range)
     88    {
     89        m_characters[index].addRanges(range);
    7090    }
    7191
    … …  
    125145    unsigned end = 0;
    126146    constexpr unsigned maxCandidatesPerCharacter = 32;
     147    static_assert(maxCandidatesPerCharacter < BoyerMooreBitmap::mapSize);
    127148    for (unsigned limit = 4; limit < maxCandidatesPerCharacter; limit *= 2) {
    128149        auto [newPoint, newBegin, newEnd] = findBestCharacterSequence(limit);
    … …  
    23822403                        unsigned mapCount = map.count();
    23832404                        // If candiate characters are <= 2, checking each is better than using vector.
     2405                        JumpList outOfLengthFailure;
     2406                        JumpList matched;
     2407                        dataLogLnIf(YarrJITInternal::verbose, "BM Bitmap is ", map);
     2408                        // Patterns like /[]/ have zero candidates. Since it is rare, we do not do nothing for now.
     2409                        if (!mapCount)
     2410                            break;
    23842411                        if (mapCount <= 2) {
    23852412                            UChar32 character1 = map.findBit(0, true);
    … …  
    23922419                            dataLogLnIf(Options::verboseRegExpCompilation(), "Found 1-or-2 characters lookahead character:(0x", hex(character1), "),character2:(", hex(character2), "),isMaskEffective:(", isMaskEffective,"),range:[", beginIndex, ", ", endIndex, ")");
    23932420
    2394                             JumpList matched;
    23952421                            auto loopHead = label();
    23962422                            readCharacter(m_checkedOffset - endIndex + 1, regT0);
    … …  
    24002426                            if (mapCount == 2)
    24012427                                matched.append(branch32(Equal, regT0, TrustedImm32(character2)));
    2402                             op.m_jumps.append(jumpIfNoAvailableInput(endIndex - beginIndex));
     2428                            outOfLengthFailure.append(jumpIfNoAvailableInput(endIndex - beginIndex));
    24032429                            jump().linkTo(loopHead, this);
    2404                             matched.link(this);
    24052430                        } else {
    24062431                            const auto* pointer = getBoyerMooreBitmap(map);
    … …  
    24172442                            load64(BaseIndex(regT1, regT2, TimesEight), regT2);
    24182443                            urshift64(regT0, regT2); // We can ignore upper bits and only lower 6bits are effective.
    2419                             auto matched = branchTest64(NonZero, regT2, TrustedImm32(1));
     2444                            matched.append(branchTest64(NonZero, regT2, TrustedImm32(1)));
    24202445#elif CPU(X86_64)
    24212446                            static_assert(sizeof(BoyerMooreBitmap::Map::WordType) == sizeof(uint64_t));
    … …  
    24262451                            and32(TrustedImm32(1), regT2);
    24272452                            load64(BaseIndex(regT1, regT2, TimesEight), regT2);
    2428                             auto matched = branchTestBit64(NonZero, regT2, regT0); // We can ignore upper bits since modulo-64 is performed.
     2453                            matched.append(branchTestBit64(NonZero, regT2, regT0)); // We can ignore upper bits since modulo-64 is performed.
    24292454#else
    24302455                            static_assert(sizeof(BoyerMooreBitmap::Map::WordType) == sizeof(uint32_t));
    … …  
    24362461                            load32(BaseIndex(regT1, regT2, TimesFour), regT2);
    24372462                            urshift32(regT0, regT2); // We can ignore upper bits and only lower 5bits are effective.
    2438                             auto matched = branchTest32(NonZero, regT2, TrustedImm32(1));
    2439 #endif
    2440                             op.m_jumps.append(jumpIfNoAvailableInput(endIndex - beginIndex));
     2463                            matched.append(branchTest32(NonZero, regT2, TrustedImm32(1)));
     2464#endif
     2465                            outOfLengthFailure.append(jumpIfNoAvailableInput(endIndex - beginIndex));
    24412466                            jump().linkTo(loopHead, this);
    2442                             matched.link(this);
    24432467                        }
    24442468
    24452469                        // If the pattern size is not fixed, then store the start index for use if we match.
     2470                        // This is used for adjusting match-start when we failed to find the start with BoyerMoore search.
     2471                        if (!m_pattern.m_body->m_hasFixedSize) {
     2472                            outOfLengthFailure.link(this);
     2473                            if (alternative->m_minimumSize) {
     2474                                move(index, regT0);
     2475                                sub32(Imm32(alternative->m_minimumSize), regT0);
     2476                                setMatchStart(regT0);
     2477                            } else
     2478                                setMatchStart(index);
     2479                            op.m_jumps.append(jump());
     2480                        } else
     2481                            op.m_jumps.append(outOfLengthFailure);
     2482
     2483                        matched.link(this);
     2484                        // If the pattern size is not fixed, then store the start index for use if we match.
     2485                        // This is used for adjusting match-start when we start pattern matching with the updated index
     2486                        // by BoyerMoore search.
    24462487                        if (!m_pattern.m_body->m_hasFixedSize) {
    24472488                            if (alternative->m_minimumSize) {
    … …  
    38433884        // FIXME: Support unicode flag.
    38443885        // https://bugs.webkit.org/show_bug.cgi?id=228611
    3845         if (disjunction->m_minimumSize && disjunction->m_hasFixedSize && !m_pattern.sticky() && !m_pattern.unicode()) {
     3886        if (disjunction->m_minimumSize && !m_pattern.sticky() && !m_pattern.unicode()) {
    38463887            auto bmInfo = BoyerMooreInfo::create(std::min<unsigned>(disjunction->m_minimumSize, BoyerMooreInfo::maxLength));
    38473888            if (collectBoyerMooreInfo(disjunction, currentAlternativeIndex, bmInfo.get())) {
    … …  
    38913932        // that and construct fast searching with long stride.
    38923933
    3893         ASSERT(disjunction->m_hasFixedSize); // We only support fixed-sized lookahead for BoyerMoore search.
    38943934        ASSERT(disjunction->m_minimumSize);
    38953935
    38963936        // FIXME: Support nested disjunctions (e.g. /(?:abc|def|g(?:hi|jk))/).
    38973937        // https://bugs.webkit.org/show_bug.cgi?id=228614
    3898         // FIXME: Support character-class (e.g. /[\d]test/).
    3899         // https://bugs.webkit.org/show_bug.cgi?id=228613
    39003938        // FIXME: Support non-fixed-sized lookahead (e.g. /.*abc/ and extract "abc" sequence).
    39013939        // https://bugs.webkit.org/show_bug.cgi?id=228612
    … …  
    39103948                case PatternTerm::Type::AssertionEOL:
    39113949                case PatternTerm::Type::AssertionWordBoundary:
    3912                 case PatternTerm::Type::CharacterClass:
    39133950                case PatternTerm::Type::BackReference:
    39143951                case PatternTerm::Type::ForwardReference:
    … …  
    39163953                case PatternTerm::Type::ParentheticalAssertion:
    39173954                case PatternTerm::Type::DotStarEnclosure:
    3918                     return false;
     3955                    break;
     3956                case PatternTerm::Type::CharacterClass: {
     3957                    if (term.quantityType != QuantifierType::FixedCount || term.quantityMaxCount != 1)
     3958                        break;
     3959                    if (term.inputPosition != index)
     3960                        break;
     3961                    auto& characterClass = *term.characterClass;
     3962                    if (term.invert() || characterClass.m_anyCharacter) {
     3963                        bmInfo.setAll(cursor);
     3964                        ++cursor;
     3965                        continue;
     3966                    }
     3967                    if (characterClass.m_table) {
     3968                        bmInfo.setAll(cursor);
     3969                        ++cursor;
     3970                        continue;
     3971                    }
     3972                    if (!characterClass.m_rangesUnicode.isEmpty())
     3973                        bmInfo.addRanges(cursor, characterClass.m_rangesUnicode);
     3974                    if (!characterClass.m_matchesUnicode.isEmpty())
     3975                        bmInfo.addCharacters(cursor, characterClass.m_matchesUnicode);
     3976                    if (!characterClass.m_ranges.isEmpty())
     3977                        bmInfo.addRanges(cursor, characterClass.m_ranges);
     3978                    if (!characterClass.m_matches.isEmpty())
     3979                        bmInfo.addCharacters(cursor, characterClass.m_matches);
     3980                    ++cursor;
     3981                    continue;
     3982                }
    39193983                case PatternTerm::Type::PatternCharacter: {
    39203984                    if (term.quantityType != QuantifierType::FixedCount || term.quantityMaxCount != 1)
    3921                         return false;
     3985                        break;
    39223986                    if (term.inputPosition != index)
    3923                         return false;
     3987                        break;
    39243988                    if (U16_LENGTH(term.patternCharacter) != 1 && m_decodeSurrogatePairs)
    3925                         return false;
     3989                        break;
    39263990                    // For case-insesitive compares, non-ascii characters that have different
    39273991                    // upper & lower case representations are already converted to a character class.
    … …  
    39333997                        bmInfo.set(cursor, term.patternCharacter);
    39343998                    ++cursor;
    3935                     break;
     3999                    continue;
    39364000                }
    39374001                }
    3938             }
    3939         }
    3940         dataLogLnIf(YarrJITInternal::verbose, "Characters collected");
    3941         return true;
     4002                dataLogLnIf(YarrJITInternal::verbose, "Shortening to ", cursor);
     4003                bmInfo.shortenLength(cursor);
     4004                break;
     4005            }
     4006        }
     4007        return bmInfo.length();
    39424008    }
    39434009
  • trunk/Source/JavaScriptCore/yarr/YarrJIT.h

    r280544 r280570  
    6262
    6363class BoyerMooreBitmap {
     64    WTF_MAKE_NONCOPYABLE(BoyerMooreBitmap);
    6465    WTF_MAKE_FAST_ALLOCATED(BoyerMooreBitmap);
    6566public:
    … …  
    7677    void add(UChar32 character)
    7778    {
     79        if (isAllSet())
     80            return;
    7881        unsigned position = character & mapMask;
    7982        if (position != static_cast<unsigned>(character))
    … …  
    8487        }
    8588    }
     89
     90    void addCharacters(const Vector<UChar32>& characters)
     91    {
     92        if (isAllSet())
     93            return;
     94        if (characters.size() >= mapSize) {
     95            setAll();
     96            return;
     97        }
     98        for (UChar character : characters)
     99            add(character);
     100    }
     101
     102    void addRanges(const Vector<CharacterRange>& ranges)
     103    {
     104        if (ranges.size() >= mapSize) {
     105            setAll();
     106            return;
     107        }
     108        for (CharacterRange range : ranges) {
     109            if (isAllSet())
     110                return;
     111            if (static_cast<unsigned>(range.end - range.begin + 1) >= mapSize) {
     112                setAll();
     113                return;
     114            }
     115            for (UChar32 character = range.begin; character <= range.end; ++character)
     116                add(character);
     117        }
     118    }
     119
     120    void setAll()
     121    {
     122        m_count = mapSize;
     123    }
     124
     125    bool isAllSet() const { return m_count == mapSize; }
    86126
    87127private:
Note: See TracChangeset for help on using the changeset viewer.