perf(semantic): simplify logic in enter_scope + leave_scope (#4383)

`SemanticBuilder::enter_scope` contained multiple checks purely to handle when called for `Program` scope (where scope has no parent). This is a very uncommon case as `Program` is visited only once, but these checks run for every scope.

So don't call `enter_scope` from `visit_program`, and inline the special logic for root scope there instead, to simplify `enter_scope`.

A branch in `leave_scope` also gets more predictable (always taken).
This commit is contained in:
overlookmotel 2024-07-21 12:33:24 +00:00
parent 000d083c61
commit 402006f28d

View file

@ -123,7 +123,7 @@ impl<'a> SemanticBuilder<'a> {
current_symbol_flags: SymbolFlags::empty(), current_symbol_flags: SymbolFlags::empty(),
current_reference_flag: ReferenceFlag::empty(), current_reference_flag: ReferenceFlag::empty(),
current_scope_id, current_scope_id,
current_scope_depth: 0, current_scope_depth: 1, // Start with depth for `Program`
function_stack: vec![], function_stack: vec![],
namespace_stack: vec![], namespace_stack: vec![],
nodes: AstNodes::default(), nodes: AstNodes::default(),
@ -200,7 +200,7 @@ impl<'a> SemanticBuilder<'a> {
} }
} }
debug_assert_eq!(self.current_scope_depth, 0); debug_assert_eq!(self.current_scope_depth, 1);
self.scope.root_unresolved_references = self.scope.root_unresolved_references =
self.unresolved_references.into_iter().next().unwrap(); self.unresolved_references.into_iter().next().unwrap();
@ -460,32 +460,27 @@ impl<'a> SemanticBuilder<'a> {
} }
impl<'a> Visit<'a> for SemanticBuilder<'a> { impl<'a> Visit<'a> for SemanticBuilder<'a> {
// NB: Not called for `Program`
fn enter_scope(&mut self, flags: ScopeFlags, scope_id: &Cell<Option<ScopeId>>) { fn enter_scope(&mut self, flags: ScopeFlags, scope_id: &Cell<Option<ScopeId>>) {
let parent_scope_id = let parent_scope_id = self.current_scope_id;
if flags.contains(ScopeFlags::Top) { None } else { Some(self.current_scope_id) };
let mut flags = flags; let mut flags = flags;
if !flags.is_strict_mode() && self.current_node_flags.has_class() { if !flags.is_strict_mode() && self.current_node_flags.has_class() {
// NOTE A class definition is always strict mode code. // NOTE A class definition is always strict mode code.
flags |= ScopeFlags::StrictMode; flags |= ScopeFlags::StrictMode;
}; };
if let Some(parent_scope_id) = parent_scope_id {
flags = self.scope.get_new_scope_flags(flags, parent_scope_id); flags = self.scope.get_new_scope_flags(flags, parent_scope_id);
}
self.current_scope_id = self.scope.add_scope(parent_scope_id, self.current_node_id, flags); self.current_scope_id =
self.scope.add_scope(Some(parent_scope_id), self.current_node_id, flags);
scope_id.set(Some(self.current_scope_id)); scope_id.set(Some(self.current_scope_id));
if let Some(parent_scope_id) = parent_scope_id {
if self.scope.get_flags(parent_scope_id).is_catch_clause() { if self.scope.get_flags(parent_scope_id).is_catch_clause() {
// Clone the `CatchClause` bindings and add them to the current scope. // Clone the `CatchClause` bindings and add them to the current scope.
// to make it easier to check redeclare errors. // to make it easier to check redeclare errors.
let bindings = self.scope.get_bindings(parent_scope_id).clone(); let bindings = self.scope.get_bindings(parent_scope_id).clone();
self.scope.get_bindings_mut(self.current_scope_id).extend(bindings); self.scope.get_bindings_mut(self.current_scope_id).extend(bindings);
} }
}
// Increment scope depth, and ensure stack is large enough that // Increment scope depth, and ensure stack is large enough that
// `self.unresolved_references[self.current_scope_depth]` is initialized // `self.unresolved_references[self.current_scope_depth]` is initialized
@ -495,16 +490,25 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
} }
} }
// NB: Not called for `Program`
fn leave_scope(&mut self) { fn leave_scope(&mut self) {
self.resolve_references_for_current_scope(); self.resolve_references_for_current_scope();
if let Some(parent_id) = self.scope.get_parent_id(self.current_scope_id) {
// `get_parent_id` always returns `Some` because this method is not called for `Program`.
// So we could `.unwrap()` here. But that seems to produce a small perf impact, probably because
// `leave_scope` then doesn't get inlined because of its larger size due to the panic code.
let parent_id = self.scope.get_parent_id(self.current_scope_id);
debug_assert!(parent_id.is_some());
if let Some(parent_id) = parent_id {
self.current_scope_id = parent_id; self.current_scope_id = parent_id;
} }
self.current_scope_depth -= 1; self.current_scope_depth -= 1;
} }
// Setup all the context for the binder. // Setup all the context for the binder.
// The order is important here. // The order is important here.
// NB: Not called for `Program`.
fn enter_node(&mut self, kind: AstKind<'a>) { fn enter_node(&mut self, kind: AstKind<'a>) {
self.create_ast_node(kind); self.create_ast_node(kind);
self.enter_kind(kind); self.enter_kind(kind);
@ -542,16 +546,15 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
); );
self.record_ast_node(); self.record_ast_node();
self.enter_scope( // Don't call `enter_scope` here as `Program` is a special case - scope has no `parent_id`.
{ // Inline the specific logic for `Program` here instead.
// This simplifies logic in `enter_scope`, as it doesn't have to handle the special case.
let mut flags = ScopeFlags::Top; let mut flags = ScopeFlags::Top;
if program.is_strict() { if program.is_strict() {
flags |= ScopeFlags::StrictMode; flags |= ScopeFlags::StrictMode;
} }
flags self.current_scope_id = self.scope.add_scope(None, self.current_node_id, flags);
}, program.scope_id.set(Some(self.current_scope_id));
&program.scope_id,
);
if let Some(hashbang) = &program.hashbang { if let Some(hashbang) = &program.hashbang {
self.visit_hashbang(hashbang); self.visit_hashbang(hashbang);
@ -567,7 +570,10 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
control_flow!(self, |cfg| cfg.release_error_harness(error_harness)); control_flow!(self, |cfg| cfg.release_error_harness(error_harness));
/* cfg */ /* cfg */
self.leave_scope(); // Don't call `leave_scope` here as `Program` is a special case - scope has no `parent_id`.
// This simplifies `leave_scope`.
self.resolve_references_for_current_scope();
self.leave_node(kind); self.leave_node(kind);
} }