tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 9fe576173cabda0b21a4d8bc95042582f22a6294
parent 7af176bdec888661bee2b8aa9c2cb2f823c11655
Author: Henri Sivonen <hsivonen@hsivonen.fi>
Date:   Mon, 17 Nov 2025 18:53:26 +0000

Bug 1997049 - Accelerate more HTML tokenizer states with SIMD. r=smaug

This restores the exact old structure for Java, and only injects the
SIMD acceleration between incrementing pos and checking if pos reached
endPos.

This makes going back to SIMD after a character reference significantly
simpler. The downside is that wholly-non-BMP text (as opposed to
isolated non-BMP emoji or Hanzi) ends up uselessly bouncing to the SIMD
code without benefiting from it when loading from network and counting
column numbers as Unicode scalar values.

If we want to avoid this failure mode, we should change column numbers
to count UTF-16 code units instead of scalars. Either way, the column
is "wrong" in some cases.

Differential Revision: https://phabricator.services.mozilla.com/D270673

Diffstat:
Mparser/html/javasrc/Tokenizer.java | 359+++++++++++++++++++++++++++++--------------------------------------------------
Mparser/html/nsHtml5Tokenizer.h | 290+++++++++++++++++++++++++++++++------------------------------------------------
Mparser/html/nsHtml5TokenizerLoopPoliciesALU.h | 135+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mparser/html/nsHtml5TokenizerLoopPoliciesSIMD.h | 429+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mparser/htmlaccel/htmlaccel.h | 62+++++++++++++++++++++++++++++++++++++++++++++++++-------------
Mparser/htmlaccel/htmlaccelNotInline.cpp | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mparser/htmlaccel/htmlaccelNotInline.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 1084 insertions(+), 421 deletions(-)

diff --git a/parser/html/javasrc/Tokenizer.java b/parser/html/javasrc/Tokenizer.java @@ -1631,127 +1631,58 @@ public class Tokenizer implements Locator, Locator2 { switch (state) { case DATA: dataloop: for (;;) { - // Ideally this reconsume block would be a separate state, DATA_RECONSUME above this one - // with fallthrough into this state. However, such a change would be disruptive to - // TransitionHandler and everything that works with returnState. if (reconsume) { reconsume = false; - // This is a manual copy of the switch below with break/continue - // adjusted as relevant. Make sure to keep in sync with the switch below! - switch (c) { - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in data state. - */ - flushChars(buf, pos); - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\u0000'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the tag - * open state. - */ - flushChars(buf, pos); - - state = transition(state, Tokenizer.TAG_OPEN, reconsume, pos); - // `break` optimizes; `continue stateloop;` would be valid - break dataloop; - case '\u0000': - maybeEmitReplacementCharacter(buf, pos); - break; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - // CPPONLY: MOZ_FALLTHROUGH; - default: - /* - * Anything else Emit the input character as a - * character token. - * - * Stay in the data state. - */ - break; - } - } - datamiddle: for (;;) { + } else { ++pos; // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. // The line below advances pos by some number of code units that this state is indifferent to. // CPPONLY: pos += accelerateAdvancementData(buf, pos, endPos); - for (;;) { - if (pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - // Make sure to keep in sync with the switch above in the reconsume block! - switch (c) { - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in data state. - */ - flushChars(buf, pos); - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\u0000'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the tag - * open state. - */ - flushChars(buf, pos); - - state = transition(state, Tokenizer.TAG_OPEN, reconsume, pos); - // `break` optimizes; `continue stateloop;` would be valid - break dataloop; - case '\u0000': - maybeEmitReplacementCharacter(buf, pos); - // Continue from above the accelerateAdvancementData call. - continue datamiddle; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - // Continue from above the accelerateAdvancementData call. - continue datamiddle; - default: - /* - * Anything else Emit the input character as a - * character token. - * - * Stay in the data state. - */ - // Don't go back to accelerateAdvancementData to avoid - // bouncing back and forth in a way that doesn't make good - // use of SIMD when we have less than a SIMD stride to go - // or when we come here due to a non-BMP characters. - // The SIMD code doesn't have ALU handling for the remainder - // that is shorter than a SIMD stride, because this case - // in this switch has to exist anyway (for SIMD-unavailable - // and for non-BMP cases) and this innermost loop can serve - // that purpose, too. In the non-BMP case we stay on the - // ALU path until we end up in one of the other cases in this - // switch (e.g. end of line) in order to avoid bouncing back - // and forth when we have text in a non-BMP script instead - // of an isolated emoji. - // - // We need to increment pos when staying in this innermost - // loop! - ++pos; - continue; - } + if (pos == endPos) { + break stateloop; } + c = checkChar(buf, pos); + } + switch (c) { + case '&': + /* + * U+0026 AMPERSAND (&) Switch to the character + * reference in data state. + */ + flushChars(buf, pos); + assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; + appendCharRefBuf(c); + setAdditionalAndRememberAmpersandLocation('\u0000'); + returnState = state; + state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); + continue stateloop; + case '<': + /* + * U+003C LESS-THAN SIGN (<) Switch to the tag + * open state. + */ + flushChars(buf, pos); + + state = transition(state, Tokenizer.TAG_OPEN, reconsume, pos); + // `break` optimizes; `continue stateloop;` would be valid + break dataloop; + case '\u0000': + maybeEmitReplacementCharacter(buf, pos); + continue; + case '\r': + emitCarriageReturn(buf, pos); + break stateloop; + case '\n': + silentLineFeed(); + // CPPONLY: MOZ_FALLTHROUGH; + default: + /* + * Anything else Emit the input character as a + * character token. + * + * Stay in the data state. + */ + continue; } } // CPPONLY: MOZ_FALLTHROUGH; @@ -2282,7 +2213,11 @@ public class Tokenizer implements Locator, Locator2 { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // CPPONLY: pos += accelerateAdvancementAttributeValueDoubleQuoted(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); @@ -2779,7 +2714,11 @@ public class Tokenizer implements Locator, Locator2 { // CPPONLY: MOZ_FALLTHROUGH; case COMMENT: commentloop: for (;;) { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // CPPONLY: pos += accelerateAdvancementComment(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); @@ -3275,7 +3214,11 @@ public class Tokenizer implements Locator, Locator2 { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // CPPONLY: pos += accelerateAdvancementCdataSection(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); @@ -3362,7 +3305,11 @@ public class Tokenizer implements Locator, Locator2 { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // CPPONLY: pos += accelerateAdvancementAttributeValueSingleQuoted(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); @@ -3974,7 +3921,11 @@ public class Tokenizer implements Locator, Locator2 { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // CPPONLY: pos += accelerateAdvancementPlaintext(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); @@ -4083,122 +4034,57 @@ public class Tokenizer implements Locator, Locator2 { // no fallthrough, reordering opportunity case RCDATA: rcdataloop: for (;;) { - // Ideally this reconsume block would be a separate state, RCDATA_RECONSUME above this one - // with fallthrough into this state. However, such a change would be disruptive to - // TransitionHandler and everything that works with returnState. if (reconsume) { reconsume = false; - // This is a manual copy of the switch below with break/continue - // adjusted as relevant. Make sure to keep in sync with the switch below! - switch (c) { - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in RCDATA state. - */ - flushChars(buf, pos); - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\u0000'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * RCDATA less-than sign state. - */ - flushChars(buf, pos); - returnState = state; - state = transition(state, Tokenizer.RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos); - continue stateloop; - case '\u0000': - maybeEmitReplacementCharacter(buf, pos); - break; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - // CPPONLY: MOZ_FALLTHROUGH; - default: - /* - * Emit the current input character as a - * character token. Stay in the RCDATA state. - */ - break; - } - } - rcdatamiddle: for (;;) { + } else { ++pos; // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. // The line below advances pos by some number of code units that this state is indifferent to. // RCDATA and DATA have the same set of characters that they are indifferent to, hence accelerateData. // CPPONLY: pos += accelerateAdvancementData(buf, pos, endPos); - for (;;) { - if (pos == endPos) { - break stateloop; - } - c = checkChar(buf, pos); - // Make sure to keep in sync with the switch above in the reconsume block! - switch (c) { - case '&': - /* - * U+0026 AMPERSAND (&) Switch to the character - * reference in RCDATA state. - */ - flushChars(buf, pos); - assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\u0000'); - returnState = state; - state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); - continue stateloop; - case '<': - /* - * U+003C LESS-THAN SIGN (<) Switch to the - * RCDATA less-than sign state. - */ - flushChars(buf, pos); - returnState = state; - state = transition(state, Tokenizer.RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos); - continue stateloop; - case '\u0000': - maybeEmitReplacementCharacter(buf, pos); - // Continue from above the accelerateAdvancementData call. - continue rcdatamiddle; - case '\r': - emitCarriageReturn(buf, pos); - break stateloop; - case '\n': - silentLineFeed(); - // Continue from above the accelerateAdvancementData call. - continue rcdatamiddle; - default: - /* - * Emit the current input character as a - * character token. Stay in the RCDATA state. - */ - // Don't go back to accelerateAdvancementData to avoid - // bouncing back and forth in a way that doesn't make good - // use of SIMD when we have less than a SIMD stride to go - // or when we come here due to a non-BMP characters. - // The SIMD code doesn't have ALU handling for the remainder - // that is shorter than a SIMD stride, because this case - // in this switch has to exist anyway (for SIMD-unavailable - // and for non-BMP cases) and this innermost loop can serve - // that purpose, too. In the non-BMP case we stay on the - // ALU path until we end up in one of the other cases in this - // switch (e.g. end of line) in order to avoid bouncing back - // and forth when we have text in a non-BMP script instead - // of an isolated emoji. - // - // We need to increment pos when staying in this innermost - // loop! - ++pos; - continue; - } + if (pos == endPos) { + break stateloop; } + c = checkChar(buf, pos); + } + switch (c) { + case '&': + /* + * U+0026 AMPERSAND (&) Switch to the character + * reference in RCDATA state. + */ + flushChars(buf, pos); + assert charRefBufLen == 0: "charRefBufLen not reset after previous use!"; + appendCharRefBuf(c); + setAdditionalAndRememberAmpersandLocation('\u0000'); + returnState = state; + state = transition(state, Tokenizer.CONSUME_CHARACTER_REFERENCE, reconsume, pos); + continue stateloop; + case '<': + /* + * U+003C LESS-THAN SIGN (<) Switch to the + * RCDATA less-than sign state. + */ + flushChars(buf, pos); + + returnState = state; + state = transition(state, Tokenizer.RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, pos); + continue stateloop; + case '\u0000': + emitReplacementCharacter(buf, pos); + continue; + case '\r': + emitCarriageReturn(buf, pos); + break stateloop; + case '\n': + silentLineFeed(); + // CPPONLY: MOZ_FALLTHROUGH; + default: + /* + * Emit the current input character as a + * character token. Stay in the RCDATA state. + */ + continue; } } // no fallthrough, reordering opportunity @@ -4207,7 +4093,11 @@ public class Tokenizer implements Locator, Locator2 { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // CPPONLY: pos += accelerateAdvancementRawtext(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); @@ -4491,7 +4381,12 @@ public class Tokenizer implements Locator, Locator2 { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // Using `accelerateAdvancementRawtext`, because this states has the same characters of interest as RAWTEXT. + // CPPONLY: pos += accelerateAdvancementRawtext(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); @@ -4687,7 +4582,11 @@ public class Tokenizer implements Locator, Locator2 { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + // Perhaps at some point, it will be appropriate to do SIMD in Java, but not today. + // The line below advances pos by some number of code units that this state is indifferent to. + // CPPONLY: pos += accelerateAdvancementScriptDataEscaped(buf, pos, endPos); + if (pos == endPos) { break stateloop; } c = checkChar(buf, pos); diff --git a/parser/html/nsHtml5Tokenizer.h b/parser/html/nsHtml5Tokenizer.h @@ -449,91 +449,49 @@ class nsHtml5Tokenizer { for (;;) { if (reconsume) { reconsume = false; - switch (c) { - case '&': { - flushChars(buf, pos); - MOZ_ASSERT(!charRefBufLen, - "charRefBufLen not reset after previous use!"); - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\0'); - returnState = state; - state = P::transition( - mViewSource.get(), - nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, reconsume, - pos); - NS_HTML5_CONTINUE(stateloop); - } - case '<': { - flushChars(buf, pos); - state = - P::transition(mViewSource.get(), - nsHtml5Tokenizer::TAG_OPEN, reconsume, pos); - NS_HTML5_BREAK(dataloop); - } - case '\0': { - maybeEmitReplacementCharacter(buf, pos); - break; - } - case '\r': { - emitCarriageReturn<P>(buf, pos); - NS_HTML5_BREAK(stateloop); - } - case '\n': { - P::silentLineFeed(this); - [[fallthrough]]; - } - default: { - break; - } - } - } - datamiddle: - for (;;) { + } else { ++pos; pos += P::accelerateAdvancementData(this, buf, pos, endPos); - for (;;) { - if (pos == endPos) { - NS_HTML5_BREAK(stateloop); - } - c = P::checkChar(this, buf, pos); - switch (c) { - case '&': { - flushChars(buf, pos); - MOZ_ASSERT(!charRefBufLen, - "charRefBufLen not reset after previous use!"); - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\0'); - returnState = state; - state = P::transition( - mViewSource.get(), - nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, - reconsume, pos); - NS_HTML5_CONTINUE(stateloop); - } - case '<': { - flushChars(buf, pos); - state = P::transition(mViewSource.get(), - nsHtml5Tokenizer::TAG_OPEN, reconsume, - pos); - NS_HTML5_BREAK(dataloop); - } - case '\0': { - maybeEmitReplacementCharacter(buf, pos); - NS_HTML5_CONTINUE(datamiddle); - } - case '\r': { - emitCarriageReturn<P>(buf, pos); - NS_HTML5_BREAK(stateloop); - } - case '\n': { - P::silentLineFeed(this); - NS_HTML5_CONTINUE(datamiddle); - } - default: { - ++pos; - continue; - } - } + if (pos == endPos) { + NS_HTML5_BREAK(stateloop); + } + c = P::checkChar(this, buf, pos); + } + switch (c) { + case '&': { + flushChars(buf, pos); + MOZ_ASSERT(!charRefBufLen, + "charRefBufLen not reset after previous use!"); + appendCharRefBuf(c); + setAdditionalAndRememberAmpersandLocation('\0'); + returnState = state; + state = + P::transition(mViewSource.get(), + nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, + reconsume, pos); + NS_HTML5_CONTINUE(stateloop); + } + case '<': { + flushChars(buf, pos); + state = + P::transition(mViewSource.get(), nsHtml5Tokenizer::TAG_OPEN, + reconsume, pos); + NS_HTML5_BREAK(dataloop); + } + case '\0': { + maybeEmitReplacementCharacter(buf, pos); + continue; + } + case '\r': { + emitCarriageReturn<P>(buf, pos); + NS_HTML5_BREAK(stateloop); + } + case '\n': { + P::silentLineFeed(this); + [[fallthrough]]; + } + default: { + continue; } } } @@ -922,7 +880,10 @@ class nsHtml5Tokenizer { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + pos += P::accelerateAdvancementAttributeValueDoubleQuoted( + this, buf, pos, endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); @@ -1347,7 +1308,9 @@ class nsHtml5Tokenizer { } case COMMENT: { for (;;) { - if (++pos == endPos) { + ++pos; + pos += P::accelerateAdvancementComment(this, buf, pos, endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); @@ -1886,7 +1849,10 @@ class nsHtml5Tokenizer { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + pos += + P::accelerateAdvancementCdataSection(this, buf, pos, endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); @@ -1981,7 +1947,10 @@ class nsHtml5Tokenizer { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + pos += P::accelerateAdvancementAttributeValueSingleQuoted( + this, buf, pos, endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); @@ -2454,7 +2423,9 @@ class nsHtml5Tokenizer { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + pos += P::accelerateAdvancementPlaintext(this, buf, pos, endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); @@ -2552,95 +2523,51 @@ class nsHtml5Tokenizer { for (;;) { if (reconsume) { reconsume = false; - switch (c) { - case '&': { - flushChars(buf, pos); - MOZ_ASSERT(!charRefBufLen, - "charRefBufLen not reset after previous use!"); - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\0'); - returnState = state; - state = P::transition( - mViewSource.get(), - nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, reconsume, - pos); - NS_HTML5_CONTINUE(stateloop); - } - case '<': { - flushChars(buf, pos); - returnState = state; - state = P::transition( - mViewSource.get(), - nsHtml5Tokenizer::RAWTEXT_RCDATA_LESS_THAN_SIGN, - reconsume, pos); - NS_HTML5_CONTINUE(stateloop); - } - case '\0': { - maybeEmitReplacementCharacter(buf, pos); - break; - } - case '\r': { - emitCarriageReturn<P>(buf, pos); - NS_HTML5_BREAK(stateloop); - } - case '\n': { - P::silentLineFeed(this); - [[fallthrough]]; - } - default: { - break; - } - } - } - rcdatamiddle: - for (;;) { + } else { ++pos; pos += P::accelerateAdvancementData(this, buf, pos, endPos); - for (;;) { - if (pos == endPos) { - NS_HTML5_BREAK(stateloop); - } - c = P::checkChar(this, buf, pos); - switch (c) { - case '&': { - flushChars(buf, pos); - MOZ_ASSERT(!charRefBufLen, - "charRefBufLen not reset after previous use!"); - appendCharRefBuf(c); - setAdditionalAndRememberAmpersandLocation('\0'); - returnState = state; - state = P::transition( - mViewSource.get(), - nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, - reconsume, pos); - NS_HTML5_CONTINUE(stateloop); - } - case '<': { - flushChars(buf, pos); - returnState = state; - state = P::transition( - mViewSource.get(), - nsHtml5Tokenizer::RAWTEXT_RCDATA_LESS_THAN_SIGN, - reconsume, pos); - NS_HTML5_CONTINUE(stateloop); - } - case '\0': { - maybeEmitReplacementCharacter(buf, pos); - NS_HTML5_CONTINUE(rcdatamiddle); - } - case '\r': { - emitCarriageReturn<P>(buf, pos); - NS_HTML5_BREAK(stateloop); - } - case '\n': { - P::silentLineFeed(this); - NS_HTML5_CONTINUE(rcdatamiddle); - } - default: { - ++pos; - continue; - } - } + if (pos == endPos) { + NS_HTML5_BREAK(stateloop); + } + c = P::checkChar(this, buf, pos); + } + switch (c) { + case '&': { + flushChars(buf, pos); + MOZ_ASSERT(!charRefBufLen, + "charRefBufLen not reset after previous use!"); + appendCharRefBuf(c); + setAdditionalAndRememberAmpersandLocation('\0'); + returnState = state; + state = + P::transition(mViewSource.get(), + nsHtml5Tokenizer::CONSUME_CHARACTER_REFERENCE, + reconsume, pos); + NS_HTML5_CONTINUE(stateloop); + } + case '<': { + flushChars(buf, pos); + returnState = state; + state = P::transition( + mViewSource.get(), + nsHtml5Tokenizer::RAWTEXT_RCDATA_LESS_THAN_SIGN, reconsume, + pos); + NS_HTML5_CONTINUE(stateloop); + } + case '\0': { + emitReplacementCharacter(buf, pos); + continue; + } + case '\r': { + emitCarriageReturn<P>(buf, pos); + NS_HTML5_BREAK(stateloop); + } + case '\n': { + P::silentLineFeed(this); + [[fallthrough]]; + } + default: { + continue; } } } @@ -2650,7 +2577,9 @@ class nsHtml5Tokenizer { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + pos += P::accelerateAdvancementRawtext(this, buf, pos, endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); @@ -2901,7 +2830,9 @@ class nsHtml5Tokenizer { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + pos += P::accelerateAdvancementRawtext(this, buf, pos, endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); @@ -3083,7 +3014,10 @@ class nsHtml5Tokenizer { if (reconsume) { reconsume = false; } else { - if (++pos == endPos) { + ++pos; + pos += P::accelerateAdvancementScriptDataEscaped(this, buf, pos, + endPos); + if (pos == endPos) { NS_HTML5_BREAK(stateloop); } c = P::checkChar(this, buf, pos); diff --git a/parser/html/nsHtml5TokenizerLoopPoliciesALU.h b/parser/html/nsHtml5TokenizerLoopPoliciesALU.h @@ -25,6 +25,51 @@ struct nsHtml5FastestPolicyALU { return 0; } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementRawtext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementScriptDataEscaped(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementComment( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueSingleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueDoubleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementCdataSection( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementPlaintext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static char16_t checkChar( nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos) { return buf[pos]; @@ -60,6 +105,51 @@ struct nsHtml5LineColPolicyALU { return 0; } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementRawtext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementScriptDataEscaped(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementComment( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueSingleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueDoubleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementCdataSection( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementPlaintext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static char16_t checkChar( nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos) { // The name of this method comes from the validator. @@ -130,6 +220,51 @@ struct nsHtml5ViewSourcePolicyALU { return 0; } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementRawtext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementScriptDataEscaped(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementComment( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueSingleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueDoubleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementCdataSection( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementPlaintext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return 0; + } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static char16_t checkChar( nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos) { return buf[pos]; diff --git a/parser/html/nsHtml5TokenizerLoopPoliciesSIMD.h b/parser/html/nsHtml5TokenizerLoopPoliciesSIMD.h @@ -44,6 +44,110 @@ struct nsHtml5FastestPolicySIMD { return mozilla::htmlaccel::AccelerateDataFastest(buf + pos, buf + endPos); } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementRawtext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + // We need to check bounds for the `buf[pos]` access below to be OK. + // Instead of just checking that `pos` isn't equal to `endPos`, let's + // check that have at least one SIMD stride of data in the same branch, + // since if we don't have at least one SIMD stride of data, we don't + // need to proceed. + if (endPos - pos < 16) { + return 0; + } + if (buf[pos] == '<') { + // Quickly handle the <iframe></iframe> case. + return 0; + } + return mozilla::htmlaccel::AccelerateRawtextFastest(buf + pos, + buf + endPos); + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementScriptDataEscaped(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + // This state shares the SIMD part with the comment state, but this + // wrapper needs to differ! + return mozilla::htmlaccel::AccelerateCommentFastest(buf + pos, + buf + endPos); + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementComment( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = mozilla::htmlaccel::AccelerateCommentFastest( + buf + pos, buf + pos + len); + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueSingleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = + mozilla::htmlaccel::AccelerateAttributeValueSingleQuotedFastest( + buf + pos, buf + pos + len); + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueDoubleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = + mozilla::htmlaccel::AccelerateAttributeValueDoubleQuotedFastest( + buf + pos, buf + pos + len); + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementCdataSection( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return mozilla::htmlaccel::AccelerateCdataSectionFastest(buf + pos, + buf + endPos); + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementPlaintext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return mozilla::htmlaccel::AcceleratePlaintextFastest(buf + pos, + buf + endPos); + } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static char16_t checkChar( nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos) { return buf[pos]; @@ -112,6 +216,223 @@ struct nsHtml5LineColPolicySIMD { return advance; } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementRawtext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + // We need to check bounds for the `buf[pos]` access below to be OK. + // Instead of just checking that `pos` isn't equal to `endPos`, let's + // check that have at least one SIMD stride of data in the same branch, + // since if we don't have at least one SIMD stride of data, we don't + // need to proceed. + if (endPos - pos < 16) { + return 0; + } + char16_t c = buf[pos]; + if (c == '<' || c == '\n') { + // Quickly handle the case where there is one tag immediately + // after another and the very first thing in the data is a + // less-than sign and the case where a tag is immediately followed + // by a line feed. + return 0; + } + int32_t advance = + mozilla::htmlaccel::AccelerateRawtextLineCol(buf + pos, buf + endPos); + if (!advance) { + // When the SIMD advance is zero, don't touch the line and col tracking. + return 0; + } + if (MOZ_UNLIKELY(aTokenizer->nextCharOnNewLine)) { + // By changing the line and column here instead + // of doing so eagerly when seeing the line break + // causes the line break itself to be considered + // column-wise at the end of a line. + aTokenizer->line++; + aTokenizer->col = advance; + aTokenizer->nextCharOnNewLine = false; + } else { + aTokenizer->col += advance; + } + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementScriptDataEscaped(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + // This state shares the SIMD part with the comment state, but this + // wrapper needs to differ! + int32_t advance = + mozilla::htmlaccel::AccelerateCommentLineCol(buf + pos, buf + endPos); + if (!advance) { + // When the SIMD advance is zero, don't touch the line and col tracking. + return 0; + } + if (MOZ_UNLIKELY(aTokenizer->nextCharOnNewLine)) { + // By changing the line and column here instead + // of doing so eagerly when seeing the line break + // causes the line break itself to be considered + // column-wise at the end of a line. + aTokenizer->line++; + aTokenizer->col = advance; + aTokenizer->nextCharOnNewLine = false; + } else { + aTokenizer->col += advance; + } + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementComment( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = mozilla::htmlaccel::AccelerateCommentLineCol( + buf + pos, buf + pos + len); + if (!advance) { + // When the SIMD advance is zero, don't touch the line and col tracking. + return 0; + } + if (MOZ_UNLIKELY(aTokenizer->nextCharOnNewLine)) { + // By changing the line and column here instead + // of doing so eagerly when seeing the line break + // causes the line break itself to be considered + // column-wise at the end of a line. + aTokenizer->line++; + aTokenizer->col = advance; + aTokenizer->nextCharOnNewLine = false; + } else { + aTokenizer->col += advance; + } + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueSingleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = + mozilla::htmlaccel::AccelerateAttributeValueSingleQuotedLineCol( + buf + pos, buf + pos + len); + if (!advance) { + // When the SIMD advance is zero, don't touch the line and col tracking. + return 0; + } + if (MOZ_UNLIKELY(aTokenizer->nextCharOnNewLine)) { + // By changing the line and column here instead + // of doing so eagerly when seeing the line break + // causes the line break itself to be considered + // column-wise at the end of a line. + aTokenizer->line++; + aTokenizer->col = advance; + aTokenizer->nextCharOnNewLine = false; + } else { + aTokenizer->col += advance; + } + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueDoubleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = + mozilla::htmlaccel::AccelerateAttributeValueDoubleQuotedLineCol( + buf + pos, buf + pos + len); + if (!advance) { + // When the SIMD advance is zero, don't touch the line and col tracking. + return 0; + } + if (MOZ_UNLIKELY(aTokenizer->nextCharOnNewLine)) { + // By changing the line and column here instead + // of doing so eagerly when seeing the line break + // causes the line break itself to be considered + // column-wise at the end of a line. + aTokenizer->line++; + aTokenizer->col = advance; + aTokenizer->nextCharOnNewLine = false; + } else { + aTokenizer->col += advance; + } + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementCdataSection( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t advance = mozilla::htmlaccel::AccelerateCdataSectionLineCol( + buf + pos, buf + endPos); + if (!advance) { + // When the SIMD advance is zero, don't touch the line and col tracking. + return 0; + } + if (MOZ_UNLIKELY(aTokenizer->nextCharOnNewLine)) { + // By changing the line and column here instead + // of doing so eagerly when seeing the line break + // causes the line break itself to be considered + // column-wise at the end of a line. + aTokenizer->line++; + aTokenizer->col = advance; + aTokenizer->nextCharOnNewLine = false; + } else { + aTokenizer->col += advance; + } + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementPlaintext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t advance = + mozilla::htmlaccel::AcceleratePlaintextLineCol(buf + pos, buf + endPos); + if (!advance) { + // When the SIMD advance is zero, don't touch the line and col tracking. + return 0; + } + if (MOZ_UNLIKELY(aTokenizer->nextCharOnNewLine)) { + // By changing the line and column here instead + // of doing so eagerly when seeing the line break + // causes the line break itself to be considered + // column-wise at the end of a line. + aTokenizer->line++; + aTokenizer->col = advance; + aTokenizer->nextCharOnNewLine = false; + } else { + aTokenizer->col += advance; + } + return advance; + } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static char16_t checkChar( nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos) { // The name of this method comes from the validator. @@ -199,6 +520,114 @@ struct nsHtml5ViewSourcePolicySIMD { buf + endPos); } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementRawtext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + // We need to check bounds for the `buf[pos]` access below to be OK. + // Instead of just checking that `pos` isn't equal to `endPos`, let's + // check that have at least one SIMD stride of data in the same branch, + // since if we don't have at least one SIMD stride of data, we don't + // need to proceed. + if (endPos - pos < 16) { + return 0; + } + char16_t c = buf[pos]; + if (c == '<' || c == '\n') { + // Quickly handle the case where there is one tag immediately + // after another and the very first thing in the state is a + // less-than sign and the case where a tag is immediately followed + // by a line feed. + return 0; + } + return mozilla::htmlaccel::AccelerateRawtextViewSource(buf + pos, + buf + endPos); + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementScriptDataEscaped(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + // This state shares the SIMD part with the comment state, but this + // wrapper needs to differ! + return mozilla::htmlaccel::AccelerateCommentViewSource(buf + pos, + buf + endPos); + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementComment( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = mozilla::htmlaccel::AccelerateCommentViewSource( + buf + pos, buf + pos + len); + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueSingleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = + mozilla::htmlaccel::AccelerateAttributeValueSingleQuotedViewSource( + buf + pos, buf + pos + len); + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t + accelerateAdvancementAttributeValueDoubleQuoted(nsHtml5Tokenizer* aTokenizer, + char16_t* buf, int32_t pos, + int32_t endPos) { + int32_t len = endPos - pos; + int32_t strBufAvailable = aTokenizer->strBuf.length - aTokenizer->strBufLen; + if (len > strBufAvailable) { + MOZ_DIAGNOSTIC_ASSERT(false, "strBuf has not been extended correctly."); + len = strBufAvailable; + } + int32_t advance = + mozilla::htmlaccel::AccelerateAttributeValueDoubleQuotedViewSource( + buf + pos, buf + pos + len); + // States that on the non-SIMD path copy characters to strBuf also + // need to do that in the SIMD case. + nsHtml5ArrayCopy::arraycopy(buf, pos, aTokenizer->strBuf, + aTokenizer->strBufLen, advance); + aTokenizer->strBufLen += advance; + return advance; + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementCdataSection( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return mozilla::htmlaccel::AccelerateCdataSectionViewSource(buf + pos, + buf + endPos); + } + + MOZ_ALWAYS_INLINE_EVEN_DEBUG static int32_t accelerateAdvancementPlaintext( + nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos, + int32_t endPos) { + return mozilla::htmlaccel::AcceleratePlaintextViewSource(buf + pos, + buf + endPos); + } + MOZ_ALWAYS_INLINE_EVEN_DEBUG static char16_t checkChar( nsHtml5Tokenizer* aTokenizer, char16_t* buf, int32_t pos) { return buf[pos]; diff --git a/parser/htmlaccel/htmlaccel.h b/parser/htmlaccel/htmlaccel.h @@ -231,6 +231,10 @@ const uint8x16_t SURROGATE_MASK = {0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, const uint8x16_t SURROGATE_MATCH = {0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8}; +const uint8x16_t HYPHENS = {'-', '-', '-', '-', '-', '-', '-', '-', + '-', '-', '-', '-', '-', '-', '-', '-'}; +const uint8x16_t RSQBS = {']', ']', ']', ']', ']', ']', ']', ']', + ']', ']', ']', ']', ']', ']', ']', ']'}; // The approach here supports disallowing up to 16 different // characters that 1) are in the Latin1 range, i.e. U+00FF or @@ -262,12 +266,36 @@ const uint8x16_t LT_GT_AMP_NBSP = {0xA0, 2, 1, 1, 1, 1, '&', 1, /// quote. const uint8x16_t LT_GT_AMP_NBSP_QUOT = {0xA0, 2, '"', 1, 1, 1, '&', 1, 1, 1, 1, 1, '<', 1, '>', 1}; +/// Disallow U+0000, less-than, and carriage return. +const uint8x16_t ZERO_LT_CR = {0, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, '<', '\r', 1, 1}; +/// Disallow U+0000, less-than, carriage return, and line feed. +const uint8x16_t ZERO_LT_CR_LF = {0, 2, 1, 1, 1, 1, 1, 1, + 1, 1, '\n', 1, '<', '\r', 1, 1}; +/// Disallow U+0000, single quote, ampersand, and carriage return. +const uint8x16_t ZERO_APOS_AMP_CR = {0, 2, 1, 1, 1, 1, '&', '\'', + 1, 1, 1, 1, 1, '\r', 1, 1}; +/// Disallow U+0000, single quote, ampersand, carriage return, and line feed. +const uint8x16_t ZERO_APOS_AMP_CR_LF = {0, 2, 1, 1, 1, 1, '&', '\'', + 1, 1, '\n', 1, 1, '\r', 1, 1}; +/// Disallow U+0000, double quote, ampersand, and carriage return. +const uint8x16_t ZERO_QUOT_AMP_CR = {0, 2, '"', 1, 1, 1, '&', 1, + 1, 1, 1, 1, 1, '\r', 1, 1}; +/// Disallow U+0000, single quote, ampersand, carriage return, and line feed. +const uint8x16_t ZERO_QUOT_AMP_CR_LF = {0, 2, '"', 1, 1, 1, '&', 1, + 1, 1, '\n', 1, 1, '\r', 1, 1}; +/// Disallow U+0000 and carriage return. +const uint8x16_t ZERO_CR = {0, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, '\r', 1, 1}; +/// Disallow U+0000, carriage return, and line feed. +const uint8x16_t ZERO_CR_LF = {0, 2, 1, 1, 1, 1, 1, 1, + 1, 1, '\n', 1, 1, '\r', 1, 1}; /// Compute a 16-lane mask for for 16 UTF-16 code units, where a lane /// is 0x00 if OK to skip and 0xFF in not OK to skip. MOZ_ALWAYS_INLINE_EVEN_DEBUG uint8x16_t StrideToMask(const char16_t* aArr /* len = 16 */, uint8x16_t aTable, - bool aAllowSurrogates) { + bool aAllowSurrogates = true, bool aAllowHyphen = true, + bool aAllowRightSquareBracket = true) { uint8x16_t first; uint8x16_t second; // memcpy generates a single unaligned load instruction with both ISAs. @@ -287,6 +315,12 @@ StrideToMask(const char16_t* aArr /* len = 16 */, uint8x16_t aTable, uint8x16_t high_half_matches = high_halves == ALL_ZEROS; uint8x16_t low_half_matches = low_halves == TableLookup(aTable, low_halves & NIBBLE_MASK); + if (!aAllowHyphen) { // Assumed to be constant-propagated + low_half_matches |= low_halves == HYPHENS; + } + if (!aAllowRightSquareBracket) { // Assumed to be constant-propagated + low_half_matches |= low_halves == RSQBS; + } uint8x16_t ret = low_half_matches & high_half_matches; if (!aAllowSurrogates) { // Assumed to be constant-propagated ret |= (high_halves & SURROGATE_MASK) == SURROGATE_MATCH; @@ -296,10 +330,12 @@ StrideToMask(const char16_t* aArr /* len = 16 */, uint8x16_t aTable, /// Compute a 16-lane mask for for 16 Latin1 code units, where a lane /// is 0x00 if OK to skip and 0xFF in not OK to skip. -/// `aAllowSurrogates` exist for signature compatibility with the UTF-16 -/// case and is unused. -MOZ_ALWAYS_INLINE_EVEN_DEBUG uint8x16_t StrideToMask( - const char* aArr /* len = 16 */, uint8x16_t aTable, bool aAllowSurrogates) { +/// The boolean arguments exist for signature compatibility with the UTF-16 +/// case and are unused in the Latin1 case. +MOZ_ALWAYS_INLINE_EVEN_DEBUG uint8x16_t +StrideToMask(const char* aArr /* len = 16 */, uint8x16_t aTable, + bool aAllowSurrogates = true, bool aAllowHyphen = true, + bool aAllowRightSquareBracket = true) { uint8x16_t stride; // memcpy generates a single unaligned load instruction with both ISAs. memcpy(&stride, aArr, 16); @@ -308,13 +344,14 @@ MOZ_ALWAYS_INLINE_EVEN_DEBUG uint8x16_t StrideToMask( } template <typename CharT> -MOZ_ALWAYS_INLINE_EVEN_DEBUG size_t AccelerateTextNode(const CharT* aInput, - const CharT* aEnd, - uint8x16_t aTable, - bool aAllowSurrogates) { +MOZ_ALWAYS_INLINE_EVEN_DEBUG size_t +AccelerateTextNode(const CharT* aInput, const CharT* aEnd, uint8x16_t aTable, + bool aAllowSurrogates = true, bool aAllowHyphen = true, + bool aAllowRightSquareBracket = true) { const CharT* current = aInput; while (aEnd - current >= 16) { - uint8x16_t mask = StrideToMask(current, aTable, aAllowSurrogates); + uint8x16_t mask = StrideToMask(current, aTable, aAllowSurrogates, + aAllowHyphen, aAllowRightSquareBracket); #if defined(__aarch64__) uint8_t max = vmaxvq_u8(mask & INVERTED_ADVANCES); if (max != 0) { @@ -343,8 +380,7 @@ MOZ_ALWAYS_INLINE_EVEN_DEBUG uint32_t CountEscaped(const CharT* aInput, const CharT* current = aInput; while (aEnd - current >= 16) { uint8x16_t mask = StrideToMask( - current, aCountDoubleQuote ? LT_GT_AMP_NBSP_QUOT : LT_GT_AMP_NBSP, - true); + current, aCountDoubleQuote ? LT_GT_AMP_NBSP_QUOT : LT_GT_AMP_NBSP); #if defined(__aarch64__) // Reduce on each iteration to avoid branching for overflow avoidance // on each iteration. @@ -369,7 +405,7 @@ MOZ_ALWAYS_INLINE_EVEN_DEBUG bool ContainsMarkup(const char16_t* aInput, const char16_t* aEnd) { const char16_t* current = aInput; while (aEnd - current >= 16) { - uint8x16_t mask = StrideToMask(current, ZERO_LT_AMP_CR, true); + uint8x16_t mask = StrideToMask(current, ZERO_LT_AMP_CR); #if defined(__aarch64__) uint8_t max = vmaxvq_u8(mask); if (max != 0) { diff --git a/parser/htmlaccel/htmlaccelNotInline.cpp b/parser/htmlaccel/htmlaccelNotInline.cpp @@ -16,6 +16,8 @@ MOZ_NEVER_INLINE bool ContainsMarkup(const char16_t* aPtr, return detail::ContainsMarkup(aPtr, aEnd); } +// HTML Serializer functions + /// Skip over SIMD strides not containing less-than, greater-than, ampersand, /// and no-break space. MOZ_NEVER_INLINE size_t SkipNonEscapedInTextNode(const char16_t* aPtr, @@ -57,6 +59,30 @@ MOZ_NEVER_INLINE uint32_t CountEscapedInAttributeValue(const char16_t* aPtr, return detail::CountEscaped(aPtr, aEnd, true); } +// HTML Tokenizer functions +// +// The "Fastest" cases don't count line numbers and, therefore, don't need +// to be sensitive to line feeds. "ViewSource" and "LineCol" count line +// numbers and, therefore, care about line feeds. +// +// Even the "Fastest" case needs to care about carriage returns in order +// to be able to normalize CR and CRLF to an LF. (CRLF to LF ends up +// finding the LF without SIMD once the CR has been detected using SIMD.) +// +// The three boolean arguments and their defaults are: +// bool aAllowSurrogates = true, +// bool aAllowHyphen = true, +// bool aAllowRightSquareBracket = true, +// +// Parsing from network (the `LineCol` cases) sets aAllowSurrogates +// to false in order to count column numbers by scalar values instead +// of UTF-16 code unit. +// +// The hyphen and the right square bracket share the low 4 bits (0xD) +// with the carriage return, so they need to be special-cased and can't +// be covered byt the lookup table that's used for other characters +// of interest, since the lookup table already needs to contain CR. + /// The innerHTML / DOMParser case for the data state in the HTML parser MOZ_NEVER_INLINE int32_t AccelerateDataFastest(const char16_t* aPtr, const char16_t* aEnd) { @@ -77,4 +103,129 @@ MOZ_NEVER_INLINE int32_t AccelerateDataLineCol(const char16_t* aPtr, false); } +/// The innerHTML / DOMParser case for the RAWTEXT state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateRawtextFastest(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_LT_CR, true); +} + +/// View Source case for the RAWTEXT state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateRawtextViewSource(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_LT_CR_LF, true); +} + +/// Normal network case for the RAWTEXT state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateRawtextLineCol(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_LT_CR_LF, false); +} + +/// The innerHTML / DOMParser case for the comment state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCommentFastest(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_LT_CR, true, + false); +} + +/// View Source case for the comment state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCommentViewSource(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_LT_CR_LF, true, + false); +} + +/// Normal network case for the comment state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCommentLineCol(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_LT_CR_LF, false, + false); +} + +/// The innerHTML / DOMParser case for the attribute value single-quoted state +/// in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueSingleQuotedFastest( + const char16_t* aPtr, const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_APOS_AMP_CR, true); +} + +/// View Source case for the attribute value single-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueSingleQuotedViewSource( + const char16_t* aPtr, const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_APOS_AMP_CR_LF, + true); +} + +/// Normal network case for the attribute value single-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueSingleQuotedLineCol( + const char16_t* aPtr, const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_APOS_AMP_CR_LF, + false); +} + +/// The innerHTML / DOMParser case for the attribute value double-quoted state +/// in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueDoubleQuotedFastest( + const char16_t* aPtr, const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_QUOT_AMP_CR, true); +} + +/// View Source case for the attribute value double-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueDoubleQuotedViewSource( + const char16_t* aPtr, const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_QUOT_AMP_CR_LF, + true); +} + +/// Normal network case for the attribute value double-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueDoubleQuotedLineCol( + const char16_t* aPtr, const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_QUOT_AMP_CR_LF, + false); +} + +/// The innerHTML / DOMParser case for the CDATA section state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateCdataSectionFastest(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_CR, true, true, + false); +} + +/// View Source case for the CDATA section state in the HTML parser +MOZ_NEVER_INLINE int32_t +AccelerateCdataSectionViewSource(const char16_t* aPtr, const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_CR_LF, true, true, + false); +} + +/// Normal network case for the CDATA section state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCdataSectionLineCol(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_CR_LF, false, true, + false); +} + +/// The innerHTML / DOMParser case for the plaintext state in the HTML parser +MOZ_NEVER_INLINE int32_t AcceleratePlaintextFastest(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_CR, true); +} + +/// View Source case for the plaintext state in the HTML parser +MOZ_NEVER_INLINE int32_t AcceleratePlaintextViewSource(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_CR_LF, true); +} + +/// Normal network case for the plaintext state in the HTML parser +MOZ_NEVER_INLINE int32_t AcceleratePlaintextLineCol(const char16_t* aPtr, + const char16_t* aEnd) { + return detail::AccelerateTextNode(aPtr, aEnd, detail::ZERO_CR_LF, false); +} + } // namespace mozilla::htmlaccel diff --git a/parser/htmlaccel/htmlaccelNotInline.h b/parser/htmlaccel/htmlaccelNotInline.h @@ -80,6 +80,85 @@ MOZ_NEVER_INLINE int32_t AccelerateDataViewSource(const char16_t* aPtr, MOZ_NEVER_INLINE int32_t AccelerateDataLineCol(const char16_t* aPtr, const char16_t* aEnd); +/// The innerHTML / DOMParser case for the RAWTEXT state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateRawtextFastest(const char16_t* aPtr, + const char16_t* aEnd); + +/// View Source case for the RAWTEXT state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateRawtextViewSource(const char16_t* aPtr, + const char16_t* aEnd); + +/// Normal network case for the RAWTEXT state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateRawtextLineCol(const char16_t* aPtr, + const char16_t* aEnd); + +/// The innerHTML / DOMParser case for the comment state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCommentFastest(const char16_t* aPtr, + const char16_t* aEnd); + +/// View Source case for the comment state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCommentViewSource(const char16_t* aPtr, + const char16_t* aEnd); + +/// Normal network case for the comment state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCommentLineCol(const char16_t* aPtr, + const char16_t* aEnd); + +/// The innerHTML / DOMParser case for the attribute value single-quoted state +/// in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueSingleQuotedFastest( + const char16_t* aPtr, const char16_t* aEnd); + +/// View Source case for the attribute value single-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueSingleQuotedViewSource( + const char16_t* aPtr, const char16_t* aEnd); + +/// Normal network case for the attribute value single-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueSingleQuotedLineCol( + const char16_t* aPtr, const char16_t* aEnd); + +/// The innerHTML / DOMParser case for the attribute value double-quoted state +/// in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueDoubleQuotedFastest( + const char16_t* aPtr, const char16_t* aEnd); + +/// View Source case for the attribute value double-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueDoubleQuotedViewSource( + const char16_t* aPtr, const char16_t* aEnd); + +/// Normal network case for the attribute value double-quoted state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateAttributeValueDoubleQuotedLineCol( + const char16_t* aPtr, const char16_t* aEnd); + +/// The innerHTML / DOMParser case for the CDATA section state in the HTML +/// parser +MOZ_NEVER_INLINE int32_t AccelerateCdataSectionFastest(const char16_t* aPtr, + const char16_t* aEnd); + +/// View Source case for the CDATA section state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCdataSectionViewSource(const char16_t* aPtr, + const char16_t* aEnd); + +/// Normal network case for the CDATA section state in the HTML parser +MOZ_NEVER_INLINE int32_t AccelerateCdataSectionLineCol(const char16_t* aPtr, + const char16_t* aEnd); + +/// The innerHTML / DOMParser case for the plaintext state in the HTML parser +MOZ_NEVER_INLINE int32_t AcceleratePlaintextFastest(const char16_t* aPtr, + const char16_t* aEnd); + +/// View Source case for the plaintext state in the HTML parser +MOZ_NEVER_INLINE int32_t AcceleratePlaintextViewSource(const char16_t* aPtr, + const char16_t* aEnd); + +/// Normal network case for the plaintext state in the HTML parser +MOZ_NEVER_INLINE int32_t AcceleratePlaintextLineCol(const char16_t* aPtr, + const char16_t* aEnd); + } // namespace mozilla::htmlaccel #endif // mozilla_htmlaccel_htmlaccelNotInline_h