Skip to content

Commit

Permalink
feat(minifier): handle positive NaN and Infinity. (#6207)
Browse files Browse the repository at this point in the history
`+NaN` -> `NaN`, `+Infinity` -> `Infinity`.
  • Loading branch information
7086cmd committed Oct 1, 2024
1 parent c3c3447 commit cca0034
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 22 deletions.
92 changes: 75 additions & 17 deletions crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,3 @@
use std::cmp::Ordering;

use num_bigint::BigInt;
use oxc_ast::ast::*;
use oxc_span::{GetSpan, Span, SPAN};
use oxc_syntax::{
number::NumberBase,
operator::{BinaryOperator, LogicalOperator, UnaryOperator},
};
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};

use crate::{
node_util::{
is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, NumberValue, ValueType,
Expand All @@ -17,6 +6,16 @@ use crate::{
ty::Ty,
CompressorPass,
};
use num_bigint::BigInt;
use oxc_ast::ast::*;
use oxc_span::{GetSpan, Span, SPAN};
use oxc_syntax::number::ToJsInt32;
use oxc_syntax::{
number::NumberBase,
operator::{BinaryOperator, LogicalOperator, UnaryOperator},
};
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
use std::cmp::Ordering;

/// Constant Folding
///
Expand Down Expand Up @@ -139,6 +138,12 @@ impl<'a> PeepholeFoldConstants {
expr: &mut UnaryExpression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<Expression<'a>> {
fn is_within_i32_range(x: f64) -> bool {
x.is_finite()
&& x.fract() == 0.0
&& x >= f64::from(i32::MIN)
&& x <= f64::from(i32::MAX)
}
match expr.operator {
UnaryOperator::Void => self.try_reduce_void(expr, ctx),
UnaryOperator::Typeof => self.try_fold_type_of(expr, ctx),
Expand All @@ -162,9 +167,51 @@ impl<'a> PeepholeFoldConstants {
matches!(unary.operator, UnaryOperator::UnaryNegation)
.then(|| ctx.ast.move_expression(&mut expr.argument))
}
Expression::Identifier(id) if id.name == "Infinity" => {
Some(ctx.ast.move_expression(&mut expr.argument))
}
// `+NaN` -> `NaN`
_ if expr.argument.is_nan() => Some(ctx.ast.move_expression(&mut expr.argument)),
_ if expr.argument.is_number() => Some(ctx.ast.move_expression(&mut expr.argument)),
_ => None,
},
UnaryOperator::BitwiseNot => match &mut expr.argument {
Expression::NumericLiteral(n) => is_within_i32_range(n.value).then(|| {
let value = !n.value.to_js_int_32();
ctx.ast.expression_numeric_literal(
SPAN,
value.into(),
value.to_string(),
NumberBase::Decimal,
)
}),
Expression::UnaryExpression(un) => {
match un.operator {
UnaryOperator::BitwiseNot => {
// Return the un-bitten value
Some(ctx.ast.move_expression(&mut un.argument))
}
UnaryOperator::UnaryNegation if un.argument.is_number() => {
// `-~1` -> `2`
if let Expression::NumericLiteral(n) = &mut un.argument {
is_within_i32_range(n.value).then(|| {
let value = !(-n.value.to_js_int_32());
ctx.ast.expression_numeric_literal(
SPAN,
value.into(),
value.to_string(),
NumberBase::Decimal,
)
})
} else {
None
}
}
_ => None,
}
}
_ => None,
},
_ => None,
}
}
Expand Down Expand Up @@ -1268,9 +1315,9 @@ mod test {
test_same("a=-Infinity");
test("a=-NaN", "a=NaN");
test_same("a=-foo()");
// test("a=~~0", "a=0");
// test("a=~~10", "a=10");
// test("a=~-7", "a=6");
test("a=~~0", "a=0");
test("a=~~10", "a=10");
test("a=~-7", "a=6");

// test("a=+true", "a=1");
test("a=+10", "a=10");
Expand All @@ -1279,8 +1326,8 @@ mod test {
test_same("a=+f");
// test("a=+(f?true:false)", "a=+(f?1:0)");
test("a=+0", "a=0");
// test("a=+Infinity", "a=Infinity");
// test("a=+NaN", "a=NaN");
test("a=+Infinity", "a=Infinity");
test("a=+NaN", "a=NaN");
test("a=+-7", "a=-7");
// test("a=+.5", "a=.5");

Expand All @@ -1299,12 +1346,23 @@ mod test {
}

#[test]
#[ignore]
fn test_unary_ops_string_compare() {
test_same("a = -1");
test("a = ~0", "a = -1");
test("a = ~1", "a = -2");
test("a = ~101", "a = -102");

// More tests added by Ethan, which aligns with Google Closure Compiler's behavior
test_same("a = ~1.1"); // By default, we don't fold floating-point numbers.
test("a = ~0x3", "a = -4"); // Hexadecimal number
test("a = ~9", "a = -10"); // Despite `-10` is longer than `~9`, the compiler still folds it.
test_same("a = ~b");
test_same("a = ~NaN");
test_same("a = ~-Infinity");
// TODO(7086cmd) We preserve it right now, since exceeded data's ~ calculation
// is hard to implement within one PR.
// test("x = ~2147483658.0", "x = 2147483647");
// test("x = ~-2147483658", "x = -2147483649");
}

#[test]
Expand Down
10 changes: 5 additions & 5 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ Original | Minified | esbuild | Gzip | esbuild

72.14 kB | 24.47 kB | 23.70 kB | 8.65 kB | 8.54 kB | react.development.js

173.90 kB | 61.71 kB | 59.82 kB | 19.56 kB | 19.33 kB | moment.js
173.90 kB | 61.70 kB | 59.82 kB | 19.54 kB | 19.33 kB | moment.js

287.63 kB | 92.83 kB | 90.07 kB | 32.29 kB | 31.95 kB | jquery.js

342.15 kB | 124.14 kB | 118.14 kB | 44.81 kB | 44.37 kB | vue.js

544.10 kB | 74.13 kB | 72.48 kB | 26.23 kB | 26.20 kB | lodash.js

555.77 kB | 278.71 kB | 270.13 kB | 91.40 kB | 90.80 kB | d3.js
555.77 kB | 278.70 kB | 270.13 kB | 91.39 kB | 90.80 kB | d3.js

1.01 MB | 470.11 kB | 458.89 kB | 126.97 kB | 126.71 kB | bundle.min.js

1.25 MB | 671.02 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js
1.25 MB | 671 kB | 646.76 kB | 164.72 kB | 163.73 kB | three.js

2.14 MB | 756.70 kB | 724.14 kB | 182.87 kB | 181.07 kB | victory.js
2.14 MB | 756.69 kB | 724.14 kB | 182.87 kB | 181.07 kB | victory.js

3.20 MB | 1.05 MB | 1.01 MB | 334.11 kB | 331.56 kB | echarts.js

6.69 MB | 2.44 MB | 2.31 MB | 498.90 kB | 488.28 kB | antd.js

10.95 MB | 3.59 MB | 3.49 MB | 913.91 kB | 915.50 kB | typescript.js
10.95 MB | 3.59 MB | 3.49 MB | 913.94 kB | 915.50 kB | typescript.js

0 comments on commit cca0034

Please sign in to comment.