mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
feat(minifier): shorten integer zero comparisons (#24933)
Integer-valued comparisons with zero remain verbose in value contexts, even when the minifier can prove that the left-hand side is an `int32` or `uint32`. Applying the same rewrite to every Number-valued expression would be incorrect for `NaN`, while bitwise expressions with unknown operands may still evaluate as BigInt. Reuse the existing `IsInt32OrUint32` proof to lower equality with zero to logical negation and inequality with zero to double negation. The optimization remains inside binary-expression compression and does not change statement transforms or DCE. ```js // before f((a & 1) != 0); f((a ^ 2) === 0); // after f(!!(a & 1)); f(!(a ^ 2)); ``` Verified with `just ready`, `just minsize`, and `just allocs`. On the latest `main`, the minsize snapshot improves Three.js raw output from 642.46 kB to 642.45 kB and TypeScript GZIP output from 852.94 kB to 852.81 kB; allocations and compression-pass iterations are unchanged. AI disclosure: Codex was used to research, implement, and verify this change.
This commit is contained in:
1 parent
2ee6e7a759
commit
e9e0f24dca
3 files changed
+37
-5
No files matched your search
@@ -2,7 +2,7 @@ use oxc_allocator::TakeIn;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_compat::ESFeature;
|
||||
use oxc_ecmascript::{
|
||||
constant_evaluation::{ConstantEvaluation, ConstantValue, DetermineValueType},
|
||||
constant_evaluation::{ConstantEvaluation, ConstantValue, DetermineValueType, IsInt32OrUint32},
|
||||
side_effects::MayHaveSideEffects,
|
||||
};
|
||||
use oxc_semantic::ReferenceFlags;
|
||||
@@ -65,13 +65,29 @@ impl<'a> PeepholeOptimizations {
|
||||
// `a instanceof b === true` -> `a instanceof b`
|
||||
// `a instanceof b === false` -> `!(a instanceof b)`
|
||||
// ^^^^^^^^^^^^^^ `ctx.expression_value_type(&e.left).is_boolean()` is `true`.
|
||||
// `x >> +y !== 0` -> `x >> +y`
|
||||
// ^^^^^^^ ctx.expression_value_type(&e.left).is_number()` is `true`.
|
||||
// `x >> +y !== 0` -> `!!(x >> +y)`
|
||||
// ^^^^^^^ `e.left.is_int32_or_uint32(ctx)` is `true`.
|
||||
pub fn minimize_binary(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let Expression::BinaryExpression(e) = expr else { return };
|
||||
if !e.operator.is_equality() {
|
||||
return;
|
||||
}
|
||||
// `NaN == 0` and `!NaN` differ, so require proof that the left side is a non-NaN Number.
|
||||
if e.right.is_number_0() && e.left.is_int32_or_uint32(ctx) {
|
||||
let argument = e.left.take_in(ctx);
|
||||
let mut new_expr =
|
||||
Expression::new_unary_expression(e.span, UnaryOperator::LogicalNot, argument, ctx);
|
||||
if matches!(e.operator, BinaryOperator::Inequality | BinaryOperator::StrictInequality) {
|
||||
new_expr = Expression::new_unary_expression(
|
||||
e.span,
|
||||
UnaryOperator::LogicalNot,
|
||||
new_expr,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
ctx.replace_expression(expr, new_expr);
|
||||
return;
|
||||
}
|
||||
let left = e.left.value_type(ctx);
|
||||
let right = e.right.value_type(ctx);
|
||||
if left.is_undetermined() || right.is_undetermined() {
|
||||
|
||||
@@ -1444,6 +1444,22 @@ fn try_minimize_binary() {
|
||||
test("f(!a === !1)", "f(!!a)");
|
||||
test("f(!a === true)", "f(!a)");
|
||||
test("f(!a === false)", "f(!!a)");
|
||||
|
||||
test("f((a & 1) != 0)", "f(!!(a & 1))");
|
||||
test("f((a & 2) == 0)", "f(!(a & 2))");
|
||||
test("f((a | 1) !== 0)", "f(!!(a | 1))");
|
||||
test("f((a ^ 2) === 0)", "f(!(a ^ 2))");
|
||||
test("f((a >>> b) !== 0)", "f(!!(a >>> b))");
|
||||
test("f(0 === (a & 4))", "f(!(a & 4))");
|
||||
|
||||
// Arithmetic can produce `NaN`, whose truthiness differs from comparison with zero.
|
||||
test_same("f((a + b) != 0)");
|
||||
test_same("f((a * 2) == 0)");
|
||||
test("f(+a === 0)", "f(+a == 0)");
|
||||
// Unknown bitwise operands can be BigInts, so their comparison must remain unchanged.
|
||||
test_same("f((a & b) !== 0)");
|
||||
test_same("f((a & 1n) !== 0)");
|
||||
test_same("f((a & 1) != 1)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -15,7 +15,7 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
|
||||
|
||||
1.01 MB | 439.26 kB | 458.89 kB | 122.03 kB | 126.71 kB | 2 | bundle.min.js
|
||||
|
||||
1.25 MB | 642.46 kB | 646.76 kB | 159.37 kB | 163.73 kB | 2 | three.js
|
||||
1.25 MB | 642.45 kB | 646.76 kB | 159.37 kB | 163.73 kB | 2 | three.js
|
||||
|
||||
2.14 MB | 710.90 kB | 724.14 kB | 160.38 kB | 181.07 kB | 2 | victory.js
|
||||
|
||||
@@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
|
||||
|
||||
6.69 MB | 2.12 MB | 2.31 MB | 453.79 kB | 488.28 kB | 5 | antd.js
|
||||
|
||||
10.95 MB | 3.33 MB | 3.49 MB | 852.94 kB | 915.50 kB | 4 | typescript.js
|
||||
10.95 MB | 3.33 MB | 3.49 MB | 852.81 kB | 915.50 kB | 4 | typescript.js
|
||||
|
||||
Reference in new issue
Block a user