Skip to content

Commit 5d96425

Browse files
committed
perf(parser): register import / export statements in module record directly (#12807)
Currently when parsing a list of statements in `parse_directives_and_statements`, we: 1. Check on every turn of the loop whether the statement is a `ModuleDeclaration`, and whether at top level. 2. If so, we call `ModuleRecordBuilder::visit_module_declaration`. 3. `visit_module_declaration` branches again on the type of the `ModuleDeclaration`. Instead, remove `ModuleRecordBuilder::visit_module_declaration` method, and instead call individual methods of `ModuleRecordBuilder` for each type of declaration directly, when parsing that particular declaration. This removes the need to check *every* statement in the loop in `parse_directives_and_statements` just in case it's a module declaration. Gives a small perf boost (+0.2%) on parser benchmarks. Might be a little bit more in real world, as this removes an unpredictable branch, which CodSpeed doesn't measure.
1 parent febb4fa commit 5d96425

File tree

4 files changed

+118
-73
lines changed

4 files changed

+118
-73
lines changed

crates/oxc_parser/src/context.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,8 @@ impl StatementContext {
164164
pub(crate) fn is_single_statement(self) -> bool {
165165
!matches!(self, Self::StatementList | Self::TopLevelStatementList)
166166
}
167+
168+
pub(crate) fn is_top_level(self) -> bool {
169+
self == Self::TopLevelStatementList
170+
}
167171
}

crates/oxc_parser/src/js/module.rs

Lines changed: 94 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
55

66
use super::FunctionKind;
77
use crate::{
8-
Context, ParserImpl, diagnostics,
8+
Context, ParserImpl, StatementContext, diagnostics,
99
lexer::Kind,
1010
modifiers::{Modifier, ModifierFlags, ModifierKind, Modifiers},
1111
};
@@ -61,7 +61,11 @@ impl<'a> ParserImpl<'a> {
6161
}
6262

6363
/// Section 16.2.2 Import Declaration
64-
pub(crate) fn parse_import_declaration(&mut self, span: u32) -> Statement<'a> {
64+
pub(crate) fn parse_import_declaration(
65+
&mut self,
66+
span: u32,
67+
should_record_module_record: bool,
68+
) -> Statement<'a> {
6569
let token_after_import = self.cur_token();
6670
let mut identifier_after_import: Option<BindingIdentifier<'_>> =
6771
if self.cur_kind().is_binding_identifier() {
@@ -186,16 +190,20 @@ impl<'a> ParserImpl<'a> {
186190
self.asi();
187191
let span = self.end_span(span);
188192

189-
self.ast
190-
.module_declaration_import_declaration(
191-
span,
192-
specifiers,
193-
source,
194-
phase,
195-
with_clause,
196-
import_kind,
197-
)
198-
.into()
193+
let import_decl = self.ast.alloc_import_declaration(
194+
span,
195+
specifiers,
196+
source,
197+
phase,
198+
with_clause,
199+
import_kind,
200+
);
201+
202+
if should_record_module_record {
203+
self.module_record_builder.visit_import_declaration(&import_decl);
204+
}
205+
206+
Statement::ImportDeclaration(import_decl)
199207
}
200208

201209
// Full Syntax: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#syntax>
@@ -322,21 +330,29 @@ impl<'a> ParserImpl<'a> {
322330
pub(crate) fn parse_ts_export_assignment_declaration(
323331
&mut self,
324332
start_span: u32,
333+
stmt_ctx: StatementContext,
325334
) -> Box<'a, TSExportAssignment<'a>> {
326335
self.expect(Kind::Eq);
327336
let expression = self.parse_assignment_expression_or_higher();
328337
self.asi();
338+
if stmt_ctx.is_top_level() {
339+
self.module_record_builder.found_ts_export();
340+
}
329341
self.ast.alloc_ts_export_assignment(self.end_span(start_span), expression)
330342
}
331343

332344
pub(crate) fn parse_ts_export_namespace(
333345
&mut self,
334346
start_span: u32,
347+
stmt_ctx: StatementContext,
335348
) -> Box<'a, TSNamespaceExportDeclaration<'a>> {
336349
self.expect(Kind::As);
337350
self.expect(Kind::Namespace);
338351
let id = self.parse_identifier_name();
339352
self.asi();
353+
if stmt_ctx.is_top_level() {
354+
self.module_record_builder.found_ts_export();
355+
}
340356
self.ast.alloc_ts_namespace_export_declaration(self.end_span(start_span), id)
341357
}
342358

@@ -345,23 +361,31 @@ impl<'a> ParserImpl<'a> {
345361
&mut self,
346362
span: u32,
347363
mut decorators: Vec<'a, Decorator<'a>>,
364+
stmt_ctx: StatementContext,
348365
) -> Statement<'a> {
349366
self.bump_any(); // bump `export`
350367
let decl = match self.cur_kind() {
351368
// `export import A = B`
352369
Kind::Import => {
353370
let import_span = self.start_span();
354371
self.bump_any();
355-
let stmt = self.parse_import_declaration(import_span);
372+
// Pass `should_record_module_record: false` to prevent an `import` module record
373+
// being created. It's an export not an import.
374+
let stmt = self.parse_import_declaration(import_span, false);
356375
if stmt.is_declaration() {
357-
self.ast.module_declaration_export_named_declaration(
376+
let export_named_decl = self.ast.alloc_export_named_declaration(
358377
self.end_span(span),
359378
Some(stmt.into_declaration()),
360379
self.ast.vec(),
361380
None,
362381
ImportOrExportKind::Value,
363382
NONE,
364-
)
383+
);
384+
if stmt_ctx.is_top_level() {
385+
self.module_record_builder
386+
.visit_export_named_declaration(&export_named_decl);
387+
}
388+
ModuleDeclaration::ExportNamedDeclaration(export_named_decl)
365389
} else {
366390
return self.fatal_error(diagnostics::unexpected_export(stmt.span()));
367391
}
@@ -378,52 +402,56 @@ impl<'a> ParserImpl<'a> {
378402
let modifiers = self.parse_modifiers(false, false);
379403
let class_decl = self.parse_class_declaration(class_span, &modifiers, decorators);
380404
let decl = Declaration::ClassDeclaration(class_decl);
381-
self.ast.module_declaration_export_named_declaration(
405+
let export_named_decl = self.ast.alloc_export_named_declaration(
382406
self.end_span(span),
383407
Some(decl),
384408
self.ast.vec(),
385409
None,
386410
ImportOrExportKind::Value,
387411
NONE,
388-
)
412+
);
413+
if stmt_ctx.is_top_level() {
414+
self.module_record_builder.visit_export_named_declaration(&export_named_decl);
415+
}
416+
ModuleDeclaration::ExportNamedDeclaration(export_named_decl)
389417
}
390418
Kind::Eq if self.is_ts => ModuleDeclaration::TSExportAssignment(
391-
self.parse_ts_export_assignment_declaration(span),
419+
self.parse_ts_export_assignment_declaration(span, stmt_ctx),
392420
),
393421
Kind::As if self.is_ts && self.lexer.peek_token().kind() == Kind::Namespace => {
394422
// `export as namespace ...`
395423
ModuleDeclaration::TSNamespaceExportDeclaration(
396-
self.parse_ts_export_namespace(span),
424+
self.parse_ts_export_namespace(span, stmt_ctx),
397425
)
398426
}
399427
Kind::Default => ModuleDeclaration::ExportDefaultDeclaration(
400-
self.parse_export_default_declaration(span, decorators),
428+
self.parse_export_default_declaration(span, decorators, stmt_ctx),
429+
),
430+
Kind::Star => ModuleDeclaration::ExportAllDeclaration(
431+
self.parse_export_all_declaration(span, stmt_ctx),
432+
),
433+
Kind::LCurly => ModuleDeclaration::ExportNamedDeclaration(
434+
self.parse_export_named_specifiers(span, stmt_ctx),
401435
),
402-
Kind::Star => {
403-
ModuleDeclaration::ExportAllDeclaration(self.parse_export_all_declaration(span))
404-
}
405-
Kind::LCurly => {
406-
ModuleDeclaration::ExportNamedDeclaration(self.parse_export_named_specifiers(span))
407-
}
408436
Kind::Type if self.is_ts => {
409437
let next_kind = self.lexer.peek_token().kind();
410438

411439
match next_kind {
412440
// `export type { ...`
413441
Kind::LCurly => ModuleDeclaration::ExportNamedDeclaration(
414-
self.parse_export_named_specifiers(span),
442+
self.parse_export_named_specifiers(span, stmt_ctx),
415443
),
416444
// `export type * as ...`
417445
Kind::Star => ModuleDeclaration::ExportAllDeclaration(
418-
self.parse_export_all_declaration(span),
446+
self.parse_export_all_declaration(span, stmt_ctx),
419447
),
420448
_ => ModuleDeclaration::ExportNamedDeclaration(
421-
self.parse_export_named_declaration(span, decorators),
449+
self.parse_export_named_declaration(span, decorators, stmt_ctx),
422450
),
423451
}
424452
}
425453
_ => ModuleDeclaration::ExportNamedDeclaration(
426-
self.parse_export_named_declaration(span, decorators),
454+
self.parse_export_named_declaration(span, decorators, stmt_ctx),
427455
),
428456
};
429457
Statement::from(decl)
@@ -440,7 +468,11 @@ impl<'a> ParserImpl<'a> {
440468
// ExportSpecifier :
441469
// ModuleExportName
442470
// ModuleExportName as ModuleExportName
443-
fn parse_export_named_specifiers(&mut self, span: u32) -> Box<'a, ExportNamedDeclaration<'a>> {
471+
fn parse_export_named_specifiers(
472+
&mut self,
473+
span: u32,
474+
stmt_ctx: StatementContext,
475+
) -> Box<'a, ExportNamedDeclaration<'a>> {
444476
let export_kind = self.parse_import_or_export_kind();
445477
self.expect(Kind::LCurly);
446478
let (mut specifiers, _) = self.context(Context::empty(), self.ctx, |p| {
@@ -496,21 +528,26 @@ impl<'a> ParserImpl<'a> {
496528

497529
self.asi();
498530
let span = self.end_span(span);
499-
self.ast.alloc_export_named_declaration(
531+
let export_named_decl = self.ast.alloc_export_named_declaration(
500532
span,
501533
None,
502534
specifiers,
503535
source,
504536
export_kind,
505537
with_clause,
506-
)
538+
);
539+
if stmt_ctx.is_top_level() {
540+
self.module_record_builder.visit_export_named_declaration(&export_named_decl);
541+
}
542+
export_named_decl
507543
}
508544

509545
// export Declaration
510546
fn parse_export_named_declaration(
511547
&mut self,
512548
span: u32,
513549
decorators: Vec<'a, Decorator<'a>>,
550+
stmt_ctx: StatementContext,
514551
) -> Box<'a, ExportNamedDeclaration<'a>> {
515552
let decl_span = self.start_span();
516553
let reserved_ctx = self.ctx;
@@ -525,14 +562,18 @@ impl<'a> ParserImpl<'a> {
525562
ImportOrExportKind::Value
526563
};
527564
self.ctx = reserved_ctx;
528-
self.ast.alloc_export_named_declaration(
565+
let export_named_decl = self.ast.alloc_export_named_declaration(
529566
self.end_span(span),
530567
Some(declaration),
531568
self.ast.vec(),
532569
None,
533570
export_kind,
534571
NONE,
535-
)
572+
);
573+
if stmt_ctx.is_top_level() {
574+
self.module_record_builder.visit_export_named_declaration(&export_named_decl);
575+
}
576+
export_named_decl
536577
}
537578

538579
// export default HoistableDeclaration[~Yield, +Await, +Default]
@@ -542,12 +583,18 @@ impl<'a> ParserImpl<'a> {
542583
&mut self,
543584
span: u32,
544585
decorators: Vec<'a, Decorator<'a>>,
586+
stmt_ctx: StatementContext,
545587
) -> Box<'a, ExportDefaultDeclaration<'a>> {
546588
let exported = self.parse_keyword_identifier(Kind::Default);
547589
let declaration = self.parse_export_default_declaration_kind(decorators);
548590
let exported = ModuleExportName::IdentifierName(exported);
549591
let span = self.end_span(span);
550-
self.ast.alloc_export_default_declaration(span, exported, declaration)
592+
let export_default_decl =
593+
self.ast.alloc_export_default_declaration(span, exported, declaration);
594+
if stmt_ctx.is_top_level() {
595+
self.module_record_builder.visit_export_default_declaration(&export_default_decl);
596+
}
597+
export_default_decl
551598
}
552599

553600
fn parse_export_default_declaration_kind(
@@ -672,7 +719,11 @@ impl<'a> ParserImpl<'a> {
672719
// *
673720
// * as ModuleExportName
674721
// NamedExports
675-
fn parse_export_all_declaration(&mut self, span: u32) -> Box<'a, ExportAllDeclaration<'a>> {
722+
fn parse_export_all_declaration(
723+
&mut self,
724+
span: u32,
725+
stmt_ctx: StatementContext,
726+
) -> Box<'a, ExportAllDeclaration<'a>> {
676727
let export_kind = self.parse_import_or_export_kind();
677728
self.bump_any(); // bump `star`
678729
let exported = self.eat(Kind::As).then(|| self.parse_module_export_name());
@@ -681,7 +732,12 @@ impl<'a> ParserImpl<'a> {
681732
let with_clause = self.parse_import_attributes();
682733
self.asi();
683734
let span = self.end_span(span);
684-
self.ast.alloc_export_all_declaration(span, exported, source, with_clause, export_kind)
735+
let export_all_decl =
736+
self.ast.alloc_export_all_declaration(span, exported, source, with_clause, export_kind);
737+
if stmt_ctx.is_top_level() {
738+
self.module_record_builder.visit_export_all_declaration(&export_all_decl);
739+
}
740+
export_all_decl
685741
}
686742

687743
// ImportSpecifier :

crates/oxc_parser/src/js/statement.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@ impl<'a> ParserImpl<'a> {
4949
}
5050
let stmt = self.parse_statement_list_item(stmt_ctx);
5151

52-
if is_top_level {
53-
if let Some(module_decl) = stmt.as_module_declaration() {
54-
self.module_record_builder.visit_module_declaration(module_decl);
55-
}
56-
}
57-
5852
// Section 11.2.1 Directive Prologue
5953
// The only way to get a correct directive is to parse the statement first and check if it is a string literal.
6054
// All other method are flawed, see test cases in [babel](https://github.com/babel/babel/blob/v7.26.2/packages/babel-parser/test/fixtures/core/categorized/not-directive/input.js)
@@ -109,7 +103,9 @@ impl<'a> ParserImpl<'a> {
109103
&Modifiers::empty(),
110104
self.ast.vec(),
111105
),
112-
Kind::Export => self.parse_export_declaration(self.start_span(), self.ast.vec()),
106+
Kind::Export => {
107+
self.parse_export_declaration(self.start_span(), self.ast.vec(), stmt_ctx)
108+
}
113109
// [+Return] ReturnStatement[?Yield, ?Await]
114110
Kind::Return => self.parse_return_statement(),
115111
Kind::Var => {
@@ -124,7 +120,7 @@ impl<'a> ParserImpl<'a> {
124120
Kind::At => self.parse_decorated_statement(stmt_ctx),
125121
Kind::Let if !self.cur_token().escaped() => self.parse_let(stmt_ctx),
126122
Kind::Async => self.parse_async_statement(self.start_span(), stmt_ctx),
127-
Kind::Import => self.parse_import_statement(),
123+
Kind::Import => self.parse_import_statement(stmt_ctx),
128124
Kind::Const => self.parse_const_statement(stmt_ctx),
129125
Kind::Using if self.is_using_declaration() => self.parse_using_statement(),
130126
Kind::Await if self.is_using_statement() => self.parse_using_statement(),
@@ -705,7 +701,7 @@ impl<'a> ParserImpl<'a> {
705701
}
706702

707703
/// Parse import statement or import expression.
708-
fn parse_import_statement(&mut self) -> Statement<'a> {
704+
fn parse_import_statement(&mut self, stmt_ctx: StatementContext) -> Statement<'a> {
709705
let checkpoint = self.checkpoint();
710706
let span = self.start_span();
711707
self.bump_any();
@@ -714,7 +710,7 @@ impl<'a> ParserImpl<'a> {
714710
self.rewind(checkpoint);
715711
self.parse_expression_or_labeled_statement()
716712
} else {
717-
self.parse_import_declaration(span)
713+
self.parse_import_declaration(span, stmt_ctx.is_top_level())
718714
}
719715
}
720716

@@ -740,7 +736,7 @@ impl<'a> ParserImpl<'a> {
740736
let kind = self.cur_kind();
741737
if kind == Kind::Export {
742738
// Export span.start starts after decorators.
743-
return self.parse_export_declaration(self.start_span(), decorators);
739+
return self.parse_export_declaration(self.start_span(), decorators, stmt_ctx);
744740
}
745741
let modifiers = self.parse_modifiers(false, false);
746742
if self.at(Kind::Class) {

0 commit comments

Comments
 (0)