Skip to content

Commit

Permalink
feat(minifier): evaluate Math.sqrt and Math.cbrt (#8731)
Browse files Browse the repository at this point in the history
Replaces `Math.sqrt(a)` and `Math.cbrt(a)` when a is a constant and the result is an integer.
The motivation of this PR is to add a comment that `Math.sqrt` / `Math.cbrt` cannot be replaced to `**`, rather than the actual minification improvement.

**Reference**
- [Spec of `Math.sqrt`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-math.sqrt)
- [Spec of `Math.cbrt`](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-math.cbrt)
- [Spec of `Number::exponentiate`](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate)
  • Loading branch information
sapphi-red committed Jan 26, 2025
1 parent 360d49e commit 9e32f55
Showing 1 changed file with 75 additions and 0 deletions.
75 changes: 75 additions & 0 deletions crates/oxc_minifier/src/peephole/replace_known_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ impl<'a> PeepholeOptimizations {
"fromCharCode" => Self::try_fold_string_from_char_code(*span, arguments, object, ctx),
"toString" => Self::try_fold_to_string(*span, arguments, object, ctx),
"pow" => self.try_fold_pow(*span, arguments, object, ctx),
"sqrt" | "cbrt" => Self::try_fold_roots(*span, arguments, name, object, ctx),
_ => None,
};
if let Some(replacement) = replacement {
Expand Down Expand Up @@ -388,6 +389,57 @@ impl<'a> PeepholeOptimizations {
))
}

/// `Math.sqrt(a)`, `Math.cbrt(a)`
///
/// These cannot be replaced with `a ** .5`, `a ** (1/3)` because `Math.sqrt(-0)` returns `-0` where `(-0) ** .5` returns `0`.
/// It can be replaced when the value is known to be not `-0`, but that makes the gzip output worse.
fn try_fold_roots(
span: Span,
arguments: &Arguments,
name: &str,
object: &Expression<'a>,
ctx: Ctx<'a, '_>,
) -> Option<Expression<'a>> {
let Expression::Identifier(ident) = object else { return None };
if ident.name != "Math" || !ctx.is_global_reference(ident) {
return None;
}
if arguments.len() != 1 || !arguments[0].is_expression() {
return None;
}
let arg_val = ctx.get_side_free_number_value(arguments[0].to_expression())?;
if arg_val == f64::INFINITY || arg_val.is_nan() || arg_val == 0.0 {
return Some(ctx.ast.expression_numeric_literal(
span,
arg_val,
None,
NumberBase::Decimal,
));
}
if arg_val < 0.0 {
return Some(ctx.ast.expression_numeric_literal(
span,
f64::NAN,
None,
NumberBase::Decimal,
));
}
let calculated_val = match name {
"sqrt" => arg_val.sqrt(),
"cbrt" => arg_val.cbrt(),
_ => unreachable!(),
};
if calculated_val.fract() == 0.0 {
return Some(ctx.ast.expression_numeric_literal(
span,
calculated_val,
None,
NumberBase::Decimal,
));
}
None
}

/// `[].concat(a).concat(b)` -> `[].concat(a, b)`
/// `"".concat(a).concat(b)` -> `"".concat(a, b)`
fn try_fold_concat_chain(&mut self, node: &mut Expression<'a>, ctx: Ctx<'a, '_>) {
Expand Down Expand Up @@ -1533,6 +1585,29 @@ mod test {
test_same("Unknown.pow(1, 2)");
}

#[test]
fn test_fold_roots() {
test_same("v = Math.sqrt()");
test_same("v = Math.sqrt(1, 2)");
test_same("v = Math.sqrt(...a)");
test_same("v = Math.sqrt(a)"); // a maybe -0
test_same("v = Math.sqrt(2n)");
test("v = Math.sqrt(Infinity)", "v = Infinity");
test("v = Math.sqrt(NaN)", "v = NaN");
test("v = Math.sqrt(0)", "v = 0");
test("v = Math.sqrt(-0)", "v = -0");
test("v = Math.sqrt(-1)", "v = NaN");
test("v = Math.sqrt(-Infinity)", "v = NaN");
test("v = Math.sqrt(1)", "v = 1");
test("v = Math.sqrt(4)", "v = 2");
test_same("v = Math.sqrt(2)");
test("v = Math.cbrt(1)", "v = 1");
test("v = Math.cbrt(8)", "v = 2");
test_same("v = Math.cbrt(2)");
test_same("Unknown.sqrt(1)");
test_same("Unknown.cbrt(1)");
}

#[test]
fn test_number_constants() {
test("v = Number.POSITIVE_INFINITY", "v = Infinity");
Expand Down

0 comments on commit 9e32f55

Please sign in to comment.