From 11dadc8c27fee63937da24b7d50c49afa3f280e7 Mon Sep 17 00:00:00 2001 From: Ethan Goh <7086cmd@gmail.com> Date: Sun, 20 Jul 2025 19:07:43 +0800 Subject: [PATCH 1/2] feat(minifier): implement known methods `Math.clz32` and `Math.imul` --- .../src/constant_evaluation/call_expr.rs | 73 +++++++++++++------ .../src/peephole/replace_known_methods.rs | 40 +++++----- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs b/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs index dbb5976e6cdd4..55f609fc41975 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs @@ -89,10 +89,10 @@ pub fn try_fold_known_global_methods<'a>( try_fold_number_methods(arguments, object, name, ctx) } "sqrt" | "cbrt" => try_fold_roots(arguments, name, object, ctx), - "abs" | "ceil" | "floor" | "round" | "fround" | "trunc" | "sign" => { + "abs" | "ceil" | "floor" | "round" | "fround" | "trunc" | "sign" | "clz32" => { try_fold_math_unary(arguments, name, object, ctx) } - "min" | "max" => try_fold_math_variadic(arguments, name, object, ctx), + "imul" | "min" | "max" => try_fold_math_variadic(arguments, name, object, ctx), _ => None, } } @@ -478,6 +478,18 @@ fn try_fold_math_unary<'a>( "sign" if arg_val.to_bits() == 0f64.to_bits() => 0f64, "sign" if arg_val.to_bits() == (-0f64).to_bits() => -0f64, "sign" => arg_val.signum(), + #[expect(clippy::cast_sign_loss)] + #[expect(clippy::cast_possible_truncation)] + "clz32" => { + let modulus = 2f64.powi(32); + let base = if arg_val < 0f64 { + let intermediate = arg_val % modulus; + if intermediate < 0f64 { intermediate + modulus } else { intermediate } + } else { + arg_val % modulus + } as u32; + f64::from(base.leading_zeros()) + } _ => unreachable!(), }; // These results are always shorter to return as a number, so we can just return them as NumericLiteral. @@ -499,28 +511,45 @@ fn try_fold_math_variadic<'a>( let value = expr.get_side_free_number_value(ctx)?; numbers.push(value); } - let result = if numbers.iter().any(|n: &f64| n.is_nan()) { - f64::NAN - } else { - match name { - // TODO - // see , we can't use `min` and `max` here due to inconsistency - "min" => numbers.iter().copied().fold(f64::INFINITY, |a, b| { - if a < b || ((a == 0f64) && (b == 0f64) && (a.to_bits() > b.to_bits())) { - a - } else { - b - } - }), - "max" => numbers.iter().copied().fold(f64::NEG_INFINITY, |a, b| { - if a > b || ((a == 0f64) && (b == 0f64) && (a.to_bits() < b.to_bits())) { - a - } else { - b + let result = match name { + "min" | "max" => { + if numbers.iter().any(|n: &f64| n.is_nan()) { + f64::NAN + } else { + match name { + // TODO + // see , we can't use `min` and `max` here due to inconsistency + "min" => numbers.iter().copied().fold(f64::INFINITY, |a, b| { + if a < b || ((a == 0f64) && (b == 0f64) && (a.to_bits() > b.to_bits())) { + a + } else { + b + } + }), + "max" => numbers.iter().copied().fold(f64::NEG_INFINITY, |a, b| { + if a > b || ((a == 0f64) && (b == 0f64) && (a.to_bits() < b.to_bits())) { + a + } else { + b + } + }), + _ => return None, } - }), - _ => return None, + } } + "imul" => { + if numbers.is_empty() { + return None; + } + if numbers.iter().take(2).any(|n| n.is_nan() || n.is_infinite()) { + 0f64 + } else { + let a = numbers.first().copied()?; + let b = numbers.get(1).copied().unwrap_or(f64::NAN); + f64::from(a.to_int_32().wrapping_mul(b.to_int_32())) + } + } + _ => return None, }; Some(ConstantValue::Number(result)) } diff --git a/crates/oxc_minifier/src/peephole/replace_known_methods.rs b/crates/oxc_minifier/src/peephole/replace_known_methods.rs index c6e7d74d19272..bde97c03d2dcd 100644 --- a/crates/oxc_minifier/src/peephole/replace_known_methods.rs +++ b/crates/oxc_minifier/src/peephole/replace_known_methods.rs @@ -1012,7 +1012,6 @@ mod test { } #[test] - #[ignore] fn test_fold_math_functions_imul() { test_same_value("Math.imul(Math.random(),2)"); test_value("Math.imul(-1,1)", "-1"); @@ -1096,27 +1095,34 @@ mod test { } #[test] - #[ignore] fn test_fold_math_functions_clz32() { - test("Math.clz32(0)", "32"); - let mut x = 1; - for i in (0..=31).rev() { - test(&format!("{x}.leading_zeros()"), &i.to_string()); - test(&format!("{}.leading_zeros()", 2 * x - 1), &i.to_string()); - x *= 2; - } - test("Math.clz32('52')", "26"); - test("Math.clz32([52])", "26"); - test("Math.clz32([52, 53])", "32"); + test_value("Math.clz32(0)", "32"); + test_value("Math.clz32(0.0)", "32"); + test_value("Math.clz32(-0.0)", "32"); + // FIXME not implemented yet + // let mut x = 1; + // for i in (0..=31).rev() { + // test(&format!("{x}.leading_zeros()"), &i.to_string()); + // test(&format!("{}.leading_zeros()", 2 * x - 1), &i.to_string()); + // x *= 2; + // } + test_value("Math.clz32('52')", "26"); + test_value("Math.clz32([52])", "26"); + test_value("Math.clz32([52, 53])", "32"); // Overflow cases - test("Math.clz32(0x100000000)", "32"); - test("Math.clz32(0x100000001)", "31"); + test_value("Math.clz32(0x100000000)", "32"); + test_value("Math.clz32(0x100000001)", "31"); + + // Negative cases + test_value("Math.clz32(-1)", "0"); + test_value("Math.clz32(-2147483647)", "0"); + test_value("Math.clz32(-2147483649)", "1"); // NaN -> 0 - test("Math.clz32(NaN)", "32"); - test("Math.clz32('foo')", "32"); - test("Math.clz32(Infinity)", "32"); + test_value("Math.clz32(NaN)", "32"); + test_value("Math.clz32('foo')", "32"); + test_value("Math.clz32(Infinity)", "32"); } #[test] From ff623b63609f2c3a2192f6b7fce7c97e91a143f4 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sun, 24 Aug 2025 01:19:14 +0900 Subject: [PATCH 2/2] refactor: simplify --- .../src/constant_evaluation/call_expr.rs | 28 ++++--------------- .../src/peephole/replace_known_methods.rs | 14 +++++----- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs b/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs index 55f609fc41975..7a9f94d89d03d 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/call_expr.rs @@ -16,7 +16,7 @@ use cow_utils::CowUtils; use crate::{ StringCharAt, StringCharAtResult, StringCharCodeAt, StringIndexOf, StringLastIndexOf, - StringSubstring, ToInt32, ToJsString as ToJsStringTrait, + StringSubstring, ToInt32, ToJsString as ToJsStringTrait, ToUint32, constant_evaluation::url_encoding::{ decode_uri_chars, encode_uri_chars, is_uri_always_unescaped, }, @@ -478,18 +478,7 @@ fn try_fold_math_unary<'a>( "sign" if arg_val.to_bits() == 0f64.to_bits() => 0f64, "sign" if arg_val.to_bits() == (-0f64).to_bits() => -0f64, "sign" => arg_val.signum(), - #[expect(clippy::cast_sign_loss)] - #[expect(clippy::cast_possible_truncation)] - "clz32" => { - let modulus = 2f64.powi(32); - let base = if arg_val < 0f64 { - let intermediate = arg_val % modulus; - if intermediate < 0f64 { intermediate + modulus } else { intermediate } - } else { - arg_val % modulus - } as u32; - f64::from(base.leading_zeros()) - } + "clz32" => f64::from(arg_val.to_uint_32().leading_zeros()), _ => unreachable!(), }; // These results are always shorter to return as a number, so we can just return them as NumericLiteral. @@ -538,16 +527,9 @@ fn try_fold_math_variadic<'a>( } } "imul" => { - if numbers.is_empty() { - return None; - } - if numbers.iter().take(2).any(|n| n.is_nan() || n.is_infinite()) { - 0f64 - } else { - let a = numbers.first().copied()?; - let b = numbers.get(1).copied().unwrap_or(f64::NAN); - f64::from(a.to_int_32().wrapping_mul(b.to_int_32())) - } + let a = numbers.first().copied().unwrap_or(f64::NAN).to_uint_32(); + let b = numbers.get(1).copied().unwrap_or(f64::NAN).to_uint_32(); + f64::from(a.wrapping_mul(b).cast_signed()) } _ => return None, }; diff --git a/crates/oxc_minifier/src/peephole/replace_known_methods.rs b/crates/oxc_minifier/src/peephole/replace_known_methods.rs index bde97c03d2dcd..827cc07969f30 100644 --- a/crates/oxc_minifier/src/peephole/replace_known_methods.rs +++ b/crates/oxc_minifier/src/peephole/replace_known_methods.rs @@ -1014,6 +1014,7 @@ mod test { #[test] fn test_fold_math_functions_imul() { test_same_value("Math.imul(Math.random(),2)"); + test_value("Math.imul()", "0"); test_value("Math.imul(-1,1)", "-1"); test_value("Math.imul(2,2)", "4"); test_value("Math.imul(2)", "0"); @@ -1099,13 +1100,12 @@ mod test { test_value("Math.clz32(0)", "32"); test_value("Math.clz32(0.0)", "32"); test_value("Math.clz32(-0.0)", "32"); - // FIXME not implemented yet - // let mut x = 1; - // for i in (0..=31).rev() { - // test(&format!("{x}.leading_zeros()"), &i.to_string()); - // test(&format!("{}.leading_zeros()", 2 * x - 1), &i.to_string()); - // x *= 2; - // } + let mut x = 1_i64; + for i in (0..=31).rev() { + test_value(&format!("Math.clz32({x})"), &i.to_string()); + test_value(&format!("Math.clz32({})", 2 * x - 1), &i.to_string()); + x *= 2; + } test_value("Math.clz32('52')", "26"); test_value("Math.clz32([52])", "26"); test_value("Math.clz32([52, 53])", "32");