perf(es/parser): Optimize macros (#9100)

**Description:**

I could optimize macros used enormous amount of times.
This commit is contained in:
Donny/강동윤 2024-06-23 23:54:51 +09:00 committed by GitHub
parent f2dc6b09da
commit 719b7c54f6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 156 additions and 97 deletions

View File

@ -175,8 +175,10 @@ impl<'a> Lexer<'a> {
(skip.offset, skip.newline)
};
self.input.bump_bytes(offset);
self.state.had_line_break |= newline;
self.input.bump_bytes(offset as usize);
if newline {
self.state.had_line_break = true;
}
if LEX_COMMENTS && self.input.is_byte(b'/') {
if self.peek() == Some('/') {

View File

@ -1,5 +1,5 @@
/// Returns true if it's done
pub(super) type ByteHandler = Option<for<'aa> fn(&mut SkipWhitespace<'aa>) -> usize>;
pub(super) type ByteHandler = Option<for<'aa> fn(&mut SkipWhitespace<'aa>) -> u32>;
/// Lookup table for whitespace
static BYTE_HANDLERS: [ByteHandler; 256] = [
@ -39,7 +39,7 @@ const SPC: ByteHandler = Some(|_| 1);
const UNI: ByteHandler = Some(|skip| {
let s = unsafe {
// Safety: `skip.offset` is always valid
skip.input.get_unchecked(skip.offset..)
skip.input.get_unchecked(skip.offset as usize..)
};
let c = unsafe {
@ -60,7 +60,7 @@ const UNI: ByteHandler = Some(|skip| {
_ => return 0,
}
c.len_utf8()
c.len_utf8() as u32
});
/// API is taked from oxc by Boshen (https://github.com/Boshen/oxc/pull/26)
@ -68,7 +68,7 @@ pub(super) struct SkipWhitespace<'a> {
pub input: &'a str,
/// Total offset
pub offset: usize,
pub offset: u32,
/// Found newline
pub newline: bool,
@ -79,7 +79,7 @@ impl SkipWhitespace<'_> {
pub fn scan(&mut self) {
let mut byte;
loop {
byte = match self.input.as_bytes().get(self.offset).copied() {
byte = match self.input.as_bytes().get(self.offset as usize).copied() {
Some(v) => v,
None => return,
};

View File

@ -129,7 +129,7 @@ impl<I: Tokens> Parser<I> {
return self.parse_yield_expr();
}
self.state.potential_arrow_start = match *cur!(self, true)? {
self.state.potential_arrow_start = match *cur!(self, true) {
Word(Word::Ident(..)) | tok!('(') | tok!("yield") => Some(cur_pos!(self)),
_ => None,
};
@ -154,8 +154,8 @@ impl<I: Tokens> Parser<I> {
fn finish_assignment_expr(&mut self, start: BytePos, cond: Box<Expr>) -> PResult<Box<Expr>> {
trace_cur!(self, finish_assignment_expr);
match cur!(self, false) {
Ok(&Token::AssignOp(op)) => {
match self.input.cur() {
Some(&Token::AssignOp(op)) => {
let left = if op == AssignOp::Assign {
match AssignTarget::try_from(
self.reparse_expr_as_pat(PatType::AssignPat, cond)?,
@ -882,7 +882,7 @@ impl<I: Tokens> Parser<I> {
type_params: None,
};
if let BlockStmtOrExpr::BlockStmt(..) = &*arrow_expr.body {
if let Ok(&Token::BinOp(..)) = cur!(self, false) {
if let Some(&Token::BinOp(..)) = self.input.cur() {
// ) is required
self.emit_err(self.input.cur_span(), SyntaxError::TS1005);
let errorred_expr =
@ -1063,7 +1063,7 @@ impl<I: Tokens> Parser<I> {
pub(super) fn parse_tpl_element(&mut self, is_tagged_tpl: bool) -> PResult<TplElement> {
let start = cur_pos!(self);
let (raw, cooked) = match *cur!(self, true)? {
let (raw, cooked) = match *cur!(self, true) {
Token::Template { .. } => match bump!(self) {
Token::Template { raw, cooked, .. } => match cooked {
Ok(cooked) => (raw, Some(cooked)),
@ -1558,7 +1558,7 @@ impl<I: Tokens> Parser<I> {
Either::Right(r) => r.into(),
}
}
match *cur!(self, true)? {
match *cur!(self, true) {
Token::JSXText { .. } => {
return self
.parse_jsx_text()
@ -1707,7 +1707,7 @@ impl<I: Tokens> Parser<I> {
let is_async = is!(self, "async")
&& matches!(
peek!(self),
Ok(tok!('(') | tok!("function") | Token::Word(..))
Some(tok!('(') | tok!("function") | Token::Word(..))
);
let start = cur_pos!(self);
@ -2016,7 +2016,7 @@ impl<I: Tokens> Parser<I> {
pub(super) fn parse_lit(&mut self) -> PResult<Lit> {
let start = cur_pos!(self);
let v = match cur!(self, true)? {
let v = match cur!(self, true) {
Word(Word::Null) => {
bump!(self);
let span = span!(self, start);

View File

@ -15,7 +15,7 @@ impl<I: Tokens> Parser<I> {
Err(err) => {
trace_cur!(self, parse_bin_expr__recovery_unary_err);
match cur!(self, true)? {
match cur!(self, true) {
&tok!("in") if ctx.include_in_expr => {
self.emit_err(self.input.cur_span(), SyntaxError::TS1109);

View File

@ -58,12 +58,12 @@ impl<I: Tokens> Parser<I> {
let start = cur_pos!(self);
let w = match cur!(self, true) {
Ok(&Word(..)) => match bump!(self) {
Word(..) => match bump!(self) {
Word(w) => w.into(),
_ => unreachable!(),
},
Ok(&Token::JSXName { .. }) if in_type => match bump!(self) {
Token::JSXName { .. } if in_type => match bump!(self) {
Token::JSXName { name } => name,
_ => unreachable!(),
},
@ -99,7 +99,7 @@ impl<I: Tokens> Parser<I> {
let word = self.parse_with(|p| {
let w = match cur!(p, true) {
Ok(&Word(..)) => match bump!(p) {
&Word(..) => match bump!(p) {
Word(w) => w,
_ => unreachable!(),
},

View File

@ -13,7 +13,7 @@ impl<I: Tokens> Parser<I> {
trace_cur!(self, parse_jsx_ident);
let ctx = self.ctx();
match *cur!(self, true)? {
match *cur!(self, true) {
Token::JSXName { .. } => match bump!(self) {
Token::JSXName { name } => {
let span = self.input.prev_span();
@ -77,7 +77,7 @@ impl<I: Tokens> Parser<I> {
let start = cur_pos!(self);
match *cur!(self, true)? {
match *cur!(self, true) {
tok!('{') => {
let node = self.parse_jsx_expr_container(start)?;
@ -313,7 +313,7 @@ impl<I: Tokens> Parser<I> {
if !self_closing {
'contents: loop {
match *cur!(p, true)? {
match *cur!(p, true) {
Token::JSXTagStart => {
let start = cur_pos!(p);
@ -409,7 +409,7 @@ impl<I: Tokens> Parser<I> {
trace_cur!(self, parse_jsx_element);
debug_assert!(self.input.syntax().jsx());
debug_assert!({ matches!(*cur!(self, true)?, Token::JSXTagStart | tok!('<')) });
debug_assert!({ matches!(*cur!(self, true), Token::JSXTagStart | tok!('<')) });
let start_pos = cur_pos!(self);

View File

@ -18,51 +18,51 @@ macro_rules! unexpected {
macro_rules! is {
($p:expr, BindingIdent) => {{
let ctx = $p.ctx();
match cur!($p, false) {
Ok(&Word(ref w)) => !ctx.is_reserved(w),
match $p.input.cur() {
Some(&Word(ref w)) => !ctx.is_reserved(w),
_ => false,
}
}};
($p:expr, IdentRef) => {{
let ctx = $p.ctx();
match cur!($p, false) {
Ok(&Word(ref w)) => !ctx.is_reserved(w),
match $p.input.cur() {
Some(&Word(ref w)) => !ctx.is_reserved(w),
_ => false,
}
}};
($p:expr,IdentName) => {{
match cur!($p, false) {
Ok(&Word(..)) => true,
match $p.input.cur() {
Some(&Word(..)) => true,
_ => false,
}
}};
($p:expr,Str) => {{
match cur!($p, false) {
Ok(&Token::Str { .. }) => true,
match $p.input.cur() {
Some(&Token::Str { .. }) => true,
_ => false,
}
}};
($p:expr,Num) => {{
match cur!($p, false) {
Ok(&Token::Num { .. }) => true,
match $p.input.cur() {
Some(&Token::Num { .. }) => true,
_ => false,
}
}};
($p:expr,Regex) => {{
match cur!($p, false) {
Ok(&Token::Regex { .. }) => true,
match $p.input.cur() {
Some(&Token::Regex { .. }) => true,
_ => false,
}
}};
($p:expr,BigInt) => {{
match cur!($p, false) {
Ok(&Token::BigInt { .. }) => true,
match $p.input.cur() {
Some(&Token::BigInt { .. }) => true,
_ => false,
}
}};
@ -84,7 +84,7 @@ macro_rules! peeked_is {
($p:expr, BindingIdent) => {{
let ctx = $p.ctx();
match peek!($p) {
Ok(&Word(ref w)) => !ctx.is_reserved(w),
Some(&Word(ref w)) => !ctx.is_reserved(w),
_ => false,
}
}};
@ -92,21 +92,21 @@ macro_rules! peeked_is {
($p:expr, IdentRef) => {{
let ctx = $p.ctx();
match peek!($p) {
Ok(&Word(ref w)) => !ctx.is_reserved(w),
Some(&Word(ref w)) => !ctx.is_reserved(w),
_ => false,
}
}};
($p:expr,IdentName) => {{
match peek!($p) {
Ok(&Word(..)) => true,
Some(&Word(..)) => true,
_ => false,
}
}};
($p:expr, JSXName) => {{
match peek!($p) {
Ok(&Token::JSXName { .. }) => true,
Some(&Token::JSXName { .. }) => true,
_ => false,
}
}};
@ -116,7 +116,7 @@ macro_rules! peeked_is {
}};
($p:expr, $t:tt) => {
match peek!($p).ok() {
match peek!($p) {
Some(&token_including_semi!($t)) => true,
_ => false,
}
@ -150,7 +150,7 @@ macro_rules! assert_and_bump {
$p.input.cur()
);
}
let _ = cur!($p, true)?;
let _ = cur!($p, true);
bump!($p);
}};
}
@ -235,29 +235,13 @@ macro_rules! store {
/// cur!($parser, required:bool)
macro_rules! cur {
($p:expr, $required:expr) => {{
let pos = $p.input.last_pos();
let last = Span::new(pos, pos, Default::default());
let is_err_token = match $p.input.cur() {
Some(&$crate::token::Token::Error(..)) => true,
_ => false,
};
if is_err_token {
match $p.input.bump() {
$crate::token::Token::Error(e) => {
return Err(e);
}
_ => unreachable!(),
}
}
($p:expr, false) => {{
match $p.input.cur() {
Some(c) => Ok(c),
None => {
if $required {
let err = crate::error::Error::new(last, crate::error::SyntaxError::Eof);
return Err(err);
}
let pos = $p.input.last_pos();
let last = Span::new(pos, pos, Default::default());
Err(crate::error::Error::new(
last,
crate::error::SyntaxError::Eof,
@ -265,6 +249,27 @@ macro_rules! cur {
}
}
}};
($p:expr, true) => {{
match $p.input.cur() {
Some(c) => match c {
Token::Error(..) => match $p.input.bump() {
$crate::token::Token::Error(e) => {
return Err(e);
}
_ => unreachable!(),
},
_ => c,
},
None => {
let pos = $p.input.last_pos();
let last = Span::new(pos, pos, Default::default());
let err = crate::error::Error::new(last, crate::error::SyntaxError::Eof);
return Err(err);
}
}
}};
}
macro_rules! peek {
@ -276,15 +281,7 @@ Current token is {:?}",
cur!($p, false),
);
let pos = cur_pos!($p);
let last = Span::new(pos, pos, Default::default());
match $p.input.peek() {
Some(c) => Ok(c),
None => {
let err = crate::error::Error::new(last, crate::error::SyntaxError::Eof);
Err(err)
}
}
$p.input.peek()
}};
}
@ -366,6 +363,21 @@ macro_rules! syntax_error {
($p:expr, $span:expr, $err:expr) => {{
let err = make_error!($p, $span, $err);
{
let is_err_token = match $p.input.cur() {
Some(&$crate::token::Token::Error(..)) => true,
_ => false,
};
if is_err_token {
match $p.input.bump() {
$crate::token::Token::Error(e) => {
$p.emit_error(e);
}
_ => unreachable!(),
}
}
}
if cfg!(feature = "debug") {
tracing::error!(
"Syntax error called from {}:{}:{}\nCurrent token = {:?}",

View File

@ -220,7 +220,7 @@ impl<I: Tokens> Parser<I> {
}
#[cold]
fn emit_err(&self, span: Span, error: SyntaxError) {
fn emit_err(&mut self, span: Span, error: SyntaxError) {
if self.ctx().ignore_error || !self.syntax().early_errors() {
return;
}
@ -229,11 +229,21 @@ impl<I: Tokens> Parser<I> {
}
#[cold]
fn emit_error(&self, error: Error) {
fn emit_error(&mut self, error: Error) {
if self.ctx().ignore_error || !self.syntax().early_errors() {
return;
}
if matches!(self.input.cur(), Some(Token::Error(..))) {
let err = self.input.bump();
match err {
Token::Error(err) => {
self.input_ref().add_error(err);
}
_ => unreachable!(),
}
}
self.input_ref().add_error(error);
}

View File

@ -51,7 +51,7 @@ impl<I: Tokens> Parser<I> {
.parse_with(|p| {
let start = cur_pos!(p);
let v = match *cur!(p, true)? {
let v = match *cur!(p, true) {
Token::Str { .. } => match bump!(p) {
Token::Str { value, raw } => PropName::Str(Str {
span: span!(p, start),

View File

@ -48,7 +48,7 @@ impl<I: Tokens> Parser<I> {
pub(super) fn parse_binding_pat_or_ident(&mut self) -> PResult<Pat> {
trace_cur!(self, parse_binding_pat_or_ident);
match *cur!(self, true)? {
match *cur!(self, true) {
tok!("yield") | Word(..) => self.parse_binding_ident().map(Pat::from),
tok!('[') => self.parse_array_binding_pat(),
tok!('{') => self.parse_object(),

View File

@ -158,7 +158,7 @@ impl<'a, I: Tokens> Parser<I> {
.map(Stmt::from);
}
match cur!(self, true)? {
match cur!(self, true) {
tok!("await") if include_decl => {
if peeked_is!(self, "using") {
assert_and_bump!(self, "await");
@ -306,7 +306,7 @@ impl<'a, I: Tokens> Parser<I> {
tok!("let") if include_decl => {
let strict = self.ctx().strict;
let is_keyword = match peek!(self) {
Ok(t) => t.kind().follows_keyword_let(strict),
Some(t) => t.kind().follows_keyword_let(strict),
_ => false,
};
@ -1134,9 +1134,13 @@ impl<'a, I: Tokens> Parser<I> {
self.with_ctx(ctx).parse_with(|p| {
let start = l.span.lo();
let mut errors = vec![];
for lb in &p.state.labels {
if l.sym == *lb {
p.emit_err(l.span, SyntaxError::DuplicateLabel(l.sym.clone()));
errors.push(Error::new(
l.span,
SyntaxError::DuplicateLabel(l.sym.clone()),
));
}
}
p.state.labels.push(l.sym.clone());
@ -1157,6 +1161,10 @@ impl<'a, I: Tokens> Parser<I> {
p.parse_stmt(false)?
});
for err in errors {
p.emit_error(err);
}
{
let pos = p.state.labels.iter().position(|v| v == &l.sym);
if let Some(pos) = pos {
@ -1238,7 +1246,8 @@ impl<'a, I: Tokens> Parser<I> {
let strict = self.ctx().strict;
if is_one_of!(self, "const", "var")
|| (is!(self, "let") && peek!(self)?.kind().follows_keyword_let(strict))
|| (is!(self, "let")
&& peek!(self).map_or(false, |v| v.kind().follows_keyword_let(strict)))
{
let decl = self.parse_var_stmt(true)?;
@ -1329,7 +1338,7 @@ impl<'a, I: Tokens> Parser<I> {
decls: vec![decl],
});
cur!(self, true)?;
cur!(self, true);
return self.parse_for_each_head(ForHead::UsingDecl(pat));
}

View File

@ -164,7 +164,7 @@ impl<I: Tokens> Parser<I> {
expect!(self, "from");
let str_start = cur_pos!(self);
match *cur!(self, true)? {
match *cur!(self, true) {
Token::Str { .. } => match bump!(self) {
Token::Str { value, raw, .. } => Box::new(Str {
span: span!(self, str_start),
@ -375,7 +375,7 @@ impl<I: Tokens> Parser<I> {
}
if self.input.syntax().typescript() && is!(self, IdentName) {
let sym = match *cur!(self, true)? {
let sym = match *cur!(self, true) {
Token::Word(ref w) => w.clone().into(),
_ => unreachable!(),
};
@ -835,7 +835,7 @@ impl<I: Tokens> Parser<I> {
expect!(self, "from");
let str_start = cur_pos!(self);
let src = match *cur!(self, true)? {
let src = match *cur!(self, true) {
Token::Str { .. } => match bump!(self) {
Token::Str { value, raw, .. } => Box::new(Str {
span: span!(self, str_start),

View File

@ -35,7 +35,7 @@ impl<I: Tokens> Parser<I> {
}
let pos = {
let modifier = match *cur!(self, true)? {
let modifier = match *cur!(self, true) {
Token::Word(ref w @ Word::Ident(..))
| Token::Word(ref w @ Word::Keyword(Keyword::In | Keyword::Const)) => w.cow(),
@ -298,7 +298,7 @@ impl<I: Tokens> Parser<I> {
let arg_span = self.input.cur_span();
let arg = match cur!(self, true)? {
let arg = match cur!(self, true) {
Token::Str { .. } => match bump!(self) {
Token::Str { value, raw } => Str {
span: arg_span,
@ -729,7 +729,7 @@ impl<I: Tokens> Parser<I> {
let start = cur_pos!(self);
// Computed property names are grammar errors in an enum, so accept just string
// literal or identifier.
let id = match *cur!(self, true)? {
let id = match *cur!(self, true) {
Token::Str { .. } => self.parse_lit().map(|lit| match lit {
Lit::Str(s) => TsEnumMemberId::Str(s),
_ => unreachable!(),
@ -876,7 +876,7 @@ impl<I: Tokens> Parser<I> {
let (global, id) = if is!(self, "global") {
let id = self.parse_ident_name()?;
(true, TsModuleName::Ident(id))
} else if matches!(*cur!(self, true)?, Token::Str { .. }) {
} else if matches!(*cur!(self, true), Token::Str { .. }) {
let id = self.parse_lit().map(|lit| match lit {
Lit::Str(s) => TsModuleName::Str(s),
_ => unreachable!(),
@ -1181,7 +1181,7 @@ impl<I: Tokens> Parser<I> {
let start = cur_pos!(self);
expect!(self, "require");
expect!(self, '(');
match *cur!(self, true)? {
match *cur!(self, true) {
Token::Str { .. } => {}
_ => unexpected!(self, "a string literal"),
}
@ -1385,7 +1385,7 @@ impl<I: Tokens> Parser<I> {
};
self.with_ctx(ctx).parse_with(|p| {
// We check if it's valid for it to be a private name when we push it.
let key = match *cur!(p, true)? {
let key = match *cur!(p, true) {
Token::Num { .. } | Token::Str { .. } => p.parse_new_expr(),
_ => p.parse_maybe_private_name().map(|e| match e {
Either::Left(e) => {
@ -2019,7 +2019,7 @@ impl<I: Tokens> Parser<I> {
let start = cur_pos!(self);
match *cur!(self, true)? {
match *cur!(self, true) {
Token::Word(Word::Ident(..))
| tok!("void")
| tok!("yield")
@ -2096,7 +2096,7 @@ impl<I: Tokens> Parser<I> {
bump!(self);
if !matches!(*cur!(self, true)?, Token::Num { .. } | Token::BigInt { .. }) {
if !matches!(*cur!(self, true), Token::Num { .. } | Token::BigInt { .. }) {
unexpected!(self, "numeric literal or bigint literal")
}
@ -2466,7 +2466,7 @@ impl<I: Tokens> Parser<I> {
.map(make_decl_declare)
.map(Some);
} else if is!(p, IdentName) {
let value = match *cur!(p, true)? {
let value = match *cur!(p, true) {
Token::Word(ref w) => w.clone().into(),
_ => unreachable!(),
};
@ -2554,7 +2554,7 @@ impl<I: Tokens> Parser<I> {
bump!(self);
}
if matches!(*cur!(self, true)?, Token::Str { .. }) {
if matches!(*cur!(self, true), Token::Str { .. }) {
return self
.parse_ts_ambient_external_module_decl(start)
.map(From::from)

View File

@ -1,4 +1,10 @@
x Expression expected
,-[$DIR/tests/jsx/errors/adjacent-tags/input.js:1:1]
1 | var x = <div>one</div><div>two</div>;
: ^^^^^
`----
x Unterminated regexp literal
,-[$DIR/tests/jsx/errors/adjacent-tags/input.js:1:1]
1 | var x = <div>one</div><div>two</div>;

View File

@ -4,3 +4,9 @@
1 | var x = /[a-z]/\ux
: ^
`----
x Expected a semicolon
,-[$DIR/tests/test262-parser/fail/6a96389a0cce57e9.js:1:1]
1 | var x = /[a-z]/\ux
: ^^
`----

View File

@ -1,6 +1,12 @@
x Bad character escape sequence, expected 4 hex characters
x Expected unicode escape
,-[$DIR/tests/test262-parser/fail/94535dc25ef762ee.js:1:1]
1 | var x = /[a-z]/\\ux
: ^
: ^
`----
x Expected a semicolon
,-[$DIR/tests/test262-parser/fail/94535dc25ef762ee.js:1:1]
1 | var x = /[a-z]/\\ux
: ^
`----

View File

@ -4,3 +4,9 @@
1 | var x = /[P QR]/\\u0067
: ^
`----
x Expected a semicolon
,-[$DIR/tests/test262-parser/fail/e7087ec92f0f3c44.js:1:1]
1 | var x = /[P QR]/\\u0067
: ^
`----

View File

@ -39,6 +39,8 @@ impl BenchCmd {
let mut cmd = self.build_cmd()?;
cmd.env("RUST_LOG", "off");
cmd.env("CARGO_PROFILE_RELEASE_DEBUG", "true");
cmd.env("CARGO_PROFILE_BENCH_DEBUG", "true");
run_cmd(&mut cmd)
}

View File

@ -26,7 +26,7 @@ pub fn run_cmd(cmd: &mut Command) -> Result<()> {
let status = cmd.status()?;
if !status.success() {
anyhow::bail!("Failed to run cargo clean");
anyhow::bail!("Failed to run cargo command");
}
Ok(())