feat(minifier): implement known methods Math.clz32 and Math.imul (#12405)

The known methods module of `Number` and `Math` only left `Math.pow`
unimplemented!

---------

Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com>
This commit is contained in:
Ethan Wuandsapphi-red authored and GitHub committed 2025-08-24 13:30:35 +09:00
1 parent 2a21d1b447
commit a1b6ad490a
2 files changed
+54 -37

No files matched your search

@@ -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,
},
@@ -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,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(),
"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.
@@ -499,28 +500,38 @@ 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 <https://github.com/rust-lang/rust/issues/83984>, 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
let result = match name {
"min" | "max" => {
if numbers.iter().any(|n: &f64| n.is_nan()) {
f64::NAN
} else {
match name {
// TODO
// see <https://github.com/rust-lang/rust/issues/83984>, 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,
}
}),
"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,
}
}
"imul" => {
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,
};
Some(ConstantValue::Number(result))
}
@@ -1012,9 +1012,9 @@ mod test {
}
#[test]
#[ignore]
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");
@@ -1096,27 +1096,33 @@ mod test {
}
#[test]
#[ignore]
fn test_fold_math_functions_clz32() {
test("Math.clz32(0)", "32");
let mut x = 1;
test_value("Math.clz32(0)", "32");
test_value("Math.clz32(0.0)", "32");
test_value("Math.clz32(-0.0)", "32");
let mut x = 1_i64;
for i in (0..=31).rev() {
test(&format!("{x}.leading_zeros()"), &i.to_string());
test(&format!("{}.leading_zeros()", 2 * x - 1), &i.to_string());
test_value(&format!("Math.clz32({x})"), &i.to_string());
test_value(&format!("Math.clz32({})", 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('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]