mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(minifier): replace Number.*_SAFE_INTEGER/Number.EPSILON (#8682)
The value of `Number.*_SAFE_INTEGER`, `Number.EPSILON` are constants as they cannot be changed. This PR replaces them with `2**53-1` / `-(2**53-1)` / `2**-52` for ES2016+. For ES2015, `Number.EPSILON` is not changed but `Number.*_SAFE_INTEGER`s are replaced with `9007199254740991` / `-9007199254740991`. **Reference** - Spec of [`Number.MAX_SAFE_INTEGER`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.max_safe_integer) - Spec of [`Number.MIN_SAFE_INTEGER`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.min_safe_integer) - Spec of [`Number.EPSILON`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.epsilon) ### Additional Information - [`Number.MIN_VALUE`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.min_value) cannot be replaced as the value depends on the runtime - [`Number.MAX_VALUE`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.max_value) can be replaced but I didn't come up with a shorter representation that does not lack precision
This commit is contained in:
parent
0c5bb30859
commit
343690e178
2 changed files with 91 additions and 24 deletions
|
|
@ -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;
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue