diff --git a/js/src/irregexp/imported/regexp-bytecode-generator.h b/js/src/irregexp/imported/regexp-bytecode-generator.h --- a/js/src/irregexp/imported/regexp-bytecode-generator.h +++ b/js/src/irregexp/imported/regexp-bytecode-generator.h @@ -81,10 +81,13 @@ bool CheckCharacterNotInRangeArray(const ZoneList* ranges, Label* on_not_in_range) override { return false; } void CheckBitInTable(Handle table, Label* on_bit_set) override; + void SkipUntilBitInTable(int cp_offset, Handle table, + Handle nibble_table, + int advance_by) override; void CheckNotBackReference(int start_reg, bool read_backward, Label* on_no_match) override; void CheckNotBackReferenceIgnoreCase(int start_reg, bool read_backward, bool unicode, Label* on_no_match) override; @@ -103,10 +106,11 @@ inline void Emit32(uint32_t x); inline void Emit16(uint32_t x); inline void Emit8(uint32_t x); inline void Emit(uint32_t bc, uint32_t arg); inline void Emit(uint32_t bc, int32_t arg); + void EmitSkipTable(Handle table); // Bytecode buffer. int length(); void Copy(uint8_t* a); // The buffer into which code and relocation info are generated. diff --git a/js/src/irregexp/imported/regexp-bytecode-generator.cc b/js/src/irregexp/imported/regexp-bytecode-generator.cc --- a/js/src/irregexp/imported/regexp-bytecode-generator.cc +++ b/js/src/irregexp/imported/regexp-bytecode-generator.cc @@ -310,23 +310,39 @@ Emit16(from); Emit16(to); EmitOrLink(on_not_in_range); } -void RegExpBytecodeGenerator::CheckBitInTable(Handle table, - Label* on_bit_set) { - Emit(BC_CHECK_BIT_IN_TABLE, 0); - EmitOrLink(on_bit_set); +void RegExpBytecodeGenerator::EmitSkipTable(Handle table) { for (int i = 0; i < kTableSize; i += kBitsPerByte) { int byte = 0; for (int j = 0; j < kBitsPerByte; j++) { if (table->get(i + j) != 0) byte |= 1 << j; } Emit8(byte); } } +void RegExpBytecodeGenerator::CheckBitInTable(Handle table, + Label* on_bit_set) { + Emit(BC_CHECK_BIT_IN_TABLE, 0); + EmitOrLink(on_bit_set); + EmitSkipTable(table); +} + +void RegExpBytecodeGenerator::SkipUntilBitInTable( + int cp_offset, Handle table, Handle nibble_table, + int advance_by) { + Label cont; + Emit(BC_SKIP_UNTIL_BIT_IN_TABLE, cp_offset); + Emit32(advance_by); + EmitSkipTable(table); + EmitOrLink(&cont); // goto_when_match + EmitOrLink(&cont); // goto_on_failure + Bind(&cont); +} + void RegExpBytecodeGenerator::CheckNotBackReference(int start_reg, bool read_backward, Label* on_not_equal) { DCHECK_LE(0, start_reg); DCHECK_GE(kMaxRegister, start_reg); diff --git a/js/src/irregexp/imported/regexp-compiler-tonode.cc b/js/src/irregexp/imported/regexp-compiler-tonode.cc --- a/js/src/irregexp/imported/regexp-compiler-tonode.cc +++ b/js/src/irregexp/imported/regexp-compiler-tonode.cc @@ -18,11 +18,10 @@ using namespace regexp_compiler_constants; // NOLINT(build/namespaces) constexpr base::uc32 kMaxCodePoint = 0x10ffff; constexpr int kMaxUtf16CodeUnit = 0xffff; constexpr uint32_t kMaxUtf16CodeUnitU = 0xffff; -constexpr int32_t kMaxOneByteCharCode = unibrow::Latin1::kMaxChar; // ------------------------------------------------------------------- // Tree to graph conversion RegExpNode* RegExpAtom::ToNode(RegExpCompiler* compiler, @@ -416,10 +415,11 @@ } } // namespace // static +// Only for /ui and /vi, not for /i regexps. void CharacterRange::AddUnicodeCaseEquivalents(ZoneList* ranges, Zone* zone) { #ifdef V8_INTL_SUPPORT DCHECK(IsCanonical(ranges)); @@ -1085,20 +1085,21 @@ zone->New>(3, zone); CharacterRange::AddClassEscape(StandardCharacterSet::kLineTerminator, newline_ranges, false, zone); RegExpClassRanges* newline_atom = zone->New(StandardCharacterSet::kLineTerminator); + ActionNode* submatch_success = ActionNode::PositiveSubmatchSuccess( + stack_pointer_register, position_register, + 0, // No captures inside. + -1, // Ignored if no captures. + on_success); TextNode* newline_matcher = - zone->New(newline_atom, false, - ActionNode::PositiveSubmatchSuccess( - stack_pointer_register, position_register, - 0, // No captures inside. - -1, // Ignored if no captures. - on_success)); + zone->New(newline_atom, false, submatch_success); // Create an end-of-input matcher. RegExpNode* end_of_line = ActionNode::BeginPositiveSubmatch( - stack_pointer_register, position_register, newline_matcher); + stack_pointer_register, position_register, newline_matcher, + submatch_success); // Add the two alternatives to the ChoiceNode. GuardedAlternative eol_alternative(end_of_line); result->AddAlternative(eol_alternative); GuardedAlternative end_alternative(AssertionNode::AtEnd(on_success)); result->AddAlternative(end_alternative); @@ -1186,18 +1187,19 @@ } } RegExpNode* RegExpLookaround::Builder::ForMatch(RegExpNode* match) { if (is_positive_) { - return ActionNode::BeginPositiveSubmatch(stack_pointer_register_, - position_register_, match); + ActionNode* on_match_success = on_match_success_->AsActionNode(); + return ActionNode::BeginPositiveSubmatch( + stack_pointer_register_, position_register_, match, on_match_success); } else { Zone* zone = on_success_->zone(); // We use a ChoiceNode to represent the negative lookaround. The first // alternative is the negative match. On success, the end node backtracks. // On failure, the second alternative is tried and leads to success. - // NegativeLookaheadChoiceNode is a special ChoiceNode that ignores the + // NegativeLookaroundChoiceNode is a special ChoiceNode that ignores the // first exit when calculating quick checks. ChoiceNode* choice_node = zone->New( GuardedAlternative(match), GuardedAlternative(on_success_), zone); return ActionNode::BeginNegativeSubmatch(stack_pointer_register_, position_register_, choice_node); @@ -1442,10 +1444,11 @@ break; } } // static +// Only for /i, not for /ui or /vi. void CharacterRange::AddCaseEquivalents(Isolate* isolate, Zone* zone, ZoneList* ranges, bool is_one_byte) { CharacterRange::Canonicalize(ranges); int range_count = ranges->length(); @@ -1457,12 +1460,12 @@ if (from > kMaxUtf16CodeUnit) continue; base::uc32 to = std::min({range.to(), kMaxUtf16CodeUnitU}); // Nothing to be done for surrogates. if (from >= kLeadSurrogateStart && to <= kTrailSurrogateEnd) continue; if (is_one_byte && !RangeContainsLatin1Equivalents(range)) { - if (from > kMaxOneByteCharCode) continue; - if (to > kMaxOneByteCharCode) to = kMaxOneByteCharCode; + if (from > String::kMaxOneByteCharCode) continue; + if (to > String::kMaxOneByteCharCode) to = String::kMaxOneByteCharCode; } others.add(from, to); } // Compute the set of additional characters that should be added, @@ -1500,12 +1503,12 @@ if (bottom > kMaxUtf16CodeUnit) continue; base::uc32 top = std::min({range.to(), kMaxUtf16CodeUnitU}); // Nothing to be done for surrogates. if (bottom >= kLeadSurrogateStart && top <= kTrailSurrogateEnd) continue; if (is_one_byte && !RangeContainsLatin1Equivalents(range)) { - if (bottom > kMaxOneByteCharCode) continue; - if (top > kMaxOneByteCharCode) top = kMaxOneByteCharCode; + if (bottom > String::kMaxOneByteCharCode) continue; + if (top > String::kMaxOneByteCharCode) top = String::kMaxOneByteCharCode; } unibrow::uchar chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; if (top == bottom) { // If this is a singleton we just expand the one character. int length = isolate->jsregexp_uncanonicalize()->get(bottom, '\0', chars); diff --git a/js/src/irregexp/imported/regexp-compiler.h b/js/src/irregexp/imported/regexp-compiler.h --- a/js/src/irregexp/imported/regexp-compiler.h +++ b/js/src/irregexp/imported/regexp-compiler.h @@ -203,12 +203,14 @@ RegExpCompiler* compiler_; // 0xff for Latin1, 0xffff for UTF-16. int max_char_; ZoneList* bitmaps_; - int GetSkipTable(int min_lookahead, int max_lookahead, - DirectHandle boolean_skip_table); + int GetSkipTable( + int min_lookahead, int max_lookahead, + DirectHandle boolean_skip_table, + DirectHandle nibble_table = DirectHandle{}); bool FindWorthwhileInterval(int* from, int* to); int FindBestInterval(int max_number_of_chars, int old_biggest_points, int* from, int* to); }; diff --git a/js/src/irregexp/imported/regexp-compiler.cc b/js/src/irregexp/imported/regexp-compiler.cc --- a/js/src/irregexp/imported/regexp-compiler.cc +++ b/js/src/irregexp/imported/regexp-compiler.cc @@ -664,15 +664,17 @@ result->data_.u_clear_captures.range_to = range.to(); return result; } ActionNode* ActionNode::BeginPositiveSubmatch(int stack_reg, int position_reg, - RegExpNode* on_success) { + RegExpNode* body, + ActionNode* success_node) { ActionNode* result = - on_success->zone()->New(BEGIN_POSITIVE_SUBMATCH, on_success); + body->zone()->New(BEGIN_POSITIVE_SUBMATCH, body); result->data_.u_submatch.stack_pointer_register = stack_reg; result->data_.u_submatch.current_position_register = position_reg; + result->data_.u_submatch.success_node = success_node; return result; } ActionNode* ActionNode::BeginNegativeSubmatch(int stack_reg, int position_reg, RegExpNode* on_success) { @@ -750,41 +752,66 @@ return true; } #endif // DEBUG // Returns the number of characters in the equivalence class, omitting those -// that cannot occur in the source string because it is Latin1. +// that cannot occur in the source string because it is Latin1. This is called +// both for unicode modes /ui and /vi, and also for legacy case independent +// mode /i. In the case of Unicode modes we handled surrogate pair expansions +// earlier so at this point it's all about single-code-unit expansions. int GetCaseIndependentLetters(Isolate* isolate, base::uc16 character, - bool one_byte_subject, unibrow::uchar* letters, + RegExpCompiler* compiler, unibrow::uchar* letters, int letter_length) { + bool one_byte_subject = compiler->one_byte(); + bool unicode = IsEitherUnicode(compiler->flags()); + static const base::uc16 kMaxAscii = 0x7f; + if (!unicode && character <= kMaxAscii) { + // Fast case for common characters. + base::uc16 upper = character & ~0x20; + if ('A' <= upper && upper <= 'Z') { + letters[0] = upper; + letters[1] = upper | 0x20; + return 2; + } + letters[0] = character; + return 1; + } #ifdef V8_INTL_SUPPORT - if (RegExpCaseFolding::IgnoreSet().contains(character)) { + + if (!unicode && RegExpCaseFolding::IgnoreSet().contains(character)) { + if (one_byte_subject && character > String::kMaxOneByteCharCode) { + // This function promises not to return a character that is impossible + // for the subject encoding. + return 0; + } letters[0] = character; DCHECK(ContainsOnlyUtf16CodeUnits(letters, 1)); return 1; } bool in_special_add_set = RegExpCaseFolding::SpecialAddSet().contains(character); icu::UnicodeSet set; set.add(character); - set = set.closeOver(USET_CASE_INSENSITIVE); + set = set.closeOver(unicode ? USET_SIMPLE_CASE_INSENSITIVE + : USET_CASE_INSENSITIVE); UChar32 canon = 0; - if (in_special_add_set) { + if (in_special_add_set && !unicode) { canon = RegExpCaseFolding::Canonicalize(character); } int32_t range_count = set.getRangeCount(); int items = 0; for (int32_t i = 0; i < range_count; i++) { UChar32 start = set.getRangeStart(i); UChar32 end = set.getRangeEnd(i); CHECK(end - start + items <= letter_length); for (UChar32 cu = start; cu <= end; cu++) { - if (one_byte_subject && cu > String::kMaxOneByteCharCode) break; - if (in_special_add_set && RegExpCaseFolding::Canonicalize(cu) != canon) { + if (one_byte_subject && cu > String::kMaxOneByteCharCode) continue; + if (!unicode && in_special_add_set && + RegExpCaseFolding::Canonicalize(cu) != canon) { continue; } letters[items++] = static_cast(cu); } } @@ -834,29 +861,30 @@ base::uc16 c, Label* on_failure, int cp_offset, bool check, bool preloaded) { RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); bool one_byte = compiler->one_byte(); unibrow::uchar chars[4]; - int length = GetCaseIndependentLetters(isolate, c, one_byte, chars, 4); + int length = GetCaseIndependentLetters(isolate, c, compiler, chars, 4); if (length < 1) { // This can't match. Must be an one-byte subject and a non-one-byte // character. We do not need to do anything since the one-byte pass // already handled this. + CHECK(one_byte); return false; // Bounds not checked. } bool checked = false; // We handle the length > 1 case in a later pass. if (length == 1) { - if (one_byte && c > String::kMaxOneByteCharCodeU) { - // Can't match - see above. - return false; // Bounds not checked. - } + // GetCaseIndependentLetters promises not to return characters that can't + // match because of the subject encoding. This case is already handled by + // the one-byte pass. + CHECK_IMPLIES(one_byte, chars[0] <= String::kMaxOneByteCharCodeU); if (!preloaded) { macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); checked = check; } - macro_assembler->CheckNotCharacter(c, on_failure); + macro_assembler->CheckNotCharacter(chars[0], on_failure); } return checked; } bool ShortCutEmitCharacterPair(RegExpMacroAssembler* macro_assembler, @@ -894,11 +922,12 @@ base::uc16 c, Label* on_failure, int cp_offset, bool check, bool preloaded) { RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); bool one_byte = compiler->one_byte(); unibrow::uchar chars[4]; - int length = GetCaseIndependentLetters(isolate, c, one_byte, chars, 4); + int length = GetCaseIndependentLetters(isolate, c, compiler, chars, 4); + // The 0 and 1 case are handled by earlier passes. if (length <= 1) return false; // We may not need to check against the end of the input string // if this character lies before a character that matched. if (!preloaded) { macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); @@ -1374,15 +1403,19 @@ // recursion budget limitation for filling in BMInfo. Therefore we reset the // flags manually to the previous state after recursing. old_flags = bm->compiler()->flags(); bm->compiler()->set_flags(flags()); } - if (action_type_ == POSITIVE_SUBMATCH_SUCCESS) { - // Anything may follow a positive submatch success, thus we need to accept - // all characters from this position onwards. - bm->SetRest(offset); - } else { + if (action_type_ == BEGIN_POSITIVE_SUBMATCH) { + // We use the node after the lookaround to fill in the eats_at_least info + // so we have to use the same node to fill in the Boyer-Moore info. + success_node()->on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, + not_at_start); + } else if (action_type_ != POSITIVE_SUBMATCH_SUCCESS) { + // We don't use the node after a positive submatch success because it + // rewinds the position. Since we returned 0 as the eats_at_least value for + // this node, we don't need to fill in any data. on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); } SaveBMInfo(bm, not_at_start, offset); if (old_flags.has_value()) { bm->compiler()->set_flags(*old_flags); @@ -1393,11 +1426,19 @@ RegExpCompiler* compiler, int filled_in, bool not_at_start) { if (action_type_ == SET_REGISTER_FOR_LOOP) { on_success()->GetQuickCheckDetailsFromLoopEntry(details, compiler, filled_in, not_at_start); - } else { + } else if (action_type_ == BEGIN_POSITIVE_SUBMATCH) { + // We use the node after the lookaround to fill in the eats_at_least info + // so we have to use the same node to fill in the QuickCheck info. + success_node()->on_success()->GetQuickCheckDetails(details, compiler, + filled_in, not_at_start); + } else if (action_type() != POSITIVE_SUBMATCH_SUCCESS) { + // We don't use the node after a positive submatch success because it + // rewinds the position. Since we returned 0 as the eats_at_least value + // for this node, we don't need to fill in any data. if (action_type() == MODIFY_FLAGS) { compiler->set_flags(flags()); } on_success()->GetQuickCheckDetails(details, compiler, filled_in, not_at_start); @@ -1617,12 +1658,12 @@ QuickCheckDetails::Position* pos = details->positions(characters_filled_in); base::uc16 c = quarks[i]; if (IsIgnoreCase(compiler->flags())) { unibrow::uchar chars[4]; - int length = GetCaseIndependentLetters( - isolate, c, compiler->one_byte(), chars, 4); + int length = + GetCaseIndependentLetters(isolate, c, compiler, chars, 4); if (length == 0) { // This can happen because all case variants are non-Latin1, but we // know the input is Latin1. details->set_cannot_match(); pos->determines_perfectly = false; @@ -1847,20 +1888,21 @@ private: LoopChoiceNode* node_; }; -RegExpNode* SeqRegExpNode::FilterOneByte(int depth, RegExpFlags flags) { +RegExpNode* SeqRegExpNode::FilterOneByte(int depth, RegExpCompiler* compiler) { if (info()->replacement_calculated) return replacement(); if (depth < 0) return this; DCHECK(!info()->visited); VisitMarker marker(info()); - return FilterSuccessor(depth - 1, flags); + return FilterSuccessor(depth - 1, compiler); } -RegExpNode* SeqRegExpNode::FilterSuccessor(int depth, RegExpFlags flags) { - RegExpNode* next = on_success_->FilterOneByte(depth - 1, flags); +RegExpNode* SeqRegExpNode::FilterSuccessor(int depth, + RegExpCompiler* compiler) { + RegExpNode* next = on_success_->FilterOneByte(depth - 1, compiler); if (next == nullptr) return set_replacement(nullptr); on_success_ = next; return set_replacement(this); } @@ -1881,11 +1923,12 @@ return false; } } // namespace -RegExpNode* TextNode::FilterOneByte(int depth, RegExpFlags flags) { +RegExpNode* TextNode::FilterOneByte(int depth, RegExpCompiler* compiler) { + RegExpFlags flags = compiler->flags(); if (info()->replacement_calculated) return replacement(); if (depth < 0) return this; DCHECK(!info()->visited); VisitMarker marker(info()); int element_count = elements()->length(); @@ -1893,67 +1936,77 @@ TextElement elm = elements()->at(i); if (elm.text_type() == TextElement::ATOM) { base::Vector quarks = elm.atom()->data(); for (int j = 0; j < quarks.length(); j++) { base::uc16 c = quarks[j]; - if (IsIgnoreCase(flags)) { - c = unibrow::Latin1::TryConvertToLatin1(c); + if (!IsIgnoreCase(flags)) { + if (c > String::kMaxOneByteCharCode) return set_replacement(nullptr); + } else { + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters(compiler->isolate(), c, + compiler, chars, 4); + if (length == 0 || chars[0] > String::kMaxOneByteCharCode) { + return set_replacement(nullptr); + } } - if (c > unibrow::Latin1::kMaxChar) return set_replacement(nullptr); - // Replace quark in case we converted to Latin-1. - base::uc16* writable_quarks = const_cast(quarks.begin()); - writable_quarks[j] = c; } } else { + // A character class can also be impossible to match in one-byte mode. DCHECK(elm.text_type() == TextElement::CLASS_RANGES); RegExpClassRanges* cr = elm.class_ranges(); ZoneList* ranges = cr->ranges(zone()); CharacterRange::Canonicalize(ranges); // Now they are in order so we only need to look at the first. + // If we are in non-Unicode case independent mode then we need + // to be a bit careful here, because the character classes have + // not been case-desugared yet, but there are characters and ranges + // that can become Latin-1 when case is considered. int range_count = ranges->length(); if (cr->is_negated()) { if (range_count != 0 && ranges->at(0).from() == 0 && ranges->at(0).to() >= String::kMaxOneByteCharCode) { - // This will be handled in a later filter. - if (IsIgnoreCase(flags) && RangesContainLatin1Equivalents(ranges)) { - continue; + bool case_complications = !IsEitherUnicode(flags) && + IsIgnoreCase(flags) && + RangesContainLatin1Equivalents(ranges); + if (!case_complications) { + return set_replacement(nullptr); } - return set_replacement(nullptr); } } else { if (range_count == 0 || ranges->at(0).from() > String::kMaxOneByteCharCode) { - // This will be handled in a later filter. - if (IsIgnoreCase(flags) && RangesContainLatin1Equivalents(ranges)) { - continue; + bool case_complications = !IsEitherUnicode(flags) && + IsIgnoreCase(flags) && + RangesContainLatin1Equivalents(ranges); + if (!case_complications) { + return set_replacement(nullptr); } - return set_replacement(nullptr); } } } } - return FilterSuccessor(depth - 1, flags); + return FilterSuccessor(depth - 1, compiler); } -RegExpNode* LoopChoiceNode::FilterOneByte(int depth, RegExpFlags flags) { +RegExpNode* LoopChoiceNode::FilterOneByte(int depth, RegExpCompiler* compiler) { if (info()->replacement_calculated) return replacement(); if (depth < 0) return this; if (info()->visited) return this; { VisitMarker marker(info()); RegExpNode* continue_replacement = - continue_node_->FilterOneByte(depth - 1, flags); + continue_node_->FilterOneByte(depth - 1, compiler); // If we can't continue after the loop then there is no sense in doing the // loop. if (continue_replacement == nullptr) return set_replacement(nullptr); } - return ChoiceNode::FilterOneByte(depth - 1, flags); + return ChoiceNode::FilterOneByte(depth - 1, compiler); } -RegExpNode* ChoiceNode::FilterOneByte(int depth, RegExpFlags flags) { +RegExpNode* ChoiceNode::FilterOneByte(int depth, RegExpCompiler* compiler) { if (info()->replacement_calculated) return replacement(); if (depth < 0) return this; if (info()->visited) return this; VisitMarker marker(info()); int choice_count = alternatives_->length(); @@ -1970,11 +2023,11 @@ int surviving = 0; RegExpNode* survivor = nullptr; for (int i = 0; i < choice_count; i++) { GuardedAlternative alternative = alternatives_->at(i); RegExpNode* replacement = - alternative.node()->FilterOneByte(depth - 1, flags); + alternative.node()->FilterOneByte(depth - 1, compiler); DCHECK(replacement != this); // No missing EMPTY_MATCH_CHECK. if (replacement != nullptr) { alternatives_->at(i).set_node(replacement); surviving++; survivor = replacement; @@ -1990,35 +2043,35 @@ // alternatives list. ZoneList* new_alternatives = zone()->New>(surviving, zone()); for (int i = 0; i < choice_count; i++) { RegExpNode* replacement = - alternatives_->at(i).node()->FilterOneByte(depth - 1, flags); + alternatives_->at(i).node()->FilterOneByte(depth - 1, compiler); if (replacement != nullptr) { alternatives_->at(i).set_node(replacement); new_alternatives->Add(alternatives_->at(i), zone()); } } alternatives_ = new_alternatives; return this; } -RegExpNode* NegativeLookaroundChoiceNode::FilterOneByte(int depth, - RegExpFlags flags) { +RegExpNode* NegativeLookaroundChoiceNode::FilterOneByte( + int depth, RegExpCompiler* compiler) { if (info()->replacement_calculated) return replacement(); if (depth < 0) return this; if (info()->visited) return this; VisitMarker marker(info()); // Alternative 0 is the negative lookahead, alternative 1 is what comes // afterwards. RegExpNode* node = continue_node(); - RegExpNode* replacement = node->FilterOneByte(depth - 1, flags); + RegExpNode* replacement = node->FilterOneByte(depth - 1, compiler); if (replacement == nullptr) return set_replacement(nullptr); alternatives_->at(kContinueIndex).set_node(replacement); RegExpNode* neg_node = lookaround_node(); - RegExpNode* neg_replacement = neg_node->FilterOneByte(depth - 1, flags); + RegExpNode* neg_replacement = neg_node->FilterOneByte(depth - 1, compiler); // If the negative lookahead is always going to fail then // we don't need to check it. if (neg_replacement == nullptr) return set_replacement(replacement); alternatives_->at(kLookaroundIndex).set_node(neg_replacement); return set_replacement(this); @@ -2364,27 +2417,37 @@ base::Vector quarks = elm.atom()->data(); for (int j = preloaded ? 0 : quarks.length() - 1; j >= 0; j--) { if (first_element_checked && i == 0 && j == 0) continue; if (DeterminedAlready(quick_check, elm.cp_offset() + j)) continue; base::uc16 quark = quarks[j]; - if (IsIgnoreCase(compiler->flags())) { - // Everywhere else we assume that a non-Latin-1 character cannot match - // a Latin-1 character. Avoid the cases where this is assumption is - // invalid by using the Latin1 equivalent instead. - quark = unibrow::Latin1::TryConvertToLatin1(quark); - } bool needs_bounds_check = *checked_up_to < cp_offset + j || read_backward(); bool bounds_checked = false; switch (pass) { - case NON_LATIN1_MATCH: - DCHECK(one_byte); - if (quark > String::kMaxOneByteCharCode) { - assembler->GoTo(backtrack); - return; + case NON_LATIN1_MATCH: { + DCHECK(one_byte); // This pass is only done in one-byte mode. + if (IsIgnoreCase(compiler->flags())) { + // We are compiling for a one-byte subject, case independent mode. + // We have to check whether any of the case alternatives are in + // the one-byte range. + unibrow::uchar chars[4]; + // Only returns characters that are in the one-byte range. + int length = + GetCaseIndependentLetters(isolate, quark, compiler, chars, 4); + if (length == 0) { + assembler->GoTo(backtrack); + return; + } + } else { + // Case-dependent mode. + if (quark > String::kMaxOneByteCharCode) { + assembler->GoTo(backtrack); + return; + } } break; + } case NON_LETTER_CHARACTER_MATCH: bounds_checked = EmitAtomNonLetter(isolate, compiler, quark, backtrack, cp_offset + j, needs_bounds_check, preloaded); break; @@ -2545,10 +2608,12 @@ void TextNode::MakeCaseIndependent(Isolate* isolate, bool is_one_byte, RegExpFlags flags) { if (!IsIgnoreCase(flags)) return; #ifdef V8_INTL_SUPPORT + // This is done in an earlier step when generating the nodes from the AST + // because we may have to split up into separate nodes. if (NeedsUnicodeCaseEquivalents(flags)) return; #endif int element_count = elements()->length(); for (int i = 0; i < element_count; i++) { @@ -2601,12 +2666,11 @@ int node_length = node->GreedyLoopTextLength(); if (node_length == kNodeIsTooComplexForGreedyLoops) { return kNodeIsTooComplexForGreedyLoops; } length += node_length; - SeqRegExpNode* seq_node = static_cast(node); - node = seq_node->on_success(); + node = node->AsSeqRegExpNode()->on_success(); } if (read_backward()) { length = -length; } // Check that we can jump by the whole text length. If not, return sentinel @@ -2884,27 +2948,44 @@ // Take all the characters that will not prevent a successful match if they // occur in the subject string in the range between min_lookahead and // max_lookahead (inclusive) measured from the current position. If the // character at max_lookahead offset is not one of these characters, then we // can safely skip forwards by the number of characters in the range. +// nibble_table is only used for SIMD variants and encodes the same information +// as boolean_skip_table but in only 128 bits. It contains 16 bytes where the +// index into the table represent low nibbles of a character, and the stored +// byte is a bitset representing matching high nibbles. E.g. to store the +// character 'b' (0x62) in the nibble table, we set the 6th bit in row 2. int BoyerMooreLookahead::GetSkipTable( int min_lookahead, int max_lookahead, - DirectHandle boolean_skip_table) { + DirectHandle boolean_skip_table, + DirectHandle nibble_table) { const int kSkipArrayEntry = 0; const int kDontSkipArrayEntry = 1; std::memset(boolean_skip_table->begin(), kSkipArrayEntry, boolean_skip_table->length()); + const bool fill_nibble_table = !nibble_table.is_null(); + if (fill_nibble_table) { + std::memset(nibble_table->begin(), 0, nibble_table->length()); + } for (int i = max_lookahead; i >= min_lookahead; i--) { BoyerMoorePositionInfo::Bitset bitset = bitmaps_->at(i)->raw_bitset(); // Iterate only over set bits. int j; while ((j = BitsetFirstSetBit(bitset)) != -1) { DCHECK(bitset[j]); // Sanity check. boolean_skip_table->set(j, kDontSkipArrayEntry); + if (fill_nibble_table) { + int lo_nibble = j & 0x0f; + int hi_nibble = (j >> 4) & 0x07; + int row = nibble_table->get(lo_nibble); + row |= 1 << hi_nibble; + nibble_table->set(lo_nibble, row); + } bitset.reset(j); } } const int skip = max_lookahead + 1 - min_lookahead; @@ -2948,10 +3029,11 @@ // The mask-compare can probably handle this better. return; } if (found_single_character) { + // TODO(pthier): Add vectorized version. Label cont, again; masm->Bind(&again); masm->LoadCurrentCharacter(max_lookahead, &cont, true); if (max_char_ > kSize) { masm->CheckCharacterAfterAnd(single_character, @@ -2966,21 +3048,23 @@ } Factory* factory = masm->isolate()->factory(); Handle boolean_skip_table = factory->NewByteArray(kSize, AllocationType::kOld); - int skip_distance = - GetSkipTable(min_lookahead, max_lookahead, boolean_skip_table); + Handle nibble_table; + const int skip_distance = max_lookahead + 1 - min_lookahead; + if (masm->SkipUntilBitInTableUseSimd(skip_distance)) { + // The current implementation is tailored specifically for 128-bit tables. + static_assert(kSize == 128); + nibble_table = + factory->NewByteArray(kSize / kBitsPerByte, AllocationType::kOld); + } + GetSkipTable(min_lookahead, max_lookahead, boolean_skip_table, nibble_table); DCHECK_NE(0, skip_distance); - Label cont, again; - masm->Bind(&again); - masm->LoadCurrentCharacter(max_lookahead, &cont, true); - masm->CheckBitInTable(boolean_skip_table, &cont); - masm->AdvanceCurrentPosition(skip_distance); - masm->GoTo(&again); - masm->Bind(&cont); + masm->SkipUntilBitInTable(max_lookahead, boolean_skip_table, nibble_table, + skip_distance); } /* Code generation for choice nodes. * * We generate quick checks that do a mask and compare to eliminate a @@ -3599,20 +3683,27 @@ } } static void VisitAction(ActionNode* that) { switch (that->action_type()) { - case ActionNode::BEGIN_POSITIVE_SUBMATCH: + case ActionNode::BEGIN_POSITIVE_SUBMATCH: { + // For a begin positive submatch we propagate the eats_at_least + // data from the successor of the success node, ignoring the body of + // the lookahead, which eats nothing, since it is a zero-width + // assertion. + // TODO(chromium:42201836) This is better than discarding all + // information when there is a positive lookahead, but it loses some + // information that could be useful, since the body of the lookahead + // could tell us something about how close to the end of the string we + // are. + that->set_eats_at_least_info( + *that->success_node()->on_success()->eats_at_least_info()); + break; + } case ActionNode::POSITIVE_SUBMATCH_SUCCESS: - // We do not propagate eats_at_least data through positive lookarounds, - // because they rewind input. - // TODO(v8:11859) Potential approaches for fixing this include: - // 1. Add a dedicated choice node for positive lookaround, similar to - // NegativeLookaroundChoiceNode. - // 2. Add an eats_at_least_inside_loop field to EatsAtLeastInfo, which - // is <= eats_at_least_from_possibly_start, and use that value in - // EatsAtLeastFromLoopEntry. + // We do not propagate eats_at_least data through positive submatch + // success because it rewinds input. DCHECK(that->eats_at_least_info()->IsZero()); break; case ActionNode::SET_REGISTER_FOR_LOOP: // SET_REGISTER_FOR_LOOP indicates a loop entry point, which means the // loop body will run at least the minimum number of times before the @@ -3851,10 +3942,11 @@ } void TextNode::FillInBMInfo(Isolate* isolate, int initial_offset, int budget, BoyerMooreLookahead* bm, bool not_at_start) { if (initial_offset >= bm->length()) return; + if (read_backward()) return; int offset = initial_offset; int max_char = bm->max_char(); for (int i = 0; i < elements()->length(); i++) { if (offset >= bm->length()) { if (initial_offset == 0) set_bm_info(not_at_start, bm); @@ -3869,13 +3961,12 @@ return; } base::uc16 character = atom->data()[j]; if (IsIgnoreCase(bm->compiler()->flags())) { unibrow::uchar chars[4]; - int length = GetCaseIndependentLetters( - isolate, character, bm->max_char() == String::kMaxOneByteCharCode, - chars, 4); + int length = GetCaseIndependentLetters(isolate, character, + bm->compiler(), chars, 4); for (int k = 0; k < length; k++) { bm->Set(offset, chars[k]); } } else { if (character <= max_char) bm->Set(offset, character); @@ -3959,15 +4050,15 @@ } else { node = loop_node; } } if (is_one_byte) { - node = node->FilterOneByte(RegExpCompiler::kMaxRecursion, flags()); + node = node->FilterOneByte(RegExpCompiler::kMaxRecursion, this); // Do it again to propagate the new nodes to places where they were not // put because they had not been calculated yet. if (node != nullptr) { - node = node->FilterOneByte(RegExpCompiler::kMaxRecursion, flags()); + node = node->FilterOneByte(RegExpCompiler::kMaxRecursion, this); } } else if (IsEitherUnicode(flags()) && (IsGlobal(flags()) || IsSticky(flags()))) { node = OptionallyStepBackToLeadSurrogate(node); } diff --git a/js/src/irregexp/imported/regexp-dotprinter.cc b/js/src/irregexp/imported/regexp-dotprinter.cc --- a/js/src/irregexp/imported/regexp-dotprinter.cc +++ b/js/src/irregexp/imported/regexp-dotprinter.cc @@ -106,10 +106,11 @@ } for (int i = 0; i < that->alternatives()->length(); i++) { GuardedAlternative alt = that->alternatives()->at(i); alt.node()->Accept(this); } + PrintAttributes(that); } void DotPrinterImpl::VisitLoopChoice(LoopChoiceNode* that) { VisitChoice(that); } diff --git a/js/src/irregexp/imported/regexp-macro-assembler-tracer.h b/js/src/irregexp/imported/regexp-macro-assembler-tracer.h --- a/js/src/irregexp/imported/regexp-macro-assembler-tracer.h +++ b/js/src/irregexp/imported/regexp-macro-assembler-tracer.h @@ -50,10 +50,13 @@ bool CheckCharacterInRangeArray(const ZoneList* ranges, Label* on_in_range) override; bool CheckCharacterNotInRangeArray(const ZoneList* ranges, Label* on_not_in_range) override; void CheckBitInTable(Handle table, Label* on_bit_set) override; + void SkipUntilBitInTable(int cp_offset, Handle table, + Handle nibble_table, + int advance_by) override; void CheckPosition(int cp_offset, Label* on_outside_input) override; bool CheckSpecialClassRanges(StandardCharacterSet type, Label* on_no_match) override; void Fail() override; Handle GetCode(Handle source) override; diff --git a/js/src/irregexp/imported/regexp-macro-assembler-tracer.cc b/js/src/irregexp/imported/regexp-macro-assembler-tracer.cc --- a/js/src/irregexp/imported/regexp-macro-assembler-tracer.cc +++ b/js/src/irregexp/imported/regexp-macro-assembler-tracer.cc @@ -367,10 +367,43 @@ } PrintF(");\n"); assembler_->CheckBitInTable(table, on_bit_set); } +void RegExpMacroAssemblerTracer::SkipUntilBitInTable( + int cp_offset, Handle table, Handle nibble_table, + int advance_by) { + PrintF("SkipUntilBitInTable(cp_offset=%d, advance_by=%d\n ", cp_offset, + advance_by); + for (int i = 0; i < kTableSize; i++) { + PrintF("%c", table->get(i) != 0 ? 'X' : '.'); + if (i % 32 == 31 && i != kTableMask) { + PrintF("\n "); + } + } + static_assert(kTableSize == 128); + static constexpr int kRows = 16; + static_assert(kRows * kBitsPerByte == kTableSize); + if (!nibble_table.is_null()) { + PrintF("\n"); + PrintF(" +----------------\n"); + PrintF(" |"); + for (int j = 0; j < kBitsPerByte; j++) { + PrintF(" %x", j); + } + PrintF("\n--+----------------"); + for (int i = 0; i < kRows; i++) { + int r = nibble_table->get(i); + PrintF("\n%x |", i); + for (int j = 0; j < kBitsPerByte; j++) { + PrintF(" %c", (r & (1 << j)) == 0 ? '.' : 'X'); + } + } + } + PrintF(");\n"); + assembler_->SkipUntilBitInTable(cp_offset, table, nibble_table, advance_by); +} void RegExpMacroAssemblerTracer::CheckNotBackReference(int start_reg, bool read_backward, Label* on_no_match) { PrintF(" CheckNotBackReference(register=%d, %s, label[%08x]);\n", start_reg, diff --git a/js/src/irregexp/imported/regexp-macro-assembler.h b/js/src/irregexp/imported/regexp-macro-assembler.h --- a/js/src/irregexp/imported/regexp-macro-assembler.h +++ b/js/src/irregexp/imported/regexp-macro-assembler.h @@ -104,10 +104,15 @@ // The current character (modulus the kTableSize) is looked up in the byte // array, and if the found byte is non-zero, we jump to the on_bit_set label. virtual void CheckBitInTable(Handle table, Label* on_bit_set) = 0; + virtual void SkipUntilBitInTable(int cp_offset, Handle table, + Handle nibble_table, + int advance_by) = 0; + virtual bool SkipUntilBitInTableUseSimd(int advance_by) { return false; } + // Checks whether the given offset from the current position is before // the end of the string. May overwrite the current character. virtual void CheckPosition(int cp_offset, Label* on_outside_input); // Check whether a standard/default character class matches the current // character. Returns false if the type of special character class does diff --git a/js/src/irregexp/imported/regexp-nodes.h b/js/src/irregexp/imported/regexp-nodes.h --- a/js/src/irregexp/imported/regexp-nodes.h +++ b/js/src/irregexp/imported/regexp-nodes.h @@ -14,13 +14,13 @@ class BoyerMooreLookahead; class GreedyLoopState; class NodeVisitor; class QuickCheckDetails; class RegExpCompiler; +class SeqRegExpNode; class Trace; struct PreloadState; -class ChoiceNode; #define FOR_EACH_NODE_TYPE(VISIT) \ VISIT(End) \ VISIT(Action) \ VISIT(Choice) \ @@ -28,10 +28,14 @@ VISIT(NegativeLookaroundChoice) \ VISIT(BackReference) \ VISIT(Assertion) \ VISIT(Text) +#define FORWARD_DECLARE(type) class type##Node; +FOR_EACH_NODE_TYPE(FORWARD_DECLARE) +#undef FORWARD_DECLARE + struct NodeInfo final { NodeInfo() : being_analyzed(false), been_analyzed(false), follows_word_interest(false), @@ -201,11 +205,11 @@ } // If we know that the input is one-byte then there are some nodes that can // never match. This method returns a node that can be substituted for // itself, or nullptr if the node can never match. - virtual RegExpNode* FilterOneByte(int depth, RegExpFlags flags) { + virtual RegExpNode* FilterOneByte(int depth, RegExpCompiler* compiler) { return this; } // Helper for FilterOneByte. RegExpNode* replacement() { DCHECK(info()->replacement_calculated); @@ -253,10 +257,17 @@ BoyerMooreLookahead* bm_info(bool not_at_start) { return bm_info_[not_at_start ? 1 : 0]; } +#define DECLARE_CAST(type) \ + virtual type##Node* As##type##Node() { return nullptr; } + FOR_EACH_NODE_TYPE(DECLARE_CAST) +#undef DECLARE_CAST + + virtual SeqRegExpNode* AsSeqRegExpNode() { return nullptr; } + Zone* zone() const { return zone_; } protected: enum LimitResult { DONE, CONTINUE }; RegExpNode* replacement_; @@ -292,19 +303,20 @@ public: explicit SeqRegExpNode(RegExpNode* on_success) : RegExpNode(on_success->zone()), on_success_(on_success) {} RegExpNode* on_success() { return on_success_; } void set_on_success(RegExpNode* node) { on_success_ = node; } - RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + RegExpNode* FilterOneByte(int depth, RegExpCompiler* compiler) override; void FillInBMInfo(Isolate* isolate, int offset, int budget, BoyerMooreLookahead* bm, bool not_at_start) override { on_success_->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); if (offset == 0) set_bm_info(not_at_start, bm); } + SeqRegExpNode* AsSeqRegExpNode() override { return this; } protected: - RegExpNode* FilterSuccessor(int depth, RegExpFlags flags); + RegExpNode* FilterSuccessor(int depth, RegExpCompiler* compiler); private: RegExpNode* on_success_; }; @@ -326,12 +338,12 @@ static ActionNode* IncrementRegister(int reg, RegExpNode* on_success); static ActionNode* StorePosition(int reg, bool is_capture, RegExpNode* on_success); static ActionNode* ClearCaptures(Interval range, RegExpNode* on_success); static ActionNode* BeginPositiveSubmatch(int stack_pointer_reg, - int position_reg, - RegExpNode* on_success); + int position_reg, RegExpNode* body, + ActionNode* success_node); static ActionNode* BeginNegativeSubmatch(int stack_pointer_reg, int position_reg, RegExpNode* on_success); static ActionNode* PositiveSubmatchSuccess(int stack_pointer_reg, int restore_reg, @@ -341,26 +353,35 @@ static ActionNode* EmptyMatchCheck(int start_register, int repetition_register, int repetition_limit, RegExpNode* on_success); static ActionNode* ModifyFlags(RegExpFlags flags, RegExpNode* on_success); + ActionNode* AsActionNode() override { return this; } void Accept(NodeVisitor* visitor) override; void Emit(RegExpCompiler* compiler, Trace* trace) override; void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int filled_in, bool not_at_start) override; void FillInBMInfo(Isolate* isolate, int offset, int budget, BoyerMooreLookahead* bm, bool not_at_start) override; - ActionType action_type() { return action_type_; } + ActionType action_type() const { return action_type_; } // TODO(erikcorry): We should allow some action nodes in greedy loops. int GreedyLoopTextLength() override { return kNodeIsTooComplexForGreedyLoops; } - RegExpFlags flags() { + RegExpFlags flags() const { DCHECK_EQ(action_type(), MODIFY_FLAGS); return RegExpFlags{data_.u_modify_flags.flags}; } + ActionNode* success_node() const { + DCHECK_EQ(action_type(), BEGIN_POSITIVE_SUBMATCH); + return data_.u_submatch.success_node; + } + + protected: + ActionNode(ActionType action_type, RegExpNode* on_success) + : SeqRegExpNode(on_success), action_type_(action_type) {} private: union { struct { int reg; @@ -376,10 +397,11 @@ struct { int stack_pointer_register; int current_position_register; int clear_register_count; int clear_register_from; + ActionNode* success_node; // Only used for positive submatch. } u_submatch; struct { int start_register; int repetition_register; int repetition_limit; @@ -390,12 +412,10 @@ } u_clear_captures; struct { int flags; } u_modify_flags; } data_; - ActionNode(ActionType action_type, RegExpNode* on_success) - : SeqRegExpNode(on_success), action_type_(action_type) {} ActionType action_type_; friend class DotPrinterImpl; friend Zone; }; @@ -424,10 +444,11 @@ static TextNode* CreateForSurrogatePair(Zone* zone, ZoneList* lead_ranges, CharacterRange trail, bool read_backward, RegExpNode* on_success); + TextNode* AsTextNode() override { return this; } void Accept(NodeVisitor* visitor) override; void Emit(RegExpCompiler* compiler, Trace* trace) override; void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, bool not_at_start) override; @@ -439,11 +460,11 @@ RegExpNode* GetSuccessorOfOmnivorousTextNode( RegExpCompiler* compiler) override; void FillInBMInfo(Isolate* isolate, int offset, int budget, BoyerMooreLookahead* bm, bool not_at_start) override; void CalculateOffsets(); - RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + RegExpNode* FilterOneByte(int depth, RegExpCompiler* compiler) override; int Length(); private: enum TextEmitPassType { NON_LATIN1_MATCH, // Check for characters that can never match. @@ -481,10 +502,11 @@ return on_success->zone()->New(AT_NON_BOUNDARY, on_success); } static AssertionNode* AfterNewline(RegExpNode* on_success) { return on_success->zone()->New(AFTER_NEWLINE, on_success); } + AssertionNode* AsAssertionNode() override { return this; } void Accept(NodeVisitor* visitor) override; void Emit(RegExpCompiler* compiler, Trace* trace) override; void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int filled_in, bool not_at_start) override; @@ -510,10 +532,11 @@ RegExpNode* on_success) : SeqRegExpNode(on_success), start_reg_(start_reg), end_reg_(end_reg), read_backward_(read_backward) {} + BackReferenceNode* AsBackReferenceNode() override { return this; } void Accept(NodeVisitor* visitor) override; int start_register() { return start_reg_; } int end_register() { return end_reg_; } bool read_backward() { return read_backward_; } void Emit(RegExpCompiler* compiler, Trace* trace) override; @@ -533,10 +556,11 @@ class EndNode : public RegExpNode { public: enum Action { ACCEPT, BACKTRACK, NEGATIVE_SUBMATCH_SUCCESS }; EndNode(Action action, Zone* zone) : RegExpNode(zone), action_(action) {} + EndNode* AsEndNode() override { return this; } void Accept(NodeVisitor* visitor) override; void Emit(RegExpCompiler* compiler, Trace* trace) override; void GetQuickCheckDetails(QuickCheckDetails* details, RegExpCompiler* compiler, int characters_filled_in, bool not_at_start) override { @@ -608,10 +632,11 @@ : RegExpNode(zone), alternatives_( zone->New>(expected_size, zone)), not_at_start_(false), being_calculated_(false) {} + ChoiceNode* AsChoiceNode() override { return this; } void Accept(NodeVisitor* visitor) override; void AddAlternative(GuardedAlternative node) { alternatives()->Add(node, zone()); } ZoneList* alternatives() { return alternatives_; } @@ -627,11 +652,11 @@ void set_not_at_start() { not_at_start_ = true; } void set_being_calculated(bool b) { being_calculated_ = b; } virtual bool try_to_emit_quick_check_for_alternative(bool is_first) { return true; } - RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + RegExpNode* FilterOneByte(int depth, RegExpCompiler* compiler) override; virtual bool read_backward() { return false; } protected: int GreedyLoopTextLengthForAlternative(GuardedAlternative* alternative); ZoneList* alternatives_; @@ -698,12 +723,15 @@ // characters, but on a negative lookahead the negative branch did not take // part in that calculation (EatsAtLeast) so the assumptions don't hold. bool try_to_emit_quick_check_for_alternative(bool is_first) override { return !is_first; } + NegativeLookaroundChoiceNode* AsNegativeLookaroundChoiceNode() override { + return this; + } void Accept(NodeVisitor* visitor) override; - RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + RegExpNode* FilterOneByte(int depth, RegExpCompiler* compiler) override; }; class LoopChoiceNode : public ChoiceNode { public: LoopChoiceNode(bool body_can_be_zero_length, bool read_backward, @@ -731,12 +759,13 @@ RegExpNode* loop_node() { return loop_node_; } RegExpNode* continue_node() { return continue_node_; } bool body_can_be_zero_length() { return body_can_be_zero_length_; } int min_loop_iterations() const { return min_loop_iterations_; } bool read_backward() override { return read_backward_; } + LoopChoiceNode* AsLoopChoiceNode() override { return this; } void Accept(NodeVisitor* visitor) override; - RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + RegExpNode* FilterOneByte(int depth, RegExpCompiler* compiler) override; private: // AddAlternative is made private for loop nodes because alternatives // should not be added freely, we need to keep track of which node // goes back to the node itself. diff --git a/js/src/irregexp/imported/regexp-parser.cc b/js/src/irregexp/imported/regexp-parser.cc --- a/js/src/irregexp/imported/regexp-parser.cc +++ b/js/src/irregexp/imported/regexp-parser.cc @@ -146,18 +146,14 @@ text_.clear(); } void RegExpTextBuilder::AddCharacter(base::uc16 c) { FlushPendingSurrogate(); - if (NeedsDesugaringForIgnoreCase(c)) { - AddClassRangesForDesugaring(c); - } else { - if (characters_ == nullptr) { - characters_ = zone()->New>(4, zone()); - } - characters_->Add(c, zone()); + if (characters_ == nullptr) { + characters_ = zone()->New>(4, zone()); } + characters_->Add(c, zone()); } void RegExpTextBuilder::AddUnicodeCharacter(base::uc32 c) { if (c > static_cast(unibrow::Utf16::kMaxNonSurrogateCharCode)) { DCHECK(IsUnicodeMode()); @@ -232,10 +228,13 @@ if (from <= kTrailSurrogateEnd && to >= kLeadSurrogateStart) return true; } return false; } +// We only use this for characters made of surrogate pairs. All other +// characters outside of character classes are made case independent in the +// code generation. bool RegExpTextBuilder::NeedsDesugaringForIgnoreCase(base::uc32 c) { #ifdef V8_INTL_SUPPORT if (IsUnicodeMode() && ignore_case()) { icu::UnicodeSet set(c, c); set.closeOver(USET_CASE_INSENSITIVE); diff --git a/js/src/irregexp/moz.yaml b/js/src/irregexp/moz.yaml --- a/js/src/irregexp/moz.yaml +++ b/js/src/irregexp/moz.yaml @@ -7,12 +7,12 @@ origin: name: irregexp description: A fast regular expression engine from V8 url: https://v8.dev - release: 72678618db88d6b27e86b0b01e271b166d8f6b8b (Tue Aug 20 12:12:43 2024). - revision: 72678618db88d6b27e86b0b01e271b166d8f6b8b + release: cfc760f80e359d93a700fffd6fb9cf31cd62b059 (Wed Sep 04 17:49:08 2024). + revision: cfc760f80e359d93a700fffd6fb9cf31cd62b059 license: BSD-3-Clause license-file: LICENSE.v8 vendoring: