Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/oxc_codegen/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1345,7 +1345,7 @@ impl Gen for RegExpLiteral<'_> {
fn gen(&self, p: &mut Codegen, _ctx: Context) {
p.add_source_mapping(self.span);
let last = p.last_byte();
let pattern_text = self.regex.pattern.source_text(p.source_text);
let pattern_text = self.regex.pattern.to_string();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because I'm not calling CodeGen::build in count_char_when_printing, self.regex.pattern.source_text(p.source_text) errors due to CodeGen::source_text being an empty string.

let mut cg = Codegen::new()
    .with_options(CodegenOptions { minify: true, ..CodegenOptions::default() });
cg.print_expression(expr);
cg.into_source_text().len()

// Avoid forming a single-line comment or "</script" sequence
if last == Some(b'/')
|| (last == Some(b'<') && pattern_text.cow_to_ascii_lowercase().starts_with("script"))
Expand Down
6 changes: 5 additions & 1 deletion crates/oxc_ecmascript/src/constant_evaluation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,11 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects {
}
None
}
_ => None,
BinaryOperator::In
| BinaryOperator::Equality
| BinaryOperator::Inequality
| BinaryOperator::StrictEquality
| BinaryOperator::StrictInequality => None,
}
}

Expand Down
113 changes: 22 additions & 91 deletions crates/oxc_minifier/src/peephole/fold_constants.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use oxc_ast::ast::*;
use oxc_codegen::{Codegen, CodegenOptions};
use oxc_ecmascript::{
constant_evaluation::{ConstantEvaluation, ConstantValue, ValueType},
side_effects::MayHaveSideEffects,
Expand All @@ -19,8 +20,9 @@ impl<'a> PeepholeOptimizations {
///
/// <https://github.com/google/closure-compiler/blob/v20240609/src/com/google/javascript/jscomp/PeepholeFoldConstants.java>
pub fn fold_constants_exit_expression(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
let current_len = Self::count_char_when_printing(expr);
if let Some(folded_expr) = match expr {
Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, ctx)
Expression::BinaryExpression(e) => Self::try_fold_binary_expr(e, current_len, ctx)
.or_else(|| Self::try_fold_binary_typeof_comparison(e, ctx)),
Expression::UnaryExpression(e) => Self::try_fold_unary_expr(e, ctx),
Expression::StaticMemberExpression(e) => Self::try_fold_static_member_expr(e, ctx),
Expand Down Expand Up @@ -202,25 +204,15 @@ impl<'a> PeepholeOptimizations {
}
}

fn extract_numeric_values(e: &BinaryExpression<'a>) -> Option<(f64, f64)> {
if let (Expression::NumericLiteral(left), Expression::NumericLiteral(right)) =
(&e.left, &e.right)
{
return Some((left.value, right.value));
}
None
}

#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn try_fold_binary_expr(
e: &mut BinaryExpression<'a>,
current_len: usize,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
// TODO: tryReduceOperandsForOp

// https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1136
// https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1222
let span = e.span;
match e.operator {
BinaryOperator::Equality
| BinaryOperator::Inequality
Expand All @@ -234,80 +226,19 @@ impl<'a> PeepholeOptimizations {
ctx.eval_binary(e).or_else(|| Self::try_fold_left_child_op(e, ctx))
}
BinaryOperator::Addition => Self::try_fold_add(e, ctx),
BinaryOperator::Subtraction => {
// Subtraction of small-ish integers can definitely be folded without issues
Self::extract_numeric_values(e)
.filter(|(left, right)| {
left.is_nan()
|| left.is_finite()
|| right.is_nan()
|| right.is_finite()
|| (left.fract() == 0.0
&& right.fract() == 0.0
&& (left.abs() as usize) <= 0xFFFF_FFFF
&& (right.abs() as usize) <= 0xFFFF_FFFF)
})
.and_then(|_| ctx.eval_binary(e))
_ => {
let folded_expr = ctx.eval_binary(e)?;
let folded_len = Self::count_char_when_printing(&folded_expr);
(folded_len <= current_len).then_some(folded_expr)
}
BinaryOperator::Multiplication
| BinaryOperator::Exponential
| BinaryOperator::Remainder => Self::extract_numeric_values(e)
.filter(|(left, right)| {
*left == 0.0
|| left.is_nan()
|| left.is_infinite()
|| *right == 0.0
|| right.is_nan()
|| right.is_infinite()
})
.and_then(|_| ctx.eval_binary(e)),
BinaryOperator::Division => Self::extract_numeric_values(e)
.filter(|(_, right)| *right == 0.0 || right.is_nan() || right.is_infinite())
.and_then(|_| ctx.eval_binary(e)),
BinaryOperator::ShiftLeft => {
if let Some((left, right)) = Self::extract_numeric_values(e) {
let result = ctx.eval_binary_expression(e)?.into_number()?;
let left_len = Self::approximate_printed_int_char_count(left);
let right_len = Self::approximate_printed_int_char_count(right);
let result_len = Self::approximate_printed_int_char_count(result);
if result_len <= left_len + 2 + right_len {
return Some(ctx.value_to_expr(span, ConstantValue::Number(result)));
}
}
None
}
BinaryOperator::ShiftRightZeroFill => {
if let Some((left, right)) = Self::extract_numeric_values(e) {
let result = ctx.eval_binary_expression(e)?.into_number()?;
let left_len = Self::approximate_printed_int_char_count(left);
let right_len = Self::approximate_printed_int_char_count(right);
let result_len = Self::approximate_printed_int_char_count(result);
if result_len <= left_len + 3 + right_len {
return Some(ctx.value_to_expr(span, ConstantValue::Number(result)));
}
}
None
}
BinaryOperator::ShiftRight | BinaryOperator::Instanceof => ctx.eval_binary(e),
BinaryOperator::In => None,
}
}

// https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_ast/js_ast_helpers.go#L1128
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
#[must_use]
fn approximate_printed_int_char_count(value: f64) -> usize {
let mut count = if value.is_infinite() {
"Infinity".len()
} else if value.is_nan() {
"NaN".len()
} else {
1 + 0.max(value.abs().log10().floor() as usize)
};
if value.is_sign_negative() {
count += 1;
}
count
fn count_char_when_printing(expr: &Expression) -> usize {
let mut cg = Codegen::new()
.with_options(CodegenOptions { minify: true, ..CodegenOptions::default() });
cg.print_expression(expr);
cg.into_source_text().len()
}

// Simplified version of `tryFoldAdd` from closure compiler.
Expand Down Expand Up @@ -1614,7 +1545,7 @@ mod test {

#[test]
fn test_fold_multiply() {
fold_same("x = 2.25 * 3");
fold("x = 2.25 * 3", "x = 6.75");
fold_same("z = x * y");
fold_same("x = y * 5");
// test("x = null * undefined", "x = NaN");
Expand All @@ -1631,28 +1562,28 @@ mod test {
fold("x = Infinity / 0", "x = Infinity");
fold("x = 1 / 0", "x = Infinity");
fold("x = 0 / 0", "x = NaN");
fold_same("x = 2 / 4");
fold("x = 2 / 4", "x = .5");
fold_same("x = y / 2 / 4");
}

#[test]
fn test_fold_remainder() {
fold_same("x = 3 % 2");
fold_same("x = 3 % -2");
fold_same("x = -1 % 3");
fold("x = 3 % 2", "x = 1");
fold("x = 3 % -2", "x = 1");
fold("x = -1 % 3", "x = -1");
fold("x = 1 % 0", "x = NaN");
fold("x = 0 % 0", "x = NaN");
}

#[test]
fn test_fold_exponential() {
fold_same("x = 2 ** 3");
fold_same("x = 2 ** -3");
fold("x = 2 ** 3", "x = 8");
fold("x = 2 ** -3", "x = .125");
fold_same("x = 2 ** 55");
fold_same("x = 3 ** -1");
fold_same("x = (-1) ** 0.5");
fold("x = (-1) ** 0.5", "x = NaN");
fold("x = (-0) ** 3", "x = -0");
fold_same("x = null ** 0");
fold("x = null ** 0", "x = 1");
}

#[test]
Expand Down
27 changes: 14 additions & 13 deletions crates/oxc_minifier/src/peephole/replace_known_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1813,19 +1813,20 @@ mod test {

#[test]
fn test_fold_pow() {
test("Math.pow(2, 3)", "2 ** 3");
test("Math.pow(a, 3)", "a ** 3");
test("Math.pow(2, b)", "2 ** b");
test("Math.pow(a, b)", "a ** +b");
test("Math.pow(2n, 3n)", "2n ** +3n"); // errors both before and after
test("Math.pow(a + b, c)", "(a + b) ** +c");
test_same("Math.pow()");
test_same("Math.pow(1)");
test_same("Math.pow(...a, 1)");
test_same("Math.pow(1, ...a)");
test_same("Math.pow(1, 2, 3)");
test_es2015("Math.pow(2, 3)", "Math.pow(2, 3)");
test_same("Unknown.pow(1, 2)");
test_value("Math.pow(2, 3)", "8");
test_value("Math.pow(a, 3)", "a ** 3");
test_value("Math.pow(2, b)", "2 ** b");
test_value("Math.pow(a, b)", "a ** +b");
test_value("Math.pow(2n, 3n)", "2n ** +3n"); // errors both before and after
test_value("Math.pow(a + b, c)", "(a + b) ** +c");
test_same_value("Math.pow()");
test_same_value("Math.pow(1)");
test_same_value("Math.pow(...a, 1)");
test_same_value("Math.pow(1, ...a)");
test_same_value("Math.pow(1, 2, 3)");
test_es2015("x = Math.pow(2, 3)", "x = Math.pow(2, 3)"); // NOTE: can fold to 8
test_es2015("x = Math.pow(a, 3)", "x = Math.pow(a, 3)");
test_same_value("Unknown.pow(1, 2)");
}

#[test]
Expand Down
8 changes: 4 additions & 4 deletions crates/oxc_minifier/tests/peephole/esbuild.rs
Original file line number Diff line number Diff line change
Expand Up @@ -907,10 +907,10 @@ fn constant_evaluation_test() {
test("x = +{valueOf:()=>1}", "x = +{ valueOf: () => 1 };");
test("x = 3 + 6", "x = 9;");
test("x = 3 - 6", "x = -3;");
test("x = 3 * 6", "x = 3 * 6;");
test("x = 3 / 6", "x = 3 / 6;");
test("x = 3 % 6", "x = 3 % 6;");
test("x = 3 ** 6", "x = 3 ** 6;");
test("x = 3 * 6", "x = 18;");
test("x = 3 / 6", "x = .5;");
test("x = 3 % 6", "x = 3;");
test("x = 3 ** 6", "x = 729;");
test("x = 0 / 0", "x = NaN;");
test("x = 123 / 0", "x = Infinity;");
test("x = 123 / -0", "x = -Infinity;");
Expand Down
10 changes: 5 additions & 5 deletions tasks/minsize/minsize.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ Original | minified | minified | gzip | gzip | Fixture
-------------------------------------------------------------------------------------
72.14 kB | 23.49 kB | 23.70 kB | 8.47 kB | 8.54 kB | react.development.js

173.90 kB | 59.53 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js
173.90 kB | 59.51 kB | 59.82 kB | 19.18 kB | 19.33 kB | moment.js

287.63 kB | 89.34 kB | 90.07 kB | 30.95 kB | 31.95 kB | jquery.js

342.15 kB | 117.25 kB | 118.14 kB | 43.31 kB | 44.37 kB | vue.js

544.10 kB | 71.38 kB | 72.48 kB | 25.85 kB | 26.20 kB | lodash.js

555.77 kB | 270.83 kB | 270.13 kB | 88.26 kB | 90.80 kB | d3.js
555.77 kB | 270.83 kB | 270.13 kB | 88.27 kB | 90.80 kB | d3.js

1.01 MB | 440.17 kB | 458.89 kB | 122.38 kB | 126.71 kB | bundle.min.js
1.01 MB | 440.17 kB | 458.89 kB | 122.37 kB | 126.71 kB | bundle.min.js

1.25 MB | 647.05 kB | 646.76 kB | 160.27 kB | 163.73 kB | three.js
1.25 MB | 647.04 kB | 646.76 kB | 160.27 kB | 163.73 kB | three.js

2.14 MB | 716.14 kB | 724.14 kB | 161.79 kB | 181.07 kB | victory.js

3.20 MB | 1.01 MB | 1.01 MB | 324.15 kB | 331.56 kB | echarts.js
3.20 MB | 1.01 MB | 1.01 MB | 324.14 kB | 331.56 kB | echarts.js

6.69 MB | 2.28 MB | 2.31 MB | 466.12 kB | 488.28 kB | antd.js

Expand Down