Skip to content

Commit

Permalink
perf(minifier): normalize undefined to void 0 before everything e…
Browse files Browse the repository at this point in the history
…lse (#8699)

so subsequent code don't need to lookup `undefined`.
  • Loading branch information
Boshen authored Jan 25, 2025
1 parent 34d3d72 commit 8587965
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 58 deletions.
2 changes: 1 addition & 1 deletion crates/oxc_minifier/examples/minifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn main() -> std::io::Result<()> {

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

if twice {
allocator.reset();
Expand Down
3 changes: 3 additions & 0 deletions crates/oxc_minifier/src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,20 +62,23 @@ impl<'a> Ctx<'a, '_> {
}
}

#[inline]
pub fn is_identifier_undefined(self, ident: &IdentifierReference) -> bool {
if ident.name == "undefined" && ident.is_global_reference(self.symbols()) {
return true;
}
false
}

#[inline]
pub fn is_identifier_infinity(self, ident: &IdentifierReference) -> bool {
if ident.name == "Infinity" && ident.is_global_reference(self.symbols()) {
return true;
}
false
}

#[inline]
pub fn is_identifier_nan(self, ident: &IdentifierReference) -> bool {
if ident.name == "NaN" && ident.is_global_reference(self.symbols()) {
return true;
Expand Down
55 changes: 28 additions & 27 deletions crates/oxc_minifier/src/peephole/fold_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,34 +573,34 @@ impl<'a, 'b> PeepholeOptimizations {
if e.arguments.len() != 1 {
return None;
}
Some(ctx.value_to_expr(
e.span,
ConstantValue::Number(match &e.arguments[0] {
// `Number(undefined)` -> `NaN`
Argument::Identifier(ident) if ctx.is_identifier_undefined(ident) => f64::NAN,
// `Number(null)` -> `0`
Argument::NullLiteral(_) => 0.0,
// `Number(true)` -> `1` `Number(false)` -> `0`
Argument::BooleanLiteral(b) => f64::from(b.value),
// `Number(100)` -> `100`
Argument::NumericLiteral(n) => n.value,
// `Number("a")` -> `+"a"` -> `NaN`
// `Number("1")` -> `+"1"` -> `1`
Argument::StringLiteral(n) => {
let argument = ctx.ast.expression_string_literal(n.span, n.value, n.raw);
if let Some(n) = ctx.eval_to_number(&argument) {
n
} else {
return Some(ctx.ast.expression_unary(
e.span,
UnaryOperator::UnaryPlus,
argument,
));
}
let arg = e.arguments[0].as_expression()?;
let value = ConstantValue::Number(match arg {
// `Number(undefined)` -> `NaN`
Expression::Identifier(ident) if ctx.is_identifier_undefined(ident) => f64::NAN,
// `Number(null)` -> `0`
Expression::NullLiteral(_) => 0.0,
// `Number(true)` -> `1` `Number(false)` -> `0`
Expression::BooleanLiteral(b) => f64::from(b.value),
// `Number(100)` -> `100`
Expression::NumericLiteral(n) => n.value,
// `Number("a")` -> `+"a"` -> `NaN`
// `Number("1")` -> `+"1"` -> `1`
Expression::StringLiteral(n) => {
let argument = ctx.ast.expression_string_literal(n.span, n.value, n.raw);
if let Some(n) = ctx.eval_to_number(&argument) {
n
} else {
return Some(ctx.ast.expression_unary(
e.span,
UnaryOperator::UnaryPlus,
argument,
));
}
_ => return None,
}),
))
}
e if e.is_void_0() => f64::NAN,
_ => return None,
});
Some(ctx.value_to_expr(e.span, value))
}

fn try_fold_binary_typeof_comparison(
Expand Down Expand Up @@ -1744,6 +1744,7 @@ mod test {
#[test]
fn test_number_constructor() {
test("Number(undefined)", "NaN");
test("Number(void 0)", "NaN");
test("Number(null)", "0");
test("Number(true)", "1");
test("Number(false)", "0");
Expand Down
13 changes: 4 additions & 9 deletions crates/oxc_minifier/src/peephole/minimize_conditions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ use oxc_ecmascript::{
constant_evaluation::{ConstantEvaluation, ValueType},
ToInt32,
};
use oxc_semantic::ReferenceFlags;
use oxc_span::{cmp::ContentEq, GetSpan};
use oxc_syntax::es_target::ESTarget;
use oxc_traverse::{Ancestor, MaybeBoundIdentifier, TraverseCtx};
use oxc_traverse::{Ancestor, TraverseCtx};

use crate::ctx::Ctx;

Expand Down Expand Up @@ -366,13 +365,9 @@ impl<'a> PeepholeOptimizations {
unreachable!()
};
let return_stmt = return_stmt.unbox();
if let Some(e) = return_stmt.argument {
e
} else {
let name = "undefined";
let symbol_id = ctx.scopes().find_binding(ctx.current_scope_id(), name);
let ident = MaybeBoundIdentifier::new(Atom::from(name), symbol_id);
ident.create_expression(ReferenceFlags::read(), ctx)
match return_stmt.argument {
Some(e) => e,
None => ctx.ast.void_0(return_stmt.span),
}
}

Expand Down
26 changes: 24 additions & 2 deletions crates/oxc_minifier/src/peephole/normalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use oxc_ast::ast::*;
use oxc_ecmascript::constant_evaluation::ConstantEvaluation;
use oxc_span::GetSpan;
use oxc_syntax::scope::ScopeFlags;
use oxc_traverse::{traverse_mut_with_ctx, ReusableTraverseCtx, Traverse, TraverseCtx};
use oxc_traverse::{traverse_mut_with_ctx, Ancestor, ReusableTraverseCtx, Traverse, TraverseCtx};

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

Expand All @@ -22,6 +22,7 @@ pub struct NormalizeOptions {
/// * convert `Infinity` to `f64::INFINITY`
/// * convert `NaN` to `f64::NaN`
/// * convert `var x; void x` to `void 0`
/// * convert `undefined` to `void 0`
///
/// Also
///
Expand Down Expand Up @@ -62,7 +63,10 @@ impl<'a> Traverse<'a> for Normalize {
*expr = ctx.ast.move_expression(&mut paren_expr.expression);
}
match expr {
Expression::Identifier(_) => {
Expression::Identifier(ident) => {
if let Some(e) = Self::try_compress_undefined(ident, ctx) {
*expr = e;
}
Self::convert_infinity_or_nan_into_number(expr, ctx);
}
Expression::UnaryExpression(e) if e.operator.is_void() => {
Expand Down Expand Up @@ -154,6 +158,24 @@ impl<'a> Normalize {
}
e.argument = ctx.ast.expression_numeric_literal(ident.span, 0.0, None, NumberBase::Decimal);
}

/// Transforms `undefined` => `void 0`
/// So subsequent passes don't need to look up whether `undefined` is shadowed or not.
fn try_compress_undefined(
ident: &IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if !Ctx(ctx).is_identifier_undefined(ident) {
return None;
}
// `delete undefined` returns `false`
// `delete void 0` returns `true`
if matches!(ctx.parent(), Ancestor::UnaryExpressionArgument(e) if e.operator().is_delete())
{
return None;
}
Some(ctx.ast.void_0(ident.span))
}
}

#[cfg(test)]
Expand Down
18 changes: 0 additions & 18 deletions crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,6 @@ impl<'a> LatePeepholeOptimizations {
}

if let Some(folded_expr) = match expr {
Expression::Identifier(ident) => Self::try_compress_undefined(ident, ctx),
Expression::BooleanLiteral(_) => Self::try_compress_boolean(expr, ctx),
_ => None,
} {
Expand All @@ -893,23 +892,6 @@ impl<'a> LatePeepholeOptimizations {
}
}

/// Transforms `undefined` => `void 0`
fn try_compress_undefined(
ident: &IdentifierReference<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
if !Ctx(ctx).is_identifier_undefined(ident) {
return None;
}
// `delete undefined` returns `false`
// `delete void 0` returns `true`
if matches!(ctx.parent(), Ancestor::UnaryExpressionArgument(e) if e.operator().is_delete())
{
return None;
}
Some(ctx.ast.void_0(ident.span))
}

/// Transforms boolean expression `true` => `!0` `false` => `!1`.
fn try_compress_boolean(
expr: &mut Expression<'a>,
Expand Down
2 changes: 1 addition & 1 deletion tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Original | minified | minified | gzip | gzip | Fixture

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

544.10 kB | 71.73 kB | 72.48 kB | 26.14 kB | 26.20 kB | lodash.js
544.10 kB | 71.75 kB | 72.48 kB | 26.15 kB | 26.20 kB | lodash.js

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

Expand Down

0 comments on commit 8587965

Please sign in to comment.