/* * Copyright (C) 2009 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "RegexJIT.h" #include "ASCIICType.h" #include "JSGlobalData.h" #include "LinkBuffer.h" #include "MacroAssembler.h" #include "RegexCompiler.h" #include "pcre.h" // temporary, remove when fallback is removed. #if ENABLE(YARR_JIT) using namespace WTF; namespace JSC { namespace Yarr { class RegexGenerator : private MacroAssembler { friend void jitCompileRegex(JSGlobalData* globalData, RegexCodeBlock& jitObject, const UString& pattern, unsigned& numSubpatterns, const char*& error, bool ignoreCase, bool multiline); #if PLATFORM(ARM) static const RegisterID input = ARM::r0; static const RegisterID index = ARM::r1; static const RegisterID length = ARM::r2; static const RegisterID output = ARM::r4; static const RegisterID regT0 = ARM::r5; static const RegisterID regT1 = ARM::r6; static const RegisterID returnRegister = ARM::r0; #elif PLATFORM(X86) static const RegisterID input = X86::eax; static const RegisterID index = X86::edx; static const RegisterID length = X86::ecx; static const RegisterID output = X86::edi; static const RegisterID regT0 = X86::ebx; static const RegisterID regT1 = X86::esi; static const RegisterID returnRegister = X86::eax; #elif PLATFORM(X86_64) static const RegisterID input = X86::edi; static const RegisterID index = X86::esi; static const RegisterID length = X86::edx; static const RegisterID output = X86::ecx; static const RegisterID regT0 = X86::eax; static const RegisterID regT1 = X86::ebx; static const RegisterID returnRegister = X86::eax; #endif void optimizeAlternative(PatternAlternative* alternative) { if (!alternative->m_terms.size()) return; for (unsigned i = 0; i < alternative->m_terms.size() - 1; ++i) { PatternTerm& term = alternative->m_terms[i]; PatternTerm& nextTerm = alternative->m_terms[i + 1]; if ((term.type == PatternTerm::TypeCharacterClass) && (term.quantityType == QuantifierFixedCount) && (nextTerm.type == PatternTerm::TypePatternCharacter) && (nextTerm.quantityType == QuantifierFixedCount)) { PatternTerm termCopy = term; alternative->m_terms[i] = nextTerm; alternative->m_terms[i + 1] = termCopy; } } } void matchCharacterClassRange(RegisterID character, JumpList& failures, JumpList& matchDest, const CharacterRange* ranges, unsigned count, unsigned* matchIndex, const UChar* matches, unsigned matchCount) { do { // pick which range we're going to generate int which = count >> 1; char lo = ranges[which].begin; char hi = ranges[which].end; // check if there are any ranges or matches below lo. If not, just jl to failure - // if there is anything else to check, check that first, if it falls through jmp to failure. if ((*matchIndex < matchCount) && (matches[*matchIndex] < lo)) { Jump loOrAbove = branch32(GreaterThanOrEqual, character, Imm32((unsigned short)lo)); // generate code for all ranges before this one if (which) matchCharacterClassRange(character, failures, matchDest, ranges, which, matchIndex, matches, matchCount); while ((*matchIndex < matchCount) && (matches[*matchIndex] < lo)) { matchDest.append(branch32(Equal, character, Imm32((unsigned short)matches[*matchIndex]))); ++*matchIndex; } failures.append(jump()); loOrAbove.link(this); } else if (which) { Jump loOrAbove = branch32(GreaterThanOrEqual, character, Imm32((unsigned short)lo)); matchCharacterClassRange(character, failures, matchDest, ranges, which, matchIndex, matches, matchCount); failures.append(jump()); loOrAbove.link(this); } else failures.append(branch32(LessThan, character, Imm32((unsigned short)lo))); while ((*matchIndex < matchCount) && (matches[*matchIndex] <= hi)) ++*matchIndex; matchDest.append(branch32(LessThanOrEqual, character, Imm32((unsigned short)hi))); // fall through to here, the value is above hi. // shuffle along & loop around if there are any more matches to handle. unsigned next = which + 1; ranges += next; count -= next; } while (count); } void matchCharacterClass(RegisterID character, JumpList& matchDest, const CharacterClass* charClass) { Jump unicodeFail; if (charClass->m_matchesUnicode.size() || charClass->m_rangesUnicode.size()) { Jump isAscii = branch32(LessThanOrEqual, character, Imm32(0x7f)); if (charClass->m_matchesUnicode.size()) { for (unsigned i = 0; i < charClass->m_matchesUnicode.size(); ++i) { UChar ch = charClass->m_matchesUnicode[i]; matchDest.append(branch32(Equal, character, Imm32(ch))); } } if (charClass->m_rangesUnicode.size()) { for (unsigned i = 0; i < charClass->m_rangesUnicode.size(); ++i) { UChar lo = charClass->m_rangesUnicode[i].begin; UChar hi = charClass->m_rangesUnicode[i].end; Jump below = branch32(LessThan, character, Imm32(lo)); matchDest.append(branch32(LessThanOrEqual, character, Imm32(hi))); below.link(this); } } unicodeFail = jump(); isAscii.link(this); } if (charClass->m_ranges.size()) { unsigned matchIndex = 0; JumpList failures; matchCharacterClassRange(character, failures, matchDest, charClass->m_ranges.begin(), charClass->m_ranges.size(), &matchIndex, charClass->m_matches.begin(), charClass->m_matches.size()); while (matchIndex < charClass->m_matches.size()) matchDest.append(branch32(Equal, character, Imm32((unsigned short)charClass->m_matches[matchIndex++]))); failures.link(this); } else if (charClass->m_matches.size()) { // optimization: gather 'a','A' etc back together, can mask & test once. Vector matchesAZaz; for (unsigned i = 0; i < charClass->m_matches.size(); ++i) { char ch = charClass->m_matches[i]; if (m_pattern.m_ignoreCase) { if (isASCIILower(ch)) { matchesAZaz.append(ch); continue; } if (isASCIIUpper(ch)) continue; } matchDest.append(branch32(Equal, character, Imm32((unsigned short)ch))); } if (unsigned countAZaz = matchesAZaz.size()) { or32(Imm32(32), character); for (unsigned i = 0; i < countAZaz; ++i) matchDest.append(branch32(Equal, character, Imm32(matchesAZaz[i]))); } } if (charClass->m_matchesUnicode.size() || charClass->m_rangesUnicode.size()) unicodeFail.link(this); } // Jumps if input not available; will have (incorrectly) incremented already! Jump jumpIfNoAvailableInput(unsigned countToCheck) { add32(Imm32(countToCheck), index); return branch32(Above, index, length); } Jump jumpIfAvailableInput(unsigned countToCheck) { add32(Imm32(countToCheck), index); return branch32(BelowOrEqual, index, length); } Jump checkInput() { return branch32(BelowOrEqual, index, length); } Jump atEndOfInput() { return branch32(Equal, index, length); } Jump notAtEndOfInput() { return branch32(NotEqual, index, length); } Jump jumpIfCharEquals(UChar ch, int inputPosition) { return branch16(Equal, BaseIndex(input, index, TimesTwo, inputPosition * sizeof(UChar)), Imm32(ch)); } Jump jumpIfCharNotEquals(UChar ch, int inputPosition) { return branch16(NotEqual, BaseIndex(input, index, TimesTwo, inputPosition * sizeof(UChar)), Imm32(ch)); } void readCharacter(int inputPosition, RegisterID reg) { load16(BaseIndex(input, index, TimesTwo, inputPosition * sizeof(UChar)), reg); } void storeToFrame(RegisterID reg, unsigned frameLocation) { poke(reg, frameLocation); } void storeToFrame(Imm32 imm, unsigned frameLocation) { poke(imm, frameLocation); } DataLabelPtr storeToFrameWithPatch(unsigned frameLocation) { return storePtrWithPatch(ImmPtr(0), Address(stackPointerRegister, frameLocation * sizeof(void*))); } void loadFromFrame(unsigned frameLocation, RegisterID reg) { peek(reg, frameLocation); } void loadFromFrameAndJump(unsigned frameLocation) { jump(Address(stackPointerRegister, frameLocation * sizeof(void*))); } struct AlternativeBacktrackRecord { DataLabelPtr dataLabel; Label backtrackLocation; AlternativeBacktrackRecord(DataLabelPtr dataLabel, Label backtrackLocation) : dataLabel(dataLabel) , backtrackLocation(backtrackLocation) { } }; struct TermGenerationState { TermGenerationState(PatternDisjunction* disjunction, unsigned checkedTotal) : disjunction(disjunction) , checkedTotal(checkedTotal) { } void resetAlternative() { isBackTrackGenerated = false; alt = 0; } bool alternativeValid() { return alt < disjunction->m_alternatives.size(); } void nextAlternative() { ++alt; } PatternAlternative* alternative() { return disjunction->m_alternatives[alt]; } void resetTerm() { ASSERT(alternativeValid()); t = 0; } bool termValid() { ASSERT(alternativeValid()); return t < alternative()->m_terms.size(); } void nextTerm() { ASSERT(alternativeValid()); ++t; } PatternTerm& term() { ASSERT(alternativeValid()); return alternative()->m_terms[t]; } PatternTerm& lookaheadTerm() { ASSERT(alternativeValid()); ASSERT((t + 1) < alternative()->m_terms.size()); return alternative()->m_terms[t + 1]; } bool isSinglePatternCharacterLookaheadTerm() { ASSERT(alternativeValid()); return ((t + 1) < alternative()->m_terms.size()) && (lookaheadTerm().type == PatternTerm::TypePatternCharacter) && (lookaheadTerm().quantityType == QuantifierFixedCount) && (lookaheadTerm().quantityCount == 1); } int inputOffset() { return term().inputPosition - checkedTotal; } void jumpToBacktrack(Jump jump, MacroAssembler* masm) { if (isBackTrackGenerated) jump.linkTo(backtrackLabel, masm); else backTrackJumps.append(jump); } void jumpToBacktrack(JumpList& jumps, MacroAssembler* masm) { if (isBackTrackGenerated) jumps.linkTo(backtrackLabel, masm); else backTrackJumps.append(jumps); } bool plantJumpToBacktrackIfExists(MacroAssembler* masm) { if (isBackTrackGenerated) { masm->jump(backtrackLabel); return true; } return false; } void addBacktrackJump(Jump jump) { backTrackJumps.append(jump); } void setBacktrackGenerated(Label label) { isBackTrackGenerated = true; backtrackLabel = label; } void linkAlternativeBacktracks(MacroAssembler* masm) { isBackTrackGenerated = false; backTrackJumps.link(masm); } void linkAlternativeBacktracksTo(Label label, MacroAssembler* masm) { isBackTrackGenerated = false; backTrackJumps.linkTo(label, masm); } void propagateBacktrackingFrom(TermGenerationState& nestedParenthesesState, MacroAssembler* masm) { jumpToBacktrack(nestedParenthesesState.backTrackJumps, masm); if (nestedParenthesesState.isBackTrackGenerated) setBacktrackGenerated(nestedParenthesesState.backtrackLabel); } PatternDisjunction* disjunction; int checkedTotal; private: unsigned alt; unsigned t; JumpList backTrackJumps; Label backtrackLabel; bool isBackTrackGenerated; }; void generateAssertionBOL(TermGenerationState& state) { PatternTerm& term = state.term(); if (m_pattern.m_multiline) { const RegisterID character = regT0; JumpList matchDest; if (!term.inputPosition) matchDest.append(branch32(Equal, index, Imm32(state.checkedTotal))); readCharacter(state.inputOffset() - 1, character); matchCharacterClass(character, matchDest, m_pattern.newlineCharacterClass()); state.jumpToBacktrack(jump(), this); matchDest.link(this); } else { // Erk, really should poison out these alternatives early. :-/ if (term.inputPosition) state.jumpToBacktrack(jump(), this); else state.jumpToBacktrack(branch32(NotEqual, index, Imm32(state.checkedTotal)), this); } } void generateAssertionEOL(TermGenerationState& state) { PatternTerm& term = state.term(); if (m_pattern.m_multiline) { const RegisterID character = regT0; JumpList matchDest; if (term.inputPosition == state.checkedTotal) matchDest.append(atEndOfInput()); readCharacter(state.inputOffset(), character); matchCharacterClass(character, matchDest, m_pattern.newlineCharacterClass()); state.jumpToBacktrack(jump(), this); matchDest.link(this); } else { if (term.inputPosition == state.checkedTotal) state.jumpToBacktrack(notAtEndOfInput(), this); // Erk, really should poison out these alternatives early. :-/ else state.jumpToBacktrack(jump(), this); } } // Also falls though on nextIsNotWordChar. void matchAssertionWordchar(TermGenerationState& state, JumpList& nextIsWordChar, JumpList& nextIsNotWordChar) { const RegisterID character = regT0; PatternTerm& term = state.term(); if (term.inputPosition == state.checkedTotal) nextIsNotWordChar.append(atEndOfInput()); readCharacter(state.inputOffset(), character); matchCharacterClass(character, nextIsWordChar, m_pattern.wordcharCharacterClass()); } void generateAssertionWordBoundary(TermGenerationState& state) { const RegisterID character = regT0; PatternTerm& term = state.term(); Jump atBegin; JumpList matchDest; if (!term.inputPosition) atBegin = branch32(Equal, index, Imm32(state.checkedTotal)); readCharacter(state.inputOffset() - 1, character); matchCharacterClass(character, matchDest, m_pattern.wordcharCharacterClass()); if (!term.inputPosition) atBegin.link(this); // We fall through to here if the last character was not a wordchar. JumpList nonWordCharThenWordChar; JumpList nonWordCharThenNonWordChar; if (term.invertOrCapture) { matchAssertionWordchar(state, nonWordCharThenNonWordChar, nonWordCharThenWordChar); nonWordCharThenWordChar.append(jump()); } else { matchAssertionWordchar(state, nonWordCharThenWordChar, nonWordCharThenNonWordChar); nonWordCharThenNonWordChar.append(jump()); } state.jumpToBacktrack(nonWordCharThenNonWordChar, this); // We jump here if the last character was a wordchar. matchDest.link(this); JumpList wordCharThenWordChar; JumpList wordCharThenNonWordChar; if (term.invertOrCapture) { matchAssertionWordchar(state, wordCharThenNonWordChar, wordCharThenWordChar); wordCharThenWordChar.append(jump()); } else { matchAssertionWordchar(state, wordCharThenWordChar, wordCharThenNonWordChar); // This can fall-though! } state.jumpToBacktrack(wordCharThenWordChar, this); nonWordCharThenWordChar.link(this); wordCharThenNonWordChar.link(this); } void generatePatternCharacterSingle(TermGenerationState& state) { const RegisterID character = regT0; UChar ch = state.term().patternCharacter; if (m_pattern.m_ignoreCase && isASCIIAlpha(ch)) { readCharacter(state.inputOffset(), character); or32(Imm32(32), character); state.jumpToBacktrack(branch32(NotEqual, character, Imm32(Unicode::toLower(ch))), this); } else { ASSERT(!m_pattern.m_ignoreCase || (Unicode::toLower(ch) == Unicode::toUpper(ch))); state.jumpToBacktrack(jumpIfCharNotEquals(ch, state.inputOffset()), this); } } void generatePatternCharacterPair(TermGenerationState& state) { const RegisterID character = regT0; UChar ch1 = state.term().patternCharacter; UChar ch2 = state.lookaheadTerm().patternCharacter; int mask = 0; int chPair = ch1 | (ch2 << 16); if (m_pattern.m_ignoreCase) { if (isASCIIAlpha(ch1)) mask |= 32; if (isASCIIAlpha(ch2)) mask |= 32 << 16; } if (mask) { load32(BaseIndex(input, index, TimesTwo, state.inputOffset() * sizeof(UChar)), character); or32(Imm32(mask), character); state.jumpToBacktrack(branch32(NotEqual, character, Imm32(chPair | mask)), this); } else state.jumpToBacktrack(branch32(NotEqual, BaseIndex(input, index, TimesTwo, state.inputOffset() * sizeof(UChar)), Imm32(chPair)), this); } void generatePatternCharacterFixed(TermGenerationState& state) { const RegisterID character = regT0; const RegisterID countRegister = regT1; PatternTerm& term = state.term(); UChar ch = term.patternCharacter; move(index, countRegister); sub32(Imm32(term.quantityCount), countRegister); Label loop(this); if (m_pattern.m_ignoreCase && isASCIIAlpha(ch)) { load16(BaseIndex(input, countRegister, TimesTwo, (state.inputOffset() + term.quantityCount) * sizeof(UChar)), character); or32(Imm32(32), character); state.jumpToBacktrack(branch32(NotEqual, character, Imm32(Unicode::toLower(ch))), this); } else { ASSERT(!m_pattern.m_ignoreCase || (Unicode::toLower(ch) == Unicode::toUpper(ch))); state.jumpToBacktrack(branch16(NotEqual, BaseIndex(input, countRegister, TimesTwo, (state.inputOffset() + term.quantityCount) * sizeof(UChar)), Imm32(ch)), this); } add32(Imm32(1), countRegister); branch32(NotEqual, countRegister, index).linkTo(loop, this); } void generatePatternCharacterGreedy(TermGenerationState& state) { const RegisterID character = regT0; const RegisterID countRegister = regT1; PatternTerm& term = state.term(); UChar ch = term.patternCharacter; move(Imm32(0), countRegister); JumpList failures; Label loop(this); failures.append(atEndOfInput()); if (m_pattern.m_ignoreCase && isASCIIAlpha(ch)) { readCharacter(state.inputOffset(), character); or32(Imm32(32), character); failures.append(branch32(NotEqual, character, Imm32(Unicode::toLower(ch)))); } else { ASSERT(!m_pattern.m_ignoreCase || (Unicode::toLower(ch) == Unicode::toUpper(ch))); failures.append(jumpIfCharNotEquals(ch, state.inputOffset())); } add32(Imm32(1), countRegister); add32(Imm32(1), index); branch32(NotEqual, countRegister, Imm32(term.quantityCount)).linkTo(loop, this); failures.append(jump()); Label backtrackBegin(this); loadFromFrame(term.frameLocation, countRegister); state.jumpToBacktrack(branchTest32(Zero, countRegister), this); sub32(Imm32(1), countRegister); sub32(Imm32(1), index); failures.link(this); storeToFrame(countRegister, term.frameLocation); state.setBacktrackGenerated(backtrackBegin); } void generatePatternCharacterNonGreedy(TermGenerationState& state) { const RegisterID character = regT0; const RegisterID countRegister = regT1; PatternTerm& term = state.term(); UChar ch = term.patternCharacter; move(Imm32(0), countRegister); Jump firstTimeDoNothing = jump(); Label hardFail(this); sub32(countRegister, index); state.jumpToBacktrack(jump(), this); Label backtrackBegin(this); loadFromFrame(term.frameLocation, countRegister); atEndOfInput().linkTo(hardFail, this); branch32(Equal, countRegister, Imm32(term.quantityCount), hardFail); if (m_pattern.m_ignoreCase && isASCIIAlpha(ch)) { readCharacter(state.inputOffset(), character); or32(Imm32(32), character); branch32(NotEqual, character, Imm32(Unicode::toLower(ch))).linkTo(hardFail, this); } else { ASSERT(!m_pattern.m_ignoreCase || (Unicode::toLower(ch) == Unicode::toUpper(ch))); jumpIfCharNotEquals(ch, state.inputOffset()).linkTo(hardFail, this); } add32(Imm32(1), countRegister); add32(Imm32(1), index); firstTimeDoNothing.link(this); storeToFrame(countRegister, term.frameLocation); state.setBacktrackGenerated(backtrackBegin); } void generateCharacterClassSingle(TermGenerationState& state) { const RegisterID character = regT0; PatternTerm& term = state.term(); JumpList matchDest; readCharacter(state.inputOffset(), character); matchCharacterClass(character, matchDest, term.characterClass); if (term.invertOrCapture) state.jumpToBacktrack(matchDest, this); else { state.jumpToBacktrack(jump(), this); matchDest.link(this); } } void generateCharacterClassFixed(TermGenerationState& state) { const RegisterID character = regT0; const RegisterID countRegister = regT1; PatternTerm& term = state.term(); move(index, countRegister); sub32(Imm32(term.quantityCount), countRegister); Label loop(this); JumpList matchDest; load16(BaseIndex(input, countRegister, TimesTwo, (state.inputOffset() + term.quantityCount) * sizeof(UChar)), character); matchCharacterClass(character, matchDest, term.characterClass); if (term.invertOrCapture) state.jumpToBacktrack(matchDest, this); else { state.jumpToBacktrack(jump(), this); matchDest.link(this); } add32(Imm32(1), countRegister); branch32(NotEqual, countRegister, index).linkTo(loop, this); } void generateCharacterClassGreedy(TermGenerationState& state) { const RegisterID character = regT0; const RegisterID countRegister = regT1; PatternTerm& term = state.term(); move(Imm32(0), countRegister); JumpList failures; Label loop(this); failures.append(atEndOfInput()); if (term.invertOrCapture) { readCharacter(state.inputOffset(), character); matchCharacterClass(character, failures, term.characterClass); } else { JumpList matchDest; readCharacter(state.inputOffset(), character); matchCharacterClass(character, matchDest, term.characterClass); failures.append(jump()); matchDest.link(this); } add32(Imm32(1), countRegister); add32(Imm32(1), index); branch32(NotEqual, countRegister, Imm32(term.quantityCount)).linkTo(loop, this); failures.append(jump()); Label backtrackBegin(this); loadFromFrame(term.frameLocation, countRegister); state.jumpToBacktrack(branchTest32(Zero, countRegister), this); sub32(Imm32(1), countRegister); sub32(Imm32(1), index); failures.link(this); storeToFrame(countRegister, term.frameLocation); state.setBacktrackGenerated(backtrackBegin); } void generateCharacterClassNonGreedy(TermGenerationState& state) { const RegisterID character = regT0; const RegisterID countRegister = regT1; PatternTerm& term = state.term(); move(Imm32(0), countRegister); Jump firstTimeDoNothing = jump(); Label hardFail(this); sub32(countRegister, index); state.jumpToBacktrack(jump(), this); Label backtrackBegin(this); loadFromFrame(term.frameLocation, countRegister); atEndOfInput().linkTo(hardFail, this); branch32(Equal, countRegister, Imm32(term.quantityCount), hardFail); JumpList matchDest; readCharacter(state.inputOffset(), character); matchCharacterClass(character, matchDest, term.characterClass); if (term.invertOrCapture) matchDest.linkTo(hardFail, this); else { jump(hardFail); matchDest.link(this); } add32(Imm32(1), countRegister); add32(Imm32(1), index); firstTimeDoNothing.link(this); storeToFrame(countRegister, term.frameLocation); state.setBacktrackGenerated(backtrackBegin); } void generateParenthesesDisjunction(PatternTerm& parenthesesTerm, TermGenerationState& state, unsigned alternativeFrameLocation) { ASSERT((parenthesesTerm.type == PatternTerm::TypeParenthesesSubpattern) || (parenthesesTerm.type == PatternTerm::TypeParentheticalAssertion)); ASSERT(parenthesesTerm.quantityCount == 1); PatternDisjunction* disjunction = parenthesesTerm.parentheses.disjunction; unsigned preCheckedCount = ((parenthesesTerm.quantityType == QuantifierFixedCount) && (parenthesesTerm.type != PatternTerm::TypeParentheticalAssertion)) ? disjunction->m_minimumSize : 0; if (disjunction->m_alternatives.size() == 1) { state.resetAlternative(); ASSERT(state.alternativeValid()); PatternAlternative* alternative = state.alternative(); optimizeAlternative(alternative); int countToCheck = alternative->m_minimumSize - preCheckedCount; if (countToCheck) { ASSERT((parenthesesTerm.type == PatternTerm::TypeParentheticalAssertion) || (parenthesesTerm.quantityType != QuantifierFixedCount)); // FIXME: This is quite horrible. The call to 'plantJumpToBacktrackIfExists' // will be forced to always trampoline into here, just to decrement the index. // Ick. Jump skip = jump(); Label backtrackBegin(this); sub32(Imm32(countToCheck), index); state.addBacktrackJump(jump()); skip.link(this); state.setBacktrackGenerated(backtrackBegin); state.jumpToBacktrack(jumpIfNoAvailableInput(countToCheck), this); state.checkedTotal += countToCheck; } for (state.resetTerm(); state.termValid(); state.nextTerm()) generateTerm(state); state.checkedTotal -= countToCheck; } else { JumpList successes; for (state.resetAlternative(); state.alternativeValid(); state.nextAlternative()) { PatternAlternative* alternative = state.alternative(); optimizeAlternative(alternative); ASSERT(alternative->m_minimumSize >= preCheckedCount); int countToCheck = alternative->m_minimumSize - preCheckedCount; if (countToCheck) { state.addBacktrackJump(jumpIfNoAvailableInput(countToCheck)); state.checkedTotal += countToCheck; } for (state.resetTerm(); state.termValid(); state.nextTerm()) generateTerm(state); // Matched an alternative. DataLabelPtr dataLabel = storeToFrameWithPatch(alternativeFrameLocation); successes.append(jump()); // Alternative did not match. Label backtrackLocation(this); // Can we backtrack the alternative? - if so, do so. If not, just fall through to the next one. state.plantJumpToBacktrackIfExists(this); state.linkAlternativeBacktracks(this); if (countToCheck) { sub32(Imm32(countToCheck), index); state.checkedTotal -= countToCheck; } m_backtrackRecords.append(AlternativeBacktrackRecord(dataLabel, backtrackLocation)); } // We fall through to here when the last alternative fails. // Add a backtrack out of here for the parenthese handling code to link up. state.addBacktrackJump(jump()); // Generate a trampoline for the parens code to backtrack to, to retry the // next alternative. state.setBacktrackGenerated(label()); loadFromFrameAndJump(alternativeFrameLocation); // FIXME: both of the above hooks are a little inefficient, in that you // may end up trampolining here, just to trampoline back out to the // parentheses code, or vice versa. We can probably eliminate a jump // by restructuring, but coding this way for now for simplicity during // development. successes.link(this); } } void generateParenthesesSingle(TermGenerationState& state) { const RegisterID indexTemporary = regT0; PatternTerm& term = state.term(); PatternDisjunction* disjunction = term.parentheses.disjunction; ASSERT(term.quantityCount == 1); unsigned preCheckedCount = ((term.quantityCount == 1) && (term.quantityType == QuantifierFixedCount)) ? disjunction->m_minimumSize : 0; unsigned parenthesesFrameLocation = term.frameLocation; unsigned alternativeFrameLocation = parenthesesFrameLocation; if (term.quantityType != QuantifierFixedCount) alternativeFrameLocation += RegexStackSpaceForBackTrackInfoParenthesesOnce; // optimized case - no capture & no quantifier can be handled in a light-weight manner. if (!term.invertOrCapture && (term.quantityType == QuantifierFixedCount)) { TermGenerationState parenthesesState(disjunction, state.checkedTotal); generateParenthesesDisjunction(state.term(), parenthesesState, alternativeFrameLocation); // this expects that any backtracks back out of the parentheses will be in the // parenthesesState's backTrackJumps vector, and that if they need backtracking // they will have set an entry point on the parenthesesState's backtrackLabel. state.propagateBacktrackingFrom(parenthesesState, this); } else { Jump nonGreedySkipParentheses; Label nonGreedyTryParentheses; if (term.quantityType == QuantifierGreedy) storeToFrame(Imm32(1), parenthesesFrameLocation); else if (term.quantityType == QuantifierNonGreedy) { storeToFrame(Imm32(0), parenthesesFrameLocation); nonGreedySkipParentheses = jump(); nonGreedyTryParentheses = label(); storeToFrame(Imm32(1), parenthesesFrameLocation); } // store the match start index if (term.invertOrCapture) { int inputOffset = state.inputOffset() - preCheckedCount; if (inputOffset) { move(index, indexTemporary); add32(Imm32(inputOffset), indexTemporary); store32(indexTemporary, Address(output, (term.parentheses.subpatternId << 1) * sizeof(int))); } else store32(index, Address(output, (term.parentheses.subpatternId << 1) * sizeof(int))); } // generate the body of the parentheses TermGenerationState parenthesesState(disjunction, state.checkedTotal); generateParenthesesDisjunction(state.term(), parenthesesState, alternativeFrameLocation); // store the match end index if (term.invertOrCapture) { int inputOffset = state.inputOffset(); if (inputOffset) { move(index, indexTemporary); add32(Imm32(state.inputOffset()), indexTemporary); store32(indexTemporary, Address(output, ((term.parentheses.subpatternId << 1) + 1) * sizeof(int))); } else store32(index, Address(output, ((term.parentheses.subpatternId << 1) + 1) * sizeof(int))); } Jump success = jump(); // A failure AFTER the parens jumps here Label backtrackFromAfterParens(this); if (term.quantityType == QuantifierGreedy) { // If this is zero we have now tested with both with and without the parens. loadFromFrame(parenthesesFrameLocation, indexTemporary); state.jumpToBacktrack(branchTest32(Zero, indexTemporary), this); } else if (term.quantityType == QuantifierNonGreedy) { // If this is zero we have now tested with both with and without the parens. loadFromFrame(parenthesesFrameLocation, indexTemporary); branchTest32(Zero, indexTemporary).linkTo(nonGreedyTryParentheses, this); } parenthesesState.plantJumpToBacktrackIfExists(this); // A failure WITHIN the parens jumps here parenthesesState.linkAlternativeBacktracks(this); if (term.invertOrCapture) { store32(Imm32(-1), Address(output, (term.parentheses.subpatternId << 1) * sizeof(int))); store32(Imm32(-1), Address(output, ((term.parentheses.subpatternId << 1) + 1) * sizeof(int))); } if (term.quantityType == QuantifierGreedy) storeToFrame(Imm32(0), parenthesesFrameLocation); else state.jumpToBacktrack(jump(), this); state.setBacktrackGenerated(backtrackFromAfterParens); if (term.quantityType == QuantifierNonGreedy) nonGreedySkipParentheses.link(this); success.link(this); } } void generateParentheticalAssertion(TermGenerationState& state) { PatternTerm& term = state.term(); PatternDisjunction* disjunction = term.parentheses.disjunction; ASSERT(term.quantityCount == 1); ASSERT(term.quantityType == QuantifierFixedCount); unsigned parenthesesFrameLocation = term.frameLocation; unsigned alternativeFrameLocation = parenthesesFrameLocation + RegexStackSpaceForBackTrackInfoParentheticalAssertion; int countCheckedAfterAssertion = state.checkedTotal - term.inputPosition; if (term.invertOrCapture) { // Inverted case storeToFrame(index, parenthesesFrameLocation); state.checkedTotal -= countCheckedAfterAssertion; if (countCheckedAfterAssertion) sub32(Imm32(countCheckedAfterAssertion), index); TermGenerationState parenthesesState(disjunction, state.checkedTotal); generateParenthesesDisjunction(state.term(), parenthesesState, alternativeFrameLocation); // Success! - which means - Fail! loadFromFrame(parenthesesFrameLocation, index); state.jumpToBacktrack(jump(), this); // And fail means success. parenthesesState.linkAlternativeBacktracks(this); loadFromFrame(parenthesesFrameLocation, index); state.checkedTotal += countCheckedAfterAssertion; } else { // Normal case storeToFrame(index, parenthesesFrameLocation); state.checkedTotal -= countCheckedAfterAssertion; if (countCheckedAfterAssertion) sub32(Imm32(countCheckedAfterAssertion), index); TermGenerationState parenthesesState(disjunction, state.checkedTotal); generateParenthesesDisjunction(state.term(), parenthesesState, alternativeFrameLocation); // Success! - which means - Success! loadFromFrame(parenthesesFrameLocation, index); Jump success = jump(); parenthesesState.linkAlternativeBacktracks(this); loadFromFrame(parenthesesFrameLocation, index); state.jumpToBacktrack(jump(), this); success.link(this); state.checkedTotal += countCheckedAfterAssertion; } } void generateTerm(TermGenerationState& state) { PatternTerm& term = state.term(); switch (term.type) { case PatternTerm::TypeAssertionBOL: generateAssertionBOL(state); break; case PatternTerm::TypeAssertionEOL: generateAssertionEOL(state); break; case PatternTerm::TypeAssertionWordBoundary: generateAssertionWordBoundary(state); break; case PatternTerm::TypePatternCharacter: switch (term.quantityType) { case QuantifierFixedCount: if (term.quantityCount == 1) { if (state.isSinglePatternCharacterLookaheadTerm() && (state.lookaheadTerm().inputPosition == (term.inputPosition + 1))) { generatePatternCharacterPair(state); state.nextTerm(); } else generatePatternCharacterSingle(state); } else generatePatternCharacterFixed(state); break; case QuantifierGreedy: generatePatternCharacterGreedy(state); break; case QuantifierNonGreedy: generatePatternCharacterNonGreedy(state); break; } break; case PatternTerm::TypeCharacterClass: switch (term.quantityType) { case QuantifierFixedCount: if (term.quantityCount == 1) generateCharacterClassSingle(state); else generateCharacterClassFixed(state); break; case QuantifierGreedy: generateCharacterClassGreedy(state); break; case QuantifierNonGreedy: generateCharacterClassNonGreedy(state); break; } break; case PatternTerm::TypeBackReference: m_generationFailed = true; break; case PatternTerm::TypeForwardReference: break; case PatternTerm::TypeParenthesesSubpattern: if ((term.quantityCount == 1) && !term.parentheses.isCopy) generateParenthesesSingle(state); else m_generationFailed = true; break; case PatternTerm::TypeParentheticalAssertion: generateParentheticalAssertion(state); break; } } void generateDisjunction(PatternDisjunction* disjunction) { TermGenerationState state(disjunction, 0); state.resetAlternative(); // Plant a check to see if there is sufficient input available to run the first alternative. // Jumping back to the label 'firstAlternative' will get to this check, jumping to // 'firstAlternativeInputChecked' will jump directly to matching the alternative having // skipped this check. Label firstAlternative(this); // check availability for the next alternative int countCheckedForCurrentAlternative = 0; int countToCheckForFirstAlternative = 0; bool hasShorterAlternatives = false; JumpList notEnoughInputForPreviousAlternative; if (state.alternativeValid()) { PatternAlternative* alternative = state.alternative(); countToCheckForFirstAlternative = alternative->m_minimumSize; state.checkedTotal += countToCheckForFirstAlternative; if (countToCheckForFirstAlternative) notEnoughInputForPreviousAlternative.append(jumpIfNoAvailableInput(countToCheckForFirstAlternative)); countCheckedForCurrentAlternative = countToCheckForFirstAlternative; } Label firstAlternativeInputChecked(this); while (state.alternativeValid()) { // Track whether any alternatives are shorter than the first one. hasShorterAlternatives = hasShorterAlternatives || (countCheckedForCurrentAlternative < countToCheckForFirstAlternative); PatternAlternative* alternative = state.alternative(); optimizeAlternative(alternative); for (state.resetTerm(); state.termValid(); state.nextTerm()) generateTerm(state); // If we get here, the alternative matched. if (m_pattern.m_body->m_callFrameSize) addPtr(Imm32(m_pattern.m_body->m_callFrameSize * sizeof(void*)), stackPointerRegister); ASSERT(index != returnRegister); if (m_pattern.m_body->m_hasFixedSize) { move(index, returnRegister); if (alternative->m_minimumSize) sub32(Imm32(alternative->m_minimumSize), returnRegister); } else pop(returnRegister); store32(index, Address(output, 4)); store32(returnRegister, output); generateReturn(); state.nextAlternative(); // if there are any more alternatives, plant the check for input before looping. if (state.alternativeValid()) { PatternAlternative* nextAlternative = state.alternative(); int countToCheckForNextAlternative = nextAlternative->m_minimumSize; if (countCheckedForCurrentAlternative > countToCheckForNextAlternative) { // CASE 1: current alternative was longer than the next one. // If we get here, there the last input checked failed. notEnoughInputForPreviousAlternative.link(this); // Check if sufficent input available to run the next alternative notEnoughInputForPreviousAlternative.append(jumpIfNoAvailableInput(countToCheckForNextAlternative - countCheckedForCurrentAlternative)); // We are now in the correct state to enter the next alternative; this add is only required // to mirror and revert operation of the sub32, just below. add32(Imm32(countCheckedForCurrentAlternative - countToCheckForNextAlternative), index); // If we get here, there the last input checked passed. state.linkAlternativeBacktracks(this); // No need to check if we can run the next alternative, since it is shorter - // just update index. sub32(Imm32(countCheckedForCurrentAlternative - countToCheckForNextAlternative), index); } else if (countCheckedForCurrentAlternative < countToCheckForNextAlternative) { // CASE 2: next alternative is longer than the current one. // If we get here, there the last input checked failed. // If there is insufficient input to run the current alternative, and the next alternative is longer, // then there is definitely not enough input to run it - don't even check. Just adjust index, as if // we had checked. notEnoughInputForPreviousAlternative.link(this); add32(Imm32(countToCheckForNextAlternative - countCheckedForCurrentAlternative), index); notEnoughInputForPreviousAlternative.append(jump()); // The next alternative is longer than the current one; check the difference. state.linkAlternativeBacktracks(this); notEnoughInputForPreviousAlternative.append(jumpIfNoAvailableInput(countToCheckForNextAlternative - countCheckedForCurrentAlternative)); } else { // CASE 3: Both alternatives are the same length. ASSERT(countCheckedForCurrentAlternative == countToCheckForNextAlternative); // If the next alterative is the same length as this one, then no need to check the input - // if there was sufficent input to run the current alternative then there is sufficient // input to run the next one; if not, there isn't. state.linkAlternativeBacktracks(this); } state.checkedTotal -= countCheckedForCurrentAlternative; countCheckedForCurrentAlternative = countToCheckForNextAlternative; state.checkedTotal += countCheckedForCurrentAlternative; } } // If we get here, all Alternatives failed... state.checkedTotal -= countCheckedForCurrentAlternative; // How much more input need there be to be able to retry from the first alternative? // examples: // /yarr_jit/ or /wrec|pcre/ // In these examples we need check for one more input before looping. // /yarr_jit|pcre/ // In this case we need check for 5 more input to loop (+4 to allow for the first alterative // being four longer than the last alternative checked, and another +1 to effectively move // the start position along by one). // /yarr|rules/ or /wrec|notsomuch/ // In these examples, provided that there was sufficient input to have just been matching for // the second alternative we can loop without checking for available input (since the second // alternative is longer than the first). In the latter example we need to decrement index // (by 4) so the start position is only progressed by 1 from the last iteration. int incrementForNextIter = (countToCheckForFirstAlternative - countCheckedForCurrentAlternative) + 1; // First, deal with the cases where there was sufficient input to try the last alternative. if (incrementForNextIter > 0) // We need to check for more input anyway, fall through to the checking below. state.linkAlternativeBacktracks(this); else if (m_pattern.m_body->m_hasFixedSize && !incrementForNextIter) // No need to update anything, link these backtracks straight to the to pof the loop! state.linkAlternativeBacktracksTo(firstAlternativeInputChecked, this); else { // no need to check the input, but we do have some bookkeeping to do first. state.linkAlternativeBacktracks(this); // Where necessary update our preserved start position. if (!m_pattern.m_body->m_hasFixedSize) { move(index, regT0); sub32(Imm32(countCheckedForCurrentAlternative - 1), regT0); poke(regT0, m_pattern.m_body->m_callFrameSize); } // Update index if necessary, and loop (without checking). if (incrementForNextIter) add32(Imm32(incrementForNextIter), index); jump().linkTo(firstAlternativeInputChecked, this); } notEnoughInputForPreviousAlternative.link(this); // Update our idea of the start position, if we're tracking this. if (!m_pattern.m_body->m_hasFixedSize) { if (countCheckedForCurrentAlternative - 1) { move(index, regT0); sub32(Imm32(countCheckedForCurrentAlternative - 1), regT0); poke(regT0, m_pattern.m_body->m_callFrameSize); } else poke(index, m_pattern.m_body->m_callFrameSize); } // Check if there is sufficent input to run the first alternative again. jumpIfAvailableInput(incrementForNextIter).linkTo(firstAlternativeInputChecked, this); // No - insufficent input to run the first alteranative, are there any other alternatives we // might need to check? If so, the last check will have left the index incremented by // (countToCheckForFirstAlternative + 1), so we need test whether countToCheckForFirstAlternative // LESS input is available, to have the effect of just progressing the start position by 1 // from the last iteration. If this check passes we can just jump up to the check associated // with the first alternative in the loop. This is a bit sad, since we'll end up trying the // first alternative again, and this check will fail (otherwise the check planted just above // here would have passed). This is a bit sad, however it saves trying to do something more // complex here in compilation, and in the common case we should end up coallescing the checks. // // FIXME: a nice improvement here may be to stop trying to match sooner, based on the least // of the minimum-alterantive-lengths. E.g. if I have two alternatives of length 200 and 150, // and a string of length 100, we'll end up looping index from 0 to 100, checking whether there // is sufficient input to run either alternative (constantly failing). If there had been only // one alternative, or if the shorter alternative had come first, we would have terminated // immediately. :-/ if (hasShorterAlternatives) jumpIfAvailableInput(-countToCheckForFirstAlternative).linkTo(firstAlternative, this); // index will now be a bit garbled (depending on whether 'hasShorterAlternatives' is true, // it has either been incremented by 1 or by (countToCheckForFirstAlternative + 1) ... // but since we're about to return a failure this doesn't really matter!) unsigned frameSize = m_pattern.m_body->m_callFrameSize; if (!m_pattern.m_body->m_hasFixedSize) ++frameSize; if (frameSize) addPtr(Imm32(frameSize * sizeof(void*)), stackPointerRegister); move(Imm32(-1), returnRegister); generateReturn(); } void generateEnter() { #if PLATFORM(X86_64) push(X86::ebp); move(stackPointerRegister, X86::ebp); push(X86::ebx); #elif PLATFORM(X86) push(X86::ebp); move(stackPointerRegister, X86::ebp); // TODO: do we need spill registers to fill the output pointer if there are no sub captures? push(X86::ebx); push(X86::edi); push(X86::esi); // load output into edi (2 = saved ebp + return address). #if COMPILER(MSVC) loadPtr(Address(X86::ebp, 2 * sizeof(void*)), input); loadPtr(Address(X86::ebp, 3 * sizeof(void*)), index); loadPtr(Address(X86::ebp, 4 * sizeof(void*)), length); loadPtr(Address(X86::ebp, 5 * sizeof(void*)), output); #else loadPtr(Address(X86::ebp, 2 * sizeof(void*)), output); #endif #elif PLATFORM(ARM) #if !PLATFORM_ARM_ARCH(7) push(ARM::lr); #endif push(ARM::r4); push(ARM::r5); push(ARM::r6); move(ARM::r3, output); #endif } void generateReturn() { #if PLATFORM(X86_64) pop(X86::ebx); pop(X86::ebp); #elif PLATFORM(X86) pop(X86::esi); pop(X86::edi); pop(X86::ebx); pop(X86::ebp); #elif PLATFORM(ARM) pop(ARM::r6); pop(ARM::r5); pop(ARM::r4); #endif ret(); } public: RegexGenerator(RegexPattern& pattern) : m_pattern(pattern) , m_generationFailed(false) { } void generate() { generateEnter(); // TODO: do I really want this on the stack? if (!m_pattern.m_body->m_hasFixedSize) push(index); if (m_pattern.m_body->m_callFrameSize) subPtr(Imm32(m_pattern.m_body->m_callFrameSize * sizeof(void*)), stackPointerRegister); generateDisjunction(m_pattern.m_body); } void compile(JSGlobalData* globalData, RegexCodeBlock& jitObject) { generate(); LinkBuffer patchBuffer(this, globalData->executableAllocator.poolForSize(size())); for (unsigned i = 0; i < m_backtrackRecords.size(); ++i) patchBuffer.patch(m_backtrackRecords[i].dataLabel, patchBuffer.locationOf(m_backtrackRecords[i].backtrackLocation)); jitObject.set(patchBuffer.finalizeCode()); } bool generationFailed() { return m_generationFailed; } private: RegexPattern& m_pattern; Vector m_backtrackRecords; bool m_generationFailed; }; void jitCompileRegex(JSGlobalData* globalData, RegexCodeBlock& jitObject, const UString& patternString, unsigned& numSubpatterns, const char*& error, bool ignoreCase, bool multiline) { RegexPattern pattern(ignoreCase, multiline); if ((error = compileRegex(patternString, pattern))) return; numSubpatterns = pattern.m_numSubpatterns; RegexGenerator generator(pattern); generator.compile(globalData, jitObject); if (generator.generationFailed()) { JSRegExpIgnoreCaseOption ignoreCaseOption = ignoreCase ? JSRegExpIgnoreCase : JSRegExpDoNotIgnoreCase; JSRegExpMultilineOption multilineOption = multiline ? JSRegExpMultiline : JSRegExpSingleLine; jitObject.setFallback(jsRegExpCompile(reinterpret_cast(patternString.data()), patternString.size(), ignoreCaseOption, multilineOption, &numSubpatterns, &error)); } } int executeRegex(RegexCodeBlock& jitObject, const UChar* input, unsigned start, unsigned length, int* output, int outputArraySize) { if (JSRegExp* fallback = jitObject.getFallback()) return (jsRegExpExecute(fallback, input, length, start, output, outputArraySize) < 0) ? -1 : output[0]; return jitObject.execute(input, start, length, output); } }} #endif