Skip to content

Commit

Permalink
feat(minifier): replace Number.*_SAFE_INTEGER/Number.EPSILON
Browse files Browse the repository at this point in the history
  • Loading branch information
sapphi-red committed Jan 23, 2025
1 parent 5896784 commit a506b39
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 24 deletions.
109 changes: 88 additions & 21 deletions crates/oxc_minifier/src/peephole/replace_known_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use oxc_ecmascript::{
constant_evaluation::ConstantEvaluation, StringCharAt, StringCharCodeAt, StringIndexOf,
StringLastIndexOf, StringSubstring, ToInt32,
};
use oxc_span::SPAN;
use oxc_syntax::es_target::ESTarget;
use oxc_traverse::{Ancestor, TraverseCtx};

use crate::ctx::Ctx;
Expand Down Expand Up @@ -491,10 +493,15 @@ impl<'a> PeepholeOptimizations {
}
_ => return,
};
let replacement = match name {
"POSITIVE_INFINITY" | "NEGATIVE_INFINITY" | "NaN" => {
Self::try_fold_number_constants(object, name, span, ctx)
}
let Expression::Identifier(ident) = object else { return };

let ctx = &mut Ctx(ctx);
if !ctx.is_global_reference(ident) {
return;
}

let replacement = match ident.name.as_str() {
"Number" => self.try_fold_number_constants(name, span, ctx),
_ => None,
};
if let Some(replacement) = replacement {
Expand All @@ -505,28 +512,68 @@ impl<'a> PeepholeOptimizations {

/// replace `Number.*` constants
fn try_fold_number_constants(
object: &Expression<'a>,
&self,
name: &str,
span: Span,
ctx: &mut TraverseCtx<'a>,
ctx: &mut Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let ctx = Ctx(ctx);
let Expression::Identifier(ident) = object else { return None };
if ident.name != "Number" || !ctx.is_global_reference(ident) {
return None;
}
let num = |span: Span, n: f64| {
ctx.ast.expression_numeric_literal(span, n, None, NumberBase::Decimal)
};
// [neg] base ** exponent [op] a
let pow_with_expr =
|span: Span, base: f64, exponent: f64, op: BinaryOperator, a: f64| -> Expression<'a> {
ctx.ast.expression_binary(
span,
ctx.ast.expression_binary(
SPAN,
num(SPAN, base),
BinaryOperator::Exponential,
num(SPAN, exponent),
),
op,
num(SPAN, a),
)
};

Some(match name {
"POSITIVE_INFINITY" => {
ctx.ast.expression_numeric_literal(span, f64::INFINITY, None, NumberBase::Decimal)
"POSITIVE_INFINITY" => num(span, f64::INFINITY),
"NEGATIVE_INFINITY" => num(span, f64::NEG_INFINITY),
"NaN" => num(span, f64::NAN),
"MAX_SAFE_INTEGER" => {
#[allow(clippy::cast_precision_loss)]
if self.target < ESTarget::ES2016 {
num(span, 2.0f64.powf(53.0) - 1.0)
} else {
// 2**53 - 1
pow_with_expr(span, 2.0, 53.0, BinaryOperator::Subtraction, 1.0)
}
}
"MIN_SAFE_INTEGER" => {
#[allow(clippy::cast_precision_loss)]
if self.target < ESTarget::ES2016 {
num(span, -(2.0f64.powf(53.0) - 1.0))
} else {
// -(2**53 - 1)
ctx.ast.expression_unary(
span,
UnaryOperator::UnaryNegation,
pow_with_expr(SPAN, 2.0, 53.0, BinaryOperator::Subtraction, 1.0),
)
}
}
"EPSILON" => {
if self.target < ESTarget::ES2016 {
return None;
}
// 2**-52
ctx.ast.expression_binary(
span,
num(SPAN, 2.0),
BinaryOperator::Exponential,
num(SPAN, -52.0),
)
}
"NEGATIVE_INFINITY" => ctx.ast.expression_numeric_literal(
span,
f64::NEG_INFINITY,
None,
NumberBase::Decimal,
),
"NaN" => ctx.ast.expression_numeric_literal(span, f64::NAN, None, NumberBase::Decimal),
_ => return None,
})
}
Expand All @@ -535,7 +582,17 @@ impl<'a> PeepholeOptimizations {
/// Port from: <https://github.com/google/closure-compiler/blob/v20240609/test/com/google/javascript/jscomp/PeepholeReplaceKnownMethodsTest.java>
#[cfg(test)]
mod test {
use crate::tester::{test, test_same};
use oxc_syntax::es_target::ESTarget;

use crate::{
tester::{run, test, test_same},
CompressOptions,
};

fn test_es2015(code: &str, expected: &str) {
let opts = CompressOptions { target: ESTarget::ES2015, ..CompressOptions::default() };
assert_eq!(run(code, Some(opts)), run(expected, None));
}

#[test]
fn test_string_index_of() {
Expand Down Expand Up @@ -1410,9 +1467,19 @@ mod test {
test("v = Number.POSITIVE_INFINITY", "v = Infinity");
test("v = Number.NEGATIVE_INFINITY", "v = -Infinity");
test("v = Number.NaN", "v = NaN");
test("v = Number.MAX_SAFE_INTEGER", "v = 2**53-1");
test("v = Number.MIN_SAFE_INTEGER", "v = -(2**53-1)");
test("v = Number.EPSILON", "v = 2**-52");

test_same("Number.POSITIVE_INFINITY = 1");
test_same("Number.NEGATIVE_INFINITY = 1");
test_same("Number.NaN = 1");
test_same("Number.MAX_SAFE_INTEGER = 1");
test_same("Number.MIN_SAFE_INTEGER = 1");
test_same("Number.EPSILON = 1");

test_es2015("v = Number.MAX_SAFE_INTEGER", "v = 9007199254740991");
test_es2015("v = Number.MIN_SAFE_INTEGER", "v = -9007199254740991");
test_es2015("v = Number.EPSILON", "v = Number.EPSILON");
}
}
6 changes: 3 additions & 3 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ Original | minified | minified | gzip | gzip | Fixture

1.01 MB | 460.16 kB | 458.89 kB | 126.78 kB | 126.71 kB | bundle.min.js

1.25 MB | 652.85 kB | 646.76 kB | 163.53 kB | 163.73 kB | three.js
1.25 MB | 652.68 kB | 646.76 kB | 163.48 kB | 163.73 kB | three.js

2.14 MB | 723.96 kB | 724.14 kB | 179.91 kB | 181.07 kB | victory.js
2.14 MB | 723.85 kB | 724.14 kB | 179.88 kB | 181.07 kB | victory.js

3.20 MB | 1.01 MB | 1.01 MB | 331.98 kB | 331.56 kB | echarts.js

6.69 MB | 2.31 MB | 2.31 MB | 491.94 kB | 488.28 kB | antd.js
6.69 MB | 2.31 MB | 2.31 MB | 491.91 kB | 488.28 kB | antd.js

10.95 MB | 3.48 MB | 3.49 MB | 905.29 kB | 915.50 kB | typescript.js

0 comments on commit a506b39

Please sign in to comment.