mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 14:49:36 +00:00
perf(minifier): stop re-deriving printed numeric spellings (#25103)
The parser cannot produce negative or non-finite NumericLiterals: `-1` parses as unary negation, and codegen prints `Infinity` as `1/0`, which parses as a division. The loop's constant folds re-derive the literal from either spelling and record a change for it, one phantom change per such number on every run over printed output. Collapse `-` into a negative literal in the Normalize pass — the same treatment `NaN` and `Infinity` identifiers already get — and keep `1/0` and `-1/0` unfolded like the existing `!0` and `void 0` guards: they are the canonical printed spellings of the non-finite numbers. The guard shifts an inlining decision in lodash.js, making it 20 bytes smaller; all other minsize outputs are unchanged.
This commit is contained in:
1 parent
9dc7756729
commit
fb402b010c
6 files changed
+30
-6
No files matched your search
@@ -311,8 +311,18 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
|
||||
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
||||
#[expect(clippy::float_cmp)]
|
||||
pub fn fold_binary_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
// Do not fold `1/0` and `-1/0`: codegen prints the non-finite numbers
|
||||
// in exactly this form, so like `!0` and `void 0` above, folding them
|
||||
// cannot shrink the output and only records a change on every run.
|
||||
if e.operator == BinaryOperator::Division
|
||||
&& matches!(&e.right, Expression::NumericLiteral(r) if r.value == 0.0 && r.value.is_sign_positive())
|
||||
&& matches!(&e.left, Expression::NumericLiteral(l) if l.value.abs() == 1.0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// TODO: tryReduceOperandsForOp
|
||||
|
||||
// https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1136
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::generated::ancestor::Ancestor;
|
||||
use oxc_allocator::{ArenaVec, ReplaceWith, TakeIn};
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{
|
||||
constant_evaluation::{DetermineValueType, ValueType},
|
||||
constant_evaluation::{ConstantValue, DetermineValueType, ValueType},
|
||||
side_effects::{is_typed_array_constructor, is_valid_regexp},
|
||||
};
|
||||
use oxc_semantic::IsGlobalReference;
|
||||
@@ -144,6 +144,15 @@ impl<'a> Traverse<'a> for Normalize {
|
||||
Self::fold_void_ident(e, ctx);
|
||||
None
|
||||
}
|
||||
// `-1` parses as unary negation of `1`. Collapsing it into a negative
|
||||
// literal does not change the output, so do it here instead of letting
|
||||
// the loop count it as compression progress on every run.
|
||||
Expression::UnaryExpression(e)
|
||||
if e.operator == UnaryOperator::UnaryNegation
|
||||
&& let Expression::NumericLiteral(lit) = &e.argument =>
|
||||
{
|
||||
Some(ctx.value_to_expr(e.span, ConstantValue::Number(-lit.value)))
|
||||
}
|
||||
Expression::StaticMemberExpression(e) => Self::fold_number_nan_to_nan(e, ctx),
|
||||
_ => None,
|
||||
} {
|
||||
|
||||
@@ -1062,7 +1062,12 @@ fn test_fold_multiply() {
|
||||
fn test_fold_division() {
|
||||
fold("x = Infinity / Infinity", "x = NaN");
|
||||
fold("x = Infinity / 0", "x = Infinity");
|
||||
fold("x = 1 / 0", "x = Infinity");
|
||||
// `1 / 0` is the canonical printed spelling of Infinity and is kept as-is.
|
||||
fold_same("x = 1 / 0");
|
||||
fold_same("x = -1 / 0");
|
||||
// A negative-zero divisor is not canonical and can still be folded.
|
||||
fold("x = 1 / -0", "x = -Infinity");
|
||||
fold("x = -1 / -0", "x = Infinity");
|
||||
fold("x = 0 / 0", "x = NaN");
|
||||
fold("x = 360 / 360", "x = 1");
|
||||
fold("x = 10.5 / 0.75", "x = 14");
|
||||
|
||||
@@ -113,8 +113,8 @@ fn test_numeric_comparison_edge_cases() {
|
||||
#[test]
|
||||
fn test_mathematical_expression_edge_cases() {
|
||||
// Test operations with special numeric values get optimized
|
||||
test("return 1 / 0", "return Infinity"); // optimized to Infinity
|
||||
test("return -1 / 0", "return -Infinity"); // optimized to -Infinity
|
||||
test_same("return 1 / 0"); // canonical printed spelling of Infinity
|
||||
test_same("return -1 / 0"); // canonical printed spelling of -Infinity
|
||||
test("return 0 / 0", "return NaN"); // optimized to NaN
|
||||
|
||||
// Test simple arithmetic - these ARE optimized by oxc
|
||||
|
||||
@@ -9,7 +9,7 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
|
||||
|
||||
342.15 kB | 116.92 kB | 118.14 kB | 43.17 kB | 44.37 kB | 2 | vue.js
|
||||
|
||||
544.10 kB | 70.91 kB | 72.48 kB | 25.73 kB | 26.20 kB | 2 | lodash.js
|
||||
544.10 kB | 70.89 kB | 72.48 kB | 25.71 kB | 26.20 kB | 2 | lodash.js
|
||||
|
||||
555.77 kB | 267.19 kB | 270.13 kB | 87.99 kB | 90.80 kB | 2 | d3.js
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ antd.js:
|
||||
sys deallocs: 1038
|
||||
sys alloc bytes: 29976031 # 29.98 MB
|
||||
sys peak growth: 19934725 # 19.93 MB
|
||||
arena allocs: 371609
|
||||
arena allocs: 371607
|
||||
arena reallocs: 76289
|
||||
arena size: 49109008 # 49.11 MB
|
||||
|
||||
|
||||
Reference in new issue
Block a user