mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
perf(minifier): normalize undefined to void 0 before everything else (#8699)
so subsequent code don't need to lookup `undefined`.
This commit is contained in:
parent
34d3d72b64
commit
8587965e45
7 changed files with 61 additions and 58 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ 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;
|
||||
|
|
@ -69,6 +70,7 @@ impl<'a> Ctx<'a, '_> {
|
|||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_identifier_infinity(self, ident: &IdentifierReference) -> bool {
|
||||
if ident.name == "Infinity" && ident.is_global_reference(self.symbols()) {
|
||||
return true;
|
||||
|
|
@ -76,6 +78,7 @@ impl<'a> Ctx<'a, '_> {
|
|||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_identifier_nan(self, ident: &IdentifierReference) -> bool {
|
||||
if ident.name == "NaN" && ident.is_global_reference(self.symbols()) {
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
@ -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
|
||||
///
|
||||
|
|
@ -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() => {
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
} {
|
||||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue