Skip to content

Commit 8587965

Browse files
authored
perf(minifier): normalize undefined to void 0 before everything else (#8699)
so subsequent code don't need to lookup `undefined`.
1 parent 34d3d72 commit 8587965

File tree

7 files changed

+61
-58
lines changed

7 files changed

+61
-58
lines changed

crates/oxc_minifier/examples/minifier.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> {
2727

2828
let mut allocator = Allocator::default();
2929
let printed = minify(&allocator, &source_text, source_type, mangle, nospace);
30-
println!("{printed}");
30+
// println!("{printed}");
3131

3232
if twice {
3333
allocator.reset();

crates/oxc_minifier/src/ctx.rs

+3
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,23 @@ impl<'a> Ctx<'a, '_> {
6262
}
6363
}
6464

65+
#[inline]
6566
pub fn is_identifier_undefined(self, ident: &IdentifierReference) -> bool {
6667
if ident.name == "undefined" && ident.is_global_reference(self.symbols()) {
6768
return true;
6869
}
6970
false
7071
}
7172

73+
#[inline]
7274
pub fn is_identifier_infinity(self, ident: &IdentifierReference) -> bool {
7375
if ident.name == "Infinity" && ident.is_global_reference(self.symbols()) {
7476
return true;
7577
}
7678
false
7779
}
7880

81+
#[inline]
7982
pub fn is_identifier_nan(self, ident: &IdentifierReference) -> bool {
8083
if ident.name == "NaN" && ident.is_global_reference(self.symbols()) {
8184
return true;

crates/oxc_minifier/src/peephole/fold_constants.rs

+28-27
Original file line numberDiff line numberDiff line change
@@ -573,34 +573,34 @@ impl<'a, 'b> PeepholeOptimizations {
573573
if e.arguments.len() != 1 {
574574
return None;
575575
}
576-
Some(ctx.value_to_expr(
577-
e.span,
578-
ConstantValue::Number(match &e.arguments[0] {
579-
// `Number(undefined)` -> `NaN`
580-
Argument::Identifier(ident) if ctx.is_identifier_undefined(ident) => f64::NAN,
581-
// `Number(null)` -> `0`
582-
Argument::NullLiteral(_) => 0.0,
583-
// `Number(true)` -> `1` `Number(false)` -> `0`
584-
Argument::BooleanLiteral(b) => f64::from(b.value),
585-
// `Number(100)` -> `100`
586-
Argument::NumericLiteral(n) => n.value,
587-
// `Number("a")` -> `+"a"` -> `NaN`
588-
// `Number("1")` -> `+"1"` -> `1`
589-
Argument::StringLiteral(n) => {
590-
let argument = ctx.ast.expression_string_literal(n.span, n.value, n.raw);
591-
if let Some(n) = ctx.eval_to_number(&argument) {
592-
n
593-
} else {
594-
return Some(ctx.ast.expression_unary(
595-
e.span,
596-
UnaryOperator::UnaryPlus,
597-
argument,
598-
));
599-
}
576+
let arg = e.arguments[0].as_expression()?;
577+
let value = ConstantValue::Number(match arg {
578+
// `Number(undefined)` -> `NaN`
579+
Expression::Identifier(ident) if ctx.is_identifier_undefined(ident) => f64::NAN,
580+
// `Number(null)` -> `0`
581+
Expression::NullLiteral(_) => 0.0,
582+
// `Number(true)` -> `1` `Number(false)` -> `0`
583+
Expression::BooleanLiteral(b) => f64::from(b.value),
584+
// `Number(100)` -> `100`
585+
Expression::NumericLiteral(n) => n.value,
586+
// `Number("a")` -> `+"a"` -> `NaN`
587+
// `Number("1")` -> `+"1"` -> `1`
588+
Expression::StringLiteral(n) => {
589+
let argument = ctx.ast.expression_string_literal(n.span, n.value, n.raw);
590+
if let Some(n) = ctx.eval_to_number(&argument) {
591+
n
592+
} else {
593+
return Some(ctx.ast.expression_unary(
594+
e.span,
595+
UnaryOperator::UnaryPlus,
596+
argument,
597+
));
600598
}
601-
_ => return None,
602-
}),
603-
))
599+
}
600+
e if e.is_void_0() => f64::NAN,
601+
_ => return None,
602+
});
603+
Some(ctx.value_to_expr(e.span, value))
604604
}
605605

606606
fn try_fold_binary_typeof_comparison(
@@ -1744,6 +1744,7 @@ mod test {
17441744
#[test]
17451745
fn test_number_constructor() {
17461746
test("Number(undefined)", "NaN");
1747+
test("Number(void 0)", "NaN");
17471748
test("Number(null)", "0");
17481749
test("Number(true)", "1");
17491750
test("Number(false)", "0");

crates/oxc_minifier/src/peephole/minimize_conditions.rs

+4-9
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ use oxc_ecmascript::{
44
constant_evaluation::{ConstantEvaluation, ValueType},
55
ToInt32,
66
};
7-
use oxc_semantic::ReferenceFlags;
87
use oxc_span::{cmp::ContentEq, GetSpan};
98
use oxc_syntax::es_target::ESTarget;
10-
use oxc_traverse::{Ancestor, MaybeBoundIdentifier, TraverseCtx};
9+
use oxc_traverse::{Ancestor, TraverseCtx};
1110

1211
use crate::ctx::Ctx;
1312

@@ -366,13 +365,9 @@ impl<'a> PeepholeOptimizations {
366365
unreachable!()
367366
};
368367
let return_stmt = return_stmt.unbox();
369-
if let Some(e) = return_stmt.argument {
370-
e
371-
} else {
372-
let name = "undefined";
373-
let symbol_id = ctx.scopes().find_binding(ctx.current_scope_id(), name);
374-
let ident = MaybeBoundIdentifier::new(Atom::from(name), symbol_id);
375-
ident.create_expression(ReferenceFlags::read(), ctx)
368+
match return_stmt.argument {
369+
Some(e) => e,
370+
None => ctx.ast.void_0(return_stmt.span),
376371
}
377372
}
378373

crates/oxc_minifier/src/peephole/normalize.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use oxc_ast::ast::*;
33
use oxc_ecmascript::constant_evaluation::ConstantEvaluation;
44
use oxc_span::GetSpan;
55
use oxc_syntax::scope::ScopeFlags;
6-
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
6+
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};
77

88
use crate::{ctx::Ctx, CompressOptions};
99

@@ -22,6 +22,7 @@ pub struct NormalizeOptions {
2222
/// * convert `Infinity` to `f64::INFINITY`
2323
/// * convert `NaN` to `f64::NaN`
2424
/// * convert `var x; void x` to `void 0`
25+
/// * convert `undefined` to `void 0`
2526
///
2627
/// Also
2728
///
@@ -62,7 +63,10 @@ impl<'a> Traverse<'a> for Normalize {
6263
*expr = ctx.ast.move_expression(&mut paren_expr.expression);
6364
}
6465
match expr {
65-
Expression::Identifier(_) => {
66+
Expression::Identifier(ident) => {
67+
if let Some(e) = Self::try_compress_undefined(ident, ctx) {
68+
*expr = e;
69+
}
6670
Self::convert_infinity_or_nan_into_number(expr, ctx);
6771
}
6872
Expression::UnaryExpression(e) if e.operator.is_void() => {
@@ -154,6 +158,24 @@ impl<'a> Normalize {
154158
}
155159
e.argument = ctx.ast.expression_numeric_literal(ident.span, 0.0, None, NumberBase::Decimal);
156160
}
161+
162+
/// Transforms `undefined` => `void 0`
163+
/// So subsequent passes don't need to look up whether `undefined` is shadowed or not.
164+
fn try_compress_undefined(
165+
ident: &IdentifierReference<'a>,
166+
ctx: &mut TraverseCtx<'a>,
167+
) -> Option<Expression<'a>> {
168+
if !Ctx(ctx).is_identifier_undefined(ident) {
169+
return None;
170+
}
171+
// `delete undefined` returns `false`
172+
// `delete void 0` returns `true`
173+
if matches!(ctx.parent(), Ancestor::UnaryExpressionArgument(e) if e.operator().is_delete())
174+
{
175+
return None;
176+
}
177+
Some(ctx.ast.void_0(ident.span))
178+
}
157179
}
158180

159181
#[cfg(test)]

crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs

-18
Original file line numberDiff line numberDiff line change
@@ -871,7 +871,6 @@ impl<'a> LatePeepholeOptimizations {
871871
}
872872

873873
if let Some(folded_expr) = match expr {
874-
Expression::Identifier(ident) => Self::try_compress_undefined(ident, ctx),
875874
Expression::BooleanLiteral(_) => Self::try_compress_boolean(expr, ctx),
876875
_ => None,
877876
} {
@@ -893,23 +892,6 @@ impl<'a> LatePeepholeOptimizations {
893892
}
894893
}
895894

896-
/// Transforms `undefined` => `void 0`
897-
fn try_compress_undefined(
898-
ident: &IdentifierReference<'a>,
899-
ctx: &mut TraverseCtx<'a>,
900-
) -> Option<Expression<'a>> {
901-
if !Ctx(ctx).is_identifier_undefined(ident) {
902-
return None;
903-
}
904-
// `delete undefined` returns `false`
905-
// `delete void 0` returns `true`
906-
if matches!(ctx.parent(), Ancestor::UnaryExpressionArgument(e) if e.operator().is_delete())
907-
{
908-
return None;
909-
}
910-
Some(ctx.ast.void_0(ident.span))
911-
}
912-
913895
/// Transforms boolean expression `true` => `!0` `false` => `!1`.
914896
fn try_compress_boolean(
915897
expr: &mut Expression<'a>,

tasks/minsize/minsize.snap

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Original | minified | minified | gzip | gzip | Fixture
99

1010
342.15 kB | 118.19 kB | 118.14 kB | 44.45 kB | 44.37 kB | vue.js
1111

12-
544.10 kB | 71.73 kB | 72.48 kB | 26.14 kB | 26.20 kB | lodash.js
12+
544.10 kB | 71.75 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js
1313

1414
555.77 kB | 272.89 kB | 270.13 kB | 90.90 kB | 90.80 kB | d3.js
1515

0 commit comments

Comments
 (0)