mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
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.
This commit is contained in:
parent
618e89ecf7
commit
5dc01543fa
4 changed files with 46 additions and 14 deletions
|
|
@ -31,6 +31,13 @@ impl<'a> ES2020<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Traverse<'a> for 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(
|
fn enter_statements(
|
||||||
&mut self,
|
&mut self,
|
||||||
statements: &mut Vec<'a, Statement<'a>>,
|
statements: &mut Vec<'a, Statement<'a>>,
|
||||||
|
|
|
||||||
|
|
@ -35,22 +35,31 @@ use oxc_span::SPAN;
|
||||||
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator};
|
use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator};
|
||||||
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||||
|
|
||||||
use crate::context::Ctx;
|
use crate::{context::Ctx, helpers::stack::SparseStack};
|
||||||
|
|
||||||
pub struct NullishCoalescingOperator<'a> {
|
pub struct NullishCoalescingOperator<'a> {
|
||||||
_ctx: Ctx<'a>,
|
_ctx: Ctx<'a>,
|
||||||
var_declarations: std::vec::Vec<Vec<'a, VariableDeclarator<'a>>>,
|
var_declarations: SparseStack<Vec<'a, VariableDeclarator<'a>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NullishCoalescingOperator<'a> {
|
impl<'a> NullishCoalescingOperator<'a> {
|
||||||
pub fn new(ctx: Ctx<'a>) -> Self {
|
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> {
|
impl<'a> Traverse<'a> for NullishCoalescingOperator<'a> {
|
||||||
fn enter_statements(&mut self, _stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
#[inline] // Inline because it's no-op in release mode
|
||||||
self.var_declarations.push(ctx.ast.vec());
|
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(
|
fn exit_statements(
|
||||||
|
|
@ -59,9 +68,7 @@ impl<'a> Traverse<'a> for NullishCoalescingOperator<'a> {
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) {
|
) {
|
||||||
if let Some(declarations) = self.var_declarations.pop() {
|
if let Some(declarations) = self.var_declarations.pop() {
|
||||||
if declarations.is_empty() {
|
debug_assert!(!declarations.is_empty());
|
||||||
return;
|
|
||||||
}
|
|
||||||
let variable = ctx.ast.alloc_variable_declaration(
|
let variable = ctx.ast.alloc_variable_declaration(
|
||||||
SPAN,
|
SPAN,
|
||||||
VariableDeclarationKind::Var,
|
VariableDeclarationKind::Var,
|
||||||
|
|
@ -151,8 +158,7 @@ impl<'a> Traverse<'a> for NullishCoalescingOperator<'a> {
|
||||||
} else {
|
} else {
|
||||||
let kind = VariableDeclarationKind::Var;
|
let kind = VariableDeclarationKind::Var;
|
||||||
self.var_declarations
|
self.var_declarations
|
||||||
.last_mut()
|
.get_mut_or_init(|| ctx.ast.vec())
|
||||||
.unwrap()
|
|
||||||
.push(ctx.ast.variable_declarator(SPAN, kind, id, None, false));
|
.push(ctx.ast.variable_declarator(SPAN, kind, id, None, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ impl<T> SparseStack<T> {
|
||||||
if has_value {
|
if has_value {
|
||||||
debug_assert!(!self.values.is_empty());
|
debug_assert!(!self.values.is_empty());
|
||||||
// SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`.
|
// 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`
|
// 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`.
|
// has been consumed at the same time we consume its corresponding value from `self.values`.
|
||||||
let value = unsafe { self.values.pop().unwrap_unchecked() };
|
let value = unsafe { self.values.pop().unwrap_unchecked() };
|
||||||
|
|
@ -70,7 +70,7 @@ impl<T> SparseStack<T> {
|
||||||
|
|
||||||
debug_assert!(!self.values.is_empty());
|
debug_assert!(!self.values.is_empty());
|
||||||
// SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`.
|
// 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`
|
// 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`.
|
// at the same time as we consume the corresponding value from `self.values`.
|
||||||
let value = unsafe { self.values.pop().unwrap_unchecked() };
|
let value = unsafe { self.values.pop().unwrap_unchecked() };
|
||||||
|
|
@ -94,12 +94,31 @@ impl<T> SparseStack<T> {
|
||||||
|
|
||||||
debug_assert!(!self.values.is_empty());
|
debug_assert!(!self.values.is_empty());
|
||||||
// SAFETY: Last `self.has_values` is only `true` if there's a corresponding value in `self.values`.
|
// 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`
|
// 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.
|
// and a value pushed to `self.values` above.
|
||||||
unsafe { self.values.last().unwrap_unchecked() }
|
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<I: FnOnce() -> 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.
|
/// Get number of entries on the stack.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
|
|
@ -108,7 +127,6 @@ impl<T> SparseStack<T> {
|
||||||
|
|
||||||
/// Returns `true` if stack is empty.
|
/// Returns `true` if stack is empty.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[expect(dead_code)]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.has_values.is_empty()
|
self.has_values.is_empty()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,7 @@ impl<'a> Traverse<'a> for Transformer<'a> {
|
||||||
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_program(&mut self, program: &mut Program<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.x1_react.exit_program(program, ctx);
|
self.x1_react.exit_program(program, ctx);
|
||||||
self.x0_typescript.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);
|
self.x3_es2015.exit_program(program, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue