From 5dc01543fa72febf080bcf7bc2fda66492620590 Mon Sep 17 00:00:00 2001 From: overlookmotel <557937+overlookmotel@users.noreply.github.com> Date: Mon, 23 Sep 2024 07:52:45 +0000 Subject: [PATCH] perf(transformer): nullish coalescing operator transform use `SparseStack` (#5942) Use `SparseStack` (introduced in #5940) to store the stack of blocks which may need a `var _temp;` statement added to them. This reduces the memory required for the stack, on assumption that most blocks won't need a `var` statement. --- crates/oxc_transformer/src/es2020/mod.rs | 7 +++++ .../src/es2020/nullish_coalescing_operator.rs | 26 ++++++++++++------- crates/oxc_transformer/src/helpers/stack.rs | 26 ++++++++++++++++--- crates/oxc_transformer/src/lib.rs | 1 + 4 files changed, 46 insertions(+), 14 deletions(-) diff --git a/crates/oxc_transformer/src/es2020/mod.rs b/crates/oxc_transformer/src/es2020/mod.rs index 1f7e3a47e7c55..5012a9bcee452 100644 --- a/crates/oxc_transformer/src/es2020/mod.rs +++ b/crates/oxc_transformer/src/es2020/mod.rs @@ -31,6 +31,13 @@ impl<'a> ES2020<'a> { } impl<'a> Traverse<'a> for ES2020<'a> { + #[inline] // Inline because it's no-op in release mode + fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { + if self.options.nullish_coalescing_operator { + self.nullish_coalescing_operator.exit_program(program, ctx); + } + } + fn enter_statements( &mut self, statements: &mut Vec<'a, Statement<'a>>, diff --git a/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs b/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs index b5145e1e7ca3c..1133b326c1c99 100644 --- a/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs +++ b/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs @@ -35,22 +35,31 @@ use oxc_span::SPAN; use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator}; use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; -use crate::context::Ctx; +use crate::{context::Ctx, helpers::stack::SparseStack}; pub struct NullishCoalescingOperator<'a> { _ctx: Ctx<'a>, - var_declarations: std::vec::Vec>>, + var_declarations: SparseStack>>, } impl<'a> NullishCoalescingOperator<'a> { pub fn new(ctx: Ctx<'a>) -> Self { - Self { _ctx: ctx, var_declarations: vec![] } + Self { _ctx: ctx, var_declarations: SparseStack::new() } } } impl<'a> Traverse<'a> for NullishCoalescingOperator<'a> { - fn enter_statements(&mut self, _stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) { - self.var_declarations.push(ctx.ast.vec()); + #[inline] // Inline because it's no-op in release mode + fn exit_program(&mut self, _program: &mut Program<'a>, _ctx: &mut TraverseCtx<'a>) { + debug_assert!(self.var_declarations.is_empty()); + } + + fn enter_statements( + &mut self, + _stmts: &mut Vec<'a, Statement<'a>>, + _ctx: &mut TraverseCtx<'a>, + ) { + self.var_declarations.push(None); } fn exit_statements( @@ -59,9 +68,7 @@ impl<'a> Traverse<'a> for NullishCoalescingOperator<'a> { ctx: &mut TraverseCtx<'a>, ) { if let Some(declarations) = self.var_declarations.pop() { - if declarations.is_empty() { - return; - } + debug_assert!(!declarations.is_empty()); let variable = ctx.ast.alloc_variable_declaration( SPAN, VariableDeclarationKind::Var, @@ -151,8 +158,7 @@ impl<'a> Traverse<'a> for NullishCoalescingOperator<'a> { } else { let kind = VariableDeclarationKind::Var; self.var_declarations - .last_mut() - .unwrap() + .get_mut_or_init(|| ctx.ast.vec()) .push(ctx.ast.variable_declarator(SPAN, kind, id, None, false)); } diff --git a/crates/oxc_transformer/src/helpers/stack.rs b/crates/oxc_transformer/src/helpers/stack.rs index 53ca9b039398c..1417b311e0ef2 100644 --- a/crates/oxc_transformer/src/helpers/stack.rs +++ b/crates/oxc_transformer/src/helpers/stack.rs @@ -49,7 +49,7 @@ impl SparseStack { if has_value { debug_assert!(!self.values.is_empty()); // SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`. - // This invariant is maintained in `push`, `take`, and `get_or_init`. + // This invariant is maintained in `push`, `take`, `get_or_init`, and `get_mut_or_init`. // We maintain it here too because we just popped from `self.has_values`, so that `true` // has been consumed at the same time we consume its corresponding value from `self.values`. let value = unsafe { self.values.pop().unwrap_unchecked() }; @@ -70,7 +70,7 @@ impl SparseStack { debug_assert!(!self.values.is_empty()); // SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`. - // This invariant is maintained in `push`, `pop`, and `get_or_init`. + // This invariant is maintained in `push`, `pop`, `get_or_init`, and `get_mut_or_init`. // We maintain it here too because we just set last `self.has_values` to `false` // at the same time as we consume the corresponding value from `self.values`. let value = unsafe { self.values.pop().unwrap_unchecked() }; @@ -94,12 +94,31 @@ impl SparseStack { debug_assert!(!self.values.is_empty()); // SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`. - // This invariant is maintained in `push`, `pop`, and `take`. + // This invariant is maintained in `push`, `pop`, `take`, and `get_mut_or_init`. // Here either last `self.has_values` was already `true`, or it's just been set to `true` // and a value pushed to `self.values` above. unsafe { self.values.last().unwrap_unchecked() } } + /// Initialize the value for top entry on the stack, if it has no value already. + /// Return mutable reference to value. + /// + /// # Panics + /// Panics if the stack is empty. + pub fn get_mut_or_init T>(&mut self, init: I) -> &mut T { + let has_value = self.has_values.last_mut().unwrap(); + if !*has_value { + *has_value = true; + self.values.push(init()); + } + + // SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`. + // This invariant is maintained in `push`, `pop`, `take`, and `get_or_init`. + // Here either last `self.has_values` was already `true`, or it's just been set to `true` + // and a value pushed to `self.values` above. + unsafe { self.values.last_mut().unwrap_unchecked() } + } + /// Get number of entries on the stack. #[inline] pub fn len(&self) -> usize { @@ -108,7 +127,6 @@ impl SparseStack { /// Returns `true` if stack is empty. #[inline] - #[expect(dead_code)] pub fn is_empty(&self) -> bool { self.has_values.is_empty() } diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index f995ae719c6f0..1a854c0b9ba53 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -132,6 +132,7 @@ impl<'a> Traverse<'a> for Transformer<'a> { fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) { self.x1_react.exit_program(program, ctx); self.x0_typescript.exit_program(program, ctx); + self.x2_es2020.exit_program(program, ctx); self.x3_es2015.exit_program(program, ctx); }