|
fn enter_kind(&mut self, kind: AstKind<'a>) { |
|
/* cfg */ |
|
control_flow!(self, |cfg| { |
|
match kind { |
|
AstKind::ReturnStatement(_) |
|
| AstKind::BreakStatement(_) |
|
| AstKind::ContinueStatement(_) |
|
| AstKind::ThrowStatement(_) => { /* These types have their own `InstructionKind`. */ |
|
} |
|
it if it.is_statement() => { |
|
cfg.enter_statement(self.current_node_id); |
|
} |
|
_ => { /* ignore the rest */ } |
|
} |
|
}); |
|
/* cfg */ |
|
|
|
match kind { |
|
AstKind::ExportDefaultDeclaration(decl) => { |
|
// Only if the declaration has an id, we mark it as an export |
|
if match &decl.declaration { |
|
ExportDefaultDeclarationKind::FunctionDeclaration(ref func) => { |
|
func.id.is_some() |
|
} |
|
ExportDefaultDeclarationKind::ClassDeclaration(ref class) => class.id.is_some(), |
|
_ => true, |
|
} { |
|
self.current_symbol_flags |= SymbolFlags::Export; |
|
} |
|
} |
|
AstKind::ExportNamedDeclaration(decl) => { |
|
self.current_symbol_flags |= SymbolFlags::Export; |
|
if decl.export_kind.is_type() { |
|
self.current_reference_flag = ReferenceFlag::Type; |
|
} |
|
} |
|
AstKind::ExportSpecifier(s) => { |
|
if self.current_reference_flag.is_type() || s.export_kind.is_type() { |
|
self.current_reference_flag = ReferenceFlag::Type; |
|
} else { |
|
self.current_reference_flag = ReferenceFlag::Read | ReferenceFlag::Type; |
|
} |
|
} |
|
AstKind::ImportSpecifier(specifier) => { |
|
specifier.bind(self); |
|
} |
|
AstKind::ImportDefaultSpecifier(specifier) => { |
|
specifier.bind(self); |
|
} |
|
AstKind::ImportNamespaceSpecifier(specifier) => { |
|
specifier.bind(self); |
|
} |
|
AstKind::TSImportEqualsDeclaration(decl) => { |
|
decl.bind(self); |
|
} |
|
AstKind::VariableDeclarator(decl) => { |
|
decl.bind(self); |
|
self.make_all_namespaces_valuelike(); |
|
} |
|
AstKind::StaticBlock(_) => self.label_builder.enter_function_or_static_block(), |
|
AstKind::Function(func) => { |
|
self.function_stack.push(self.current_node_id); |
|
if func.is_declaration() { |
|
func.bind(self); |
|
} |
|
self.label_builder.enter_function_or_static_block(); |
|
self.make_all_namespaces_valuelike(); |
|
} |
|
AstKind::ArrowFunctionExpression(_) => { |
|
self.function_stack.push(self.current_node_id); |
|
self.make_all_namespaces_valuelike(); |
|
} |
|
AstKind::Class(class) => { |
|
self.current_node_flags |= NodeFlags::Class; |
|
if class.is_declaration() { |
|
class.bind(self); |
|
} |
|
self.make_all_namespaces_valuelike(); |
|
} |
|
AstKind::ClassBody(body) => { |
|
self.class_table_builder.declare_class_body( |
|
body, |
|
self.current_node_id, |
|
&self.nodes, |
|
); |
|
} |
|
AstKind::PrivateIdentifier(ident) => { |
|
self.class_table_builder.add_private_identifier_reference( |
|
ident, |
|
self.current_node_id, |
|
&self.nodes, |
|
); |
|
} |
|
AstKind::BindingRestElement(element) => { |
|
element.bind(self); |
|
} |
|
AstKind::FormalParameter(param) => { |
|
param.bind(self); |
|
} |
|
AstKind::CatchParameter(param) => { |
|
param.bind(self); |
|
} |
|
AstKind::TSModuleDeclaration(module_declaration) => { |
|
module_declaration.bind(self); |
|
let symbol_id = self |
|
.scope |
|
.get_bindings(self.current_scope_id) |
|
.get(module_declaration.id.name().as_str()); |
|
self.namespace_stack.push(*symbol_id.unwrap()); |
|
} |
|
AstKind::TSTypeAliasDeclaration(type_alias_declaration) => { |
|
type_alias_declaration.bind(self); |
|
} |
|
AstKind::TSInterfaceDeclaration(interface_declaration) => { |
|
interface_declaration.bind(self); |
|
} |
|
AstKind::TSEnumDeclaration(enum_declaration) => { |
|
enum_declaration.bind(self); |
|
// TODO: const enum? |
|
self.make_all_namespaces_valuelike(); |
|
} |
|
AstKind::TSEnumMember(enum_member) => { |
|
enum_member.bind(self); |
|
} |
|
AstKind::TSTypeParameter(type_parameter) => { |
|
type_parameter.bind(self); |
|
} |
|
AstKind::TSInterfaceHeritage(_) => { |
|
self.current_reference_flag = ReferenceFlag::Type; |
|
} |
|
AstKind::TSTypeQuery(_) => { |
|
// type A = typeof a; |
|
// ^^^^^^^^ |
|
self.current_reference_flag = ReferenceFlag::Read | ReferenceFlag::TSTypeQuery; |
|
} |
|
AstKind::TSTypeName(_) => { |
|
match self.nodes.parent_kind(self.current_node_id) { |
|
Some( |
|
// import A = a; |
|
// ^ |
|
AstKind::TSModuleReference(_), |
|
) => { |
|
self.current_reference_flag = ReferenceFlag::Read; |
|
} |
|
Some(AstKind::TSQualifiedName(_)) => { |
|
// import A = a.b |
|
// ^^^ Keep the current reference flag |
|
} |
|
_ => { |
|
if !self.current_reference_flag.is_ts_type_query() { |
|
self.current_reference_flag = ReferenceFlag::Type; |
|
} |
|
} |
|
} |
|
} |
|
AstKind::IdentifierReference(ident) => { |
|
self.reference_identifier(ident); |
|
} |
|
AstKind::JSXIdentifier(ident) => { |
|
self.reference_jsx_identifier(ident); |
|
} |
|
AstKind::UpdateExpression(_) => { |
|
if !self.current_reference_flag.is_type() |
|
&& self.is_not_expression_statement_parent() |
|
{ |
|
self.current_reference_flag |= ReferenceFlag::Read; |
|
} |
|
self.current_reference_flag |= ReferenceFlag::Write; |
|
} |
|
AstKind::AssignmentExpression(expr) => { |
|
if expr.operator != AssignmentOperator::Assign |
|
|| self.is_not_expression_statement_parent() |
|
{ |
|
self.current_reference_flag |= ReferenceFlag::Read; |
|
} |
|
} |
|
AstKind::MemberExpression(_) => { |
|
if !self.current_reference_flag.is_type() { |
|
self.current_reference_flag = ReferenceFlag::Read; |
|
} |
|
} |
|
AstKind::AssignmentTarget(_) => { |
|
self.current_reference_flag |= ReferenceFlag::Write; |
|
} |
|
AstKind::LabeledStatement(stmt) => { |
|
self.label_builder.enter(stmt, self.current_node_id); |
|
} |
|
AstKind::ContinueStatement(ContinueStatement { label, .. }) |
|
| AstKind::BreakStatement(BreakStatement { label, .. }) => { |
|
if let Some(label) = &label { |
|
self.label_builder.mark_as_used(label); |
|
} |
|
} |
|
AstKind::YieldExpression(_) => { |
|
self.set_function_node_flag(NodeFlags::HasYield); |
|
} |
|
_ => {} |
|
} |
|
} |
Let's not do this right now!
NB: I would prefer if we don't do this right now because I think it'd be best to get wallclock benchmarks working first (oxc-project/backlog#5), so we can accurately measure perf impact. This change should remove a bunch of branch predictor misses, which CodSpeed I think does not measure. So this would be a good opportunity to test that's correct and find out how much impact branch prediction misses have on Oxc's performance vs what CodSpeed has been telling us.
So I'm writing this up now not as "let's do it!", but just to make a note before I forget.
Proposed change
SemanticBuilder::enter_kindis called when entering every AST node. It is a giantmatchonAstKind.oxc/crates/oxc_semantic/src/builder.rs
Lines 1592 to 1790 in 1458d81
This seems like an anti-pattern to me. Visitors already branch on the type of AST node, and here we're generating a huge amount of branches (or at best a large unpredictable jump table) figuring out what type of AST node we're in, when the visitor function which called
enter_kind(viaenter_node) already knew this information.Now:
Replace with:
i.e. Inline what was in
enter_nodeandenter_kindinto specific visitor functions. Do this for all the various types, and forleave_node/leave_kindtoo. All 4 methods can then be removed entirely.Likely impact
#4288 was a quick test to see what perf impact this might have. That uses
#[inline(always)], which is not the best way to do this, but it probably results in about the same effect - by inlining everything the compiler can then deduce which match arm inenter_kindis relevant in each visitor, and remove the rest, thus producing roughly what I'm suggesting above. That PR showed 3%-4% speed-up on CodSpeed (NB: which also isn't taking into account branch predictor misses being removed).