perf(minifier): allocate AST nodes in arena directly (#23710)

Same as #23709.

Small perf optimizations around AST builder calls.

- AST nodes which end up in `Box`es, allocate into the `Box` as early as possible, to increase chance compiler sees the type can be built directly in arena, rather than built on the stack, and then copied from stack into arena.
- Functions return `Box<T>` rather than `T` where the value needs to be boxed anyway. If function is not inlined, this avoids stack allocation + copy.

Also, shorten code where possible by using more specific `AstBuilder` methods.
This commit is contained in:
overlookmotel committed 2026-06-23 01:00:15 +00:00
1 parent d02588770d
commit 3855f0c0d1
4 files changed
+18 -23

No files matched your search

@@ -132,12 +132,11 @@ impl<'a> PeepholeOptimizations {
&& if2.alternate.is_some()
{
let scope_id = ctx.create_child_scope_of_current(ScopeFlags::empty());
let new_consequent =
Statement::BlockStatement(ctx.ast.alloc(ctx.ast.block_statement_with_scope_id(
if_stmt.consequent.span(),
ctx.ast.vec1(if_stmt.consequent.take_in(ctx)),
scope_id,
)));
let new_consequent = ctx.ast.statement_block_with_scope_id(
if_stmt.consequent.span(),
ctx.ast.vec1(if_stmt.consequent.take_in(ctx)),
scope_id,
);
ctx.replace_statement(&mut if_stmt.consequent, new_consequent);
}
}
@@ -834,9 +834,7 @@ impl<'a> PeepholeOptimizations {
body.remove(0)
} else {
let scope_id = ctx.create_child_scope_of_current(ScopeFlags::empty());
let block_stmt =
ctx.ast.block_statement_with_scope_id(span, body, scope_id);
Statement::BlockStatement(ctx.ast.alloc(block_stmt))
ctx.ast.statement_block_with_scope_id(span, body, scope_id)
};
let mut if_stmt = ctx.ast.if_statement(test.span(), test, consequent, None);
let if_stmt = Self::try_minimize_if(&mut if_stmt, ctx)
@@ -321,9 +321,9 @@ impl<'a> PeepholeOptimizations {
&& s.handler.as_ref().is_none_or(|handler| handler.body.body.is_empty())
{
let new_stmt = if let Some(finalizer) = &mut s.finalizer {
let mut block = ctx.ast.block_statement(finalizer.span, ctx.ast.vec());
std::mem::swap(&mut **finalizer, &mut block);
Statement::BlockStatement(ctx.ast.alloc(block))
let mut block = ctx.ast.alloc_block_statement(finalizer.span, ctx.ast.vec());
std::mem::swap(finalizer, &mut block);
Statement::BlockStatement(block)
} else {
ctx.ast.statement_empty(s.span)
};
@@ -9,29 +9,27 @@ fn test() {
let ast = AstBuilder::new(&allocator);
let mut elements = ast.vec();
elements.push(ast.array_expression_element_elision(SPAN));
elements.push(ArrayExpressionElement::NullLiteral(ast.alloc(ast.null_literal(SPAN))));
elements.push(ArrayExpressionElement::NumericLiteral(ast.alloc(ast.numeric_literal(
elements.push(ArrayExpressionElement::NullLiteral(ast.alloc_null_literal(SPAN)));
elements.push(ArrayExpressionElement::NumericLiteral(ast.alloc_numeric_literal(
SPAN,
42f64,
None,
NumberBase::Decimal,
))));
elements.push(ArrayExpressionElement::StringLiteral(
ast.alloc(ast.string_literal(SPAN, "foo", None)),
));
)));
elements
.push(ArrayExpressionElement::BooleanLiteral(ast.alloc(ast.boolean_literal(SPAN, true))));
elements.push(ArrayExpressionElement::BigIntLiteral(ast.alloc(ast.big_int_literal(
.push(ArrayExpressionElement::StringLiteral(ast.alloc_string_literal(SPAN, "foo", None)));
elements.push(ArrayExpressionElement::BooleanLiteral(ast.alloc_boolean_literal(SPAN, true)));
elements.push(ArrayExpressionElement::BigIntLiteral(ast.alloc_big_int_literal(
SPAN,
"42",
Some(Str::from("42n")),
BigintBase::Decimal,
))));
let array = ast.array_expression(SPAN, elements.clone_in(&allocator));
)));
let array = ast.array_expression(SPAN, elements);
let mut array2 = array.clone_in(&allocator);
array2.elements.push(ArrayExpressionElement::ArrayExpression(ast.alloc(array)));
array2.elements.push(ArrayExpressionElement::ObjectExpression(
ast.alloc(ast.object_expression(SPAN, ast.vec())),
ast.alloc_object_expression(SPAN, ast.vec()),
));
let joined = array2.array_join(&WithoutGlobalReferenceInformation {}, Some("_"));
assert_eq!(joined, Some("__42_foo_true_42_,,42,foo,true,42_[object Object]".to_string()));