From b6cd45c1f253891f8d240d23ebf19c3010045dec Mon Sep 17 00:00:00 2001 From: Ethan Goh <7086cmd@gmail.com> Date: Tue, 1 Oct 2024 15:26:52 +0800 Subject: [PATCH 1/6] feat: support bitwise unary folding. --- .../src/ast_passes/peephole_fold_constants.rs | 61 ++++++++++++++++--- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 728c3420a77d9..6b1d9a819a1b5 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1,13 +1,13 @@ -use std::cmp::Ordering; - 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; use crate::{ node_util::{ @@ -165,6 +165,47 @@ impl<'a> PeepholeFoldConstants { _ if expr.argument.is_number() => Some(ctx.ast.move_expression(&mut expr.argument)), _ => None, }, + UnaryOperator::BitwiseNot => match &mut expr.argument { + Expression::NumericLiteral(n) => (n.value.is_finite() && n.value.fract() == 0.0) + .then(|| { + let bits = n.value.to_js_int_32(); + let value = !bits; + ctx.ast.expression_numeric_literal( + SPAN, + value.into(), + value.to_string(), + NumberBase::Decimal, + ) + }), + Expression::UnaryExpression(un) => { + match un.operator { + UnaryOperator::BitwiseNot => { + // Return the unbitten value + Some(ctx.ast.move_expression(&mut un.argument)) + } + UnaryOperator::UnaryNegation if un.argument.is_number() => { + // `-~1` -> `2` + let mut num = ctx.ast.move_expression(&mut un.argument); + if let Expression::NumericLiteral(n) = &mut num { + (n.value.is_finite() && n.value.fract() == 0.0).then(|| { + let bits = -n.value.to_js_int_32(); + let value = !bits; + ctx.ast.expression_numeric_literal( + SPAN, + value.into(), + value.to_string(), + NumberBase::Decimal, + ) + }) + } else { + None + } + } + _ => None, + } + } + _ => None + }, _ => None, } } @@ -1268,9 +1309,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"); @@ -1284,8 +1325,8 @@ mod test { test("a=+-7", "a=-7"); // test("a=+.5", "a=.5"); - // test("a=~0xffffffff", "a=0"); - // test("a=~~0xffffffff", "a=-1"); + test("a=~0xffffffff", "a=0"); + test("a=~~0xffffffff", "a=-1"); // test_same("a=~.5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); } @@ -1299,12 +1340,16 @@ 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"); + test("a = ~0x3", "a = -4"); + test("a = ~9", "a = -10"); } #[test] From b80106fb7c7ea3c4f42aa12a689572df4dc13257 Mon Sep 17 00:00:00 2001 From: Ethan Goh <7086cmd@gmail.com> Date: Tue, 1 Oct 2024 16:10:36 +0800 Subject: [PATCH 2/6] fix: ignore exceeded numbers. --- .../src/ast_passes/peephole_fold_constants.rs | 67 ++++++++++--------- tasks/minsize/minsize.snap | 8 +-- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 6b1d9a819a1b5..65508d3b1fe61 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1,3 +1,11 @@ +use crate::{ + node_util::{ + is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, NumberValue, ValueType, + }, + tri::Tri, + ty::Ty, + CompressorPass, +}; use num_bigint::BigInt; use oxc_ast::ast::*; use oxc_span::{GetSpan, Span, SPAN}; @@ -9,15 +17,6 @@ use oxc_syntax::{ use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; use std::cmp::Ordering; -use crate::{ - node_util::{ - is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, NumberValue, ValueType, - }, - tri::Tri, - ty::Ty, - CompressorPass, -}; - /// Constant Folding /// /// @@ -139,6 +138,9 @@ impl<'a> PeepholeFoldConstants { expr: &mut UnaryExpression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Option> { + fn is_within_i32_range(x: f64) -> bool { + x.is_finite() && x.fract() == 0.0 && x >= i32::MIN as f64 && x <= i32::MAX as f64 + } match expr.operator { UnaryOperator::Void => self.try_reduce_void(expr, ctx), UnaryOperator::Typeof => self.try_fold_type_of(expr, ctx), @@ -166,30 +168,26 @@ impl<'a> PeepholeFoldConstants { _ => None, }, UnaryOperator::BitwiseNot => match &mut expr.argument { - Expression::NumericLiteral(n) => (n.value.is_finite() && n.value.fract() == 0.0) - .then(|| { - let bits = n.value.to_js_int_32(); - let value = !bits; - ctx.ast.expression_numeric_literal( - SPAN, - value.into(), - value.to_string(), - NumberBase::Decimal, - ) - }), + 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 unbitten value + // Return the un-bitten value Some(ctx.ast.move_expression(&mut un.argument)) } UnaryOperator::UnaryNegation if un.argument.is_number() => { // `-~1` -> `2` - let mut num = ctx.ast.move_expression(&mut un.argument); - if let Expression::NumericLiteral(n) = &mut num { - (n.value.is_finite() && n.value.fract() == 0.0).then(|| { - let bits = -n.value.to_js_int_32(); - let value = !bits; + 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(), @@ -204,7 +202,7 @@ impl<'a> PeepholeFoldConstants { _ => None, } } - _ => None + _ => None, }, _ => None, } @@ -1325,8 +1323,8 @@ mod test { test("a=+-7", "a=-7"); // test("a=+.5", "a=.5"); - test("a=~0xffffffff", "a=0"); - test("a=~~0xffffffff", "a=-1"); + // test("a=~0xffffffff", "a=0"); + // test("a=~~0xffffffff", "a=-1"); // test_same("a=~.5", PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND); } @@ -1347,9 +1345,14 @@ mod test { test("a = ~101", "a = -102"); // More tests added by Ethan, which aligns with Google Closure Compiler's behavior - test_same("a = ~1.1"); - test("a = ~0x3", "a = -4"); - test("a = ~9", "a = -10"); + 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"); + // 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] diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 858312f949565..9d9cc4097e66f 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -2,7 +2,7 @@ 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 @@ -10,17 +10,17 @@ Original | Minified | esbuild | Gzip | esbuild 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 -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 From 154ae2da816f5173803ef37663053a0edcfd08b7 Mon Sep 17 00:00:00 2001 From: Ethan Goh <7086cmd@gmail.com> Date: Tue, 1 Oct 2024 16:37:34 +0800 Subject: [PATCH 3/6] refactor: use `from`. --- crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 65508d3b1fe61..d312e44c570c9 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -139,7 +139,7 @@ impl<'a> PeepholeFoldConstants { ctx: &mut TraverseCtx<'a>, ) -> Option> { fn is_within_i32_range(x: f64) -> bool { - x.is_finite() && x.fract() == 0.0 && x >= i32::MIN as f64 && x <= i32::MAX as f64 + 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), From 5f93b9aafb27ef62c433bba894dc5cee194f1b18 Mon Sep 17 00:00:00 2001 From: Ethan Goh <7086cmd@gmail.com> Date: Tue, 1 Oct 2024 16:39:18 +0800 Subject: [PATCH 4/6] chore: refactor. --- .../oxc_minifier/src/ast_passes/peephole_fold_constants.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index d312e44c570c9..a2db43d53acb8 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -139,7 +139,10 @@ impl<'a> PeepholeFoldConstants { ctx: &mut TraverseCtx<'a>, ) -> Option> { 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) + 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), From 8223c79d7771cff911f95db97f971e9a9c17fe30 Mon Sep 17 00:00:00 2001 From: Ethan Goh <7086cmd@gmail.com> Date: Tue, 1 Oct 2024 16:43:06 +0800 Subject: [PATCH 5/6] test: add more tests for edge cases. --- crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index a2db43d53acb8..4bac26fbead57 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1352,6 +1352,8 @@ mod test { 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"); From fc3bd30ba80614c93e85191cf873f7f68211afe1 Mon Sep 17 00:00:00 2001 From: Ethan Goh <7086cmd@gmail.com> Date: Tue, 1 Oct 2024 18:00:36 +0800 Subject: [PATCH 6/6] chore: re-order import. --- .../src/ast_passes/peephole_fold_constants.rs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 4bac26fbead57..99e7a75cade04 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -1,3 +1,14 @@ +use std::cmp::Ordering; + +use num_bigint::BigInt; +use oxc_ast::ast::*; +use oxc_span::{GetSpan, Span, SPAN}; +use oxc_syntax::{ + number::{NumberBase, ToJsInt32}, + operator::{BinaryOperator, LogicalOperator, UnaryOperator}, +}; +use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; + use crate::{ node_util::{ is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, NumberValue, ValueType, @@ -6,16 +17,6 @@ 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 ///