feat(minifier): minimize bitwise binary expressions (#27107)

implement missing optimizations for bitwise binnary expressions
- `(a OP b) | 0` -> `a OP b`
- `a & 0xffffffff` -> `a | 0`.
This commit is contained in:
Armano authored and GitHub committed 2026-09-30 09:18:52 +01:00
1 parent b2793fa2d8
commit 1c352ce311
6 files changed
+164 -9

No files matched your search

@@ -0,0 +1,79 @@
use oxc_ast::ast::*;
use oxc_ecmascript::constant_evaluation::IsInt32OrUint32;
use oxc_syntax::operator::BinaryOperator;
use crate::TraverseCtx;
use crate::peephole::PeepholeOptimizations;
impl<'a> PeepholeOptimizations {
pub fn minimize_bitwise_binary_expr(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
let Expression::BinaryExpression(bin_expr) = expr else { return };
if !bin_expr.operator.is_bitwise() {
return;
}
match bin_expr.operator {
BinaryOperator::BitwiseAnd => {
// u32::MAX -> 2^32-1 -> 4_294_967_295
if bin_expr.right.is_number_value(4_294_967_295.0)
&& let Expression::NumericLiteral(n) = &mut bin_expr.right
{
// `a & 0xffffffff` -> `a | 0`
ctx.notice_change();
n.value = 0.0;
n.raw = None;
bin_expr.operator = BinaryOperator::BitwiseOR;
} else if bin_expr.left.is_number_value(4_294_967_295.0)
&& let Expression::NumericLiteral(n) = &mut bin_expr.left
{
// `0xffffffff & a` -> `0 | a`
ctx.notice_change();
n.value = 0.0;
n.raw = None;
bin_expr.operator = BinaryOperator::BitwiseOR;
}
}
BinaryOperator::ShiftLeft | BinaryOperator::ShiftRight | BinaryOperator::BitwiseXOR
if bin_expr.right.is_number_0() =>
{
// `a << 0` -> `a | 0`
// `a >> 0` -> `a | 0`
// `a ^ 0` -> `a | 0`
ctx.notice_change();
bin_expr.operator = BinaryOperator::BitwiseOR;
}
_ => {}
}
if bin_expr.operator == BinaryOperator::BitwiseOR {
if bin_expr.right.is_number_0() && Self::is_bitwise_int(&bin_expr.left, ctx) {
// `(a OP b) | 0` -> `a OP b`
ctx.replace_expression_with(expr, Self::unwrap_left_from_binary_expr);
} else if bin_expr.left.is_number_0() && Self::is_bitwise_int(&bin_expr.right, ctx) {
// `0 | (a OP b)` -> `a OP b`
ctx.replace_expression_with(expr, Self::unwrap_right_from_binary_expr);
}
}
}
fn is_bitwise_int(expr: &Expression<'a>, ctx: &TraverseCtx<'a>) -> bool {
let Expression::BinaryExpression(e) = expr else { return false };
e.operator != BinaryOperator::ShiftRightZeroFill && e.is_int32_or_uint32(ctx)
}
fn unwrap_left_from_binary_expr(
e: Expression<'a>,
_ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let Expression::BinaryExpression(e) = e else { unreachable!() };
e.unbox().left
}
fn unwrap_right_from_binary_expr(
e: Expression<'a>,
_ctx: &mut TraverseCtx<'a>,
) -> Expression<'a> {
let Expression::BinaryExpression(e) = e else { unreachable!() };
e.unbox().right
}
}
+2
View File
@@ -1,6 +1,7 @@
mod convert_to_dotted_properties;
mod fold_constants;
mod inline;
mod minimize_binary_expression;
mod minimize_conditional_expression;
mod minimize_conditions;
mod minimize_expression_in_boolean_context;
@@ -575,6 +576,7 @@ impl<'a> Traverse<'a> for PeepholeOptimizations {
Self::fold_sequence_expression(expr, ctx);
Self::minimize_loose_boolean(expr, ctx);
Self::minimize_binary(expr, ctx);
Self::minimize_bitwise_binary_expr(expr, ctx);
Self::substitute_loose_equals_undefined(expr, ctx);
Self::substitute_typeof_undefined(expr, ctx);
Self::substitute_rotate_binary_expression(expr, ctx);
@@ -4,7 +4,7 @@ static NEG_MAX_SAFE_FLOAT: f64 = -9_007_199_254_740_991_f64;
static MAX_SAFE_INT: i64 = 9_007_199_254_740_991_i64;
static NEG_MAX_SAFE_INT: i64 = -9_007_199_254_740_991_i64;
use crate::test;
use crate::{test, test_same};
// wrap with a function call so it doesn't get removed.
fn fold(source_text: &str, expected: &str) {
@@ -17,10 +17,6 @@ fn fold_same(source_text: &str) {
fold(source_text, source_text);
}
fn test_same(source_text: &str) {
test(source_text, source_text);
}
#[test]
fn test_comparison() {
fold("(1, 2) !== 2", "!1");
@@ -855,12 +851,12 @@ fn test_fold_bitwise_op2() {
fold("x = y | 3 | 3", "x = y | 3");
fold("x = 3 | y | 3", "x = y | 3");
fold("x = y ^ 1 ^ 1", "x = y ^ 0");
fold("x = y ^ 1 ^ 1", "x = y | 0");
fold("x = y ^ 1 ^ 2", "x = y ^ 3");
fold("x = y ^ 3 ^ 1", "x = y ^ 2");
fold("x = 3 ^ y ^ 1", "x = y ^ 2");
fold("x = y ^ 3 ^ 3", "x = y ^ 0");
fold("x = 3 ^ y ^ 3", "x = y ^ 0");
fold("x = y ^ 3 ^ 3", "x = y | 0");
fold("x = 3 ^ y ^ 3", "x = y | 0");
fold("x = Infinity | NaN", "x=0");
fold("x = 12 | NaN", "x=12");
@@ -0,0 +1,77 @@
use crate::test;
// wrap with a function call so it doesn't get removed.
fn fold(source_text: &str, expected: &str) {
let source_text = format!("NOOP({source_text})");
let expected = format!("NOOP({expected})");
test(&source_text, &expected);
}
fn fold_same(source_text: &str) {
fold(source_text, source_text);
}
#[test]
fn minimize_bitwise_binary_expr() {
// `(a OP b) | 0` -> `a OP b`.
fold_same("a << b | 0");
fold_same("0 | (a << b)");
fold_same("a >> b | 0");
fold_same("0 | (a >> b)");
fold_same("a >>> b | 0");
fold_same("0 | (a >>> b)");
fold_same("(-1 >>> a) | b");
fold_same("a | b | 0");
fold_same("0 | (a | b)");
fold_same("a ^ b | 0");
fold_same("0 | (a ^ b)");
fold_same("a & b | 0");
fold_same("0 | (a & b)");
// `(a | 0) OP b` and `a OP (b | 0)`.
fold("((1 | a) | 0) << b", "(a | 1) << b");
fold("a << ((1 | b) | 0)", "a << (b | 1)");
fold("((1 | a) | 0) >> b", "(a | 1) >> b");
fold("a >> ((1 | b) | 0)", "a >> (b | 1)");
fold("((1 | a) | 0) >>> b", "(a | 1) >>> b");
fold("a >>> ((1 | b) | 0)", "a >>> (b | 1)");
fold("((1 | a) | 0) | b", "(a | 1) | b");
fold("a | ((1 | b) | 0)", "a | b | 1");
fold("((1 | a) | 0) ^ b", "(a | 1) ^ b");
fold("a ^ ((1 | b) | 0)", "a ^ (b | 1)");
fold("((1 | a) | 0) & b", "(a | 1) & b");
fold("a & ((1 | b) | 0)", "a & (b | 1)");
fold_same("(a | 0) << b");
fold_same("a << (b | 0)");
fold_same("(a | 0) >> b");
fold_same("a >> (b | 0)");
fold_same("(a | 0) >>> b");
fold_same("a >>> (b | 0)");
fold_same("(a | 0) | b");
fold("a | (b | 0)", "a | b | 0");
fold_same("(a | 0) ^ b");
fold_same("a ^ (b | 0)");
fold_same("(a | 0) & b");
fold_same("a & (b | 0)");
// `a OP 0` -> `a | 0`
fold("a << 0", "a | 0");
fold("a >> 0", "a | 0");
fold("a ^ 0", "a | 0");
// `a & 0xffffffff` -> `a | 0`.
fold("a & 0xffffffff", "a | 0");
fold("4294967295 & a", "0 | a");
fold("a & 4294967294.9999999", "a | 0");
fold_same("a & 4294967294.999999");
fold_same("a + 0");
fold_same("a | 1");
fold_same("a & 1");
fold_same("a << 1");
fold_same("a | (b + 0)");
fold_same("a | ~b");
fold_same("~a | 0");
fold_same("0 | ~a");
}
@@ -8,6 +8,7 @@ mod inline;
mod inline_single_use_variable;
mod manual_pure_functions;
mod merge_assignments_to_declarations;
mod minimize_binary_expression;
mod minimize_conditional_expression;
mod minimize_conditions;
mod minimize_exit_points;
+1 -1
View File
@@ -15,7 +15,7 @@ Original | minified | minified | gzip | gzip | Iterations | Fi
1.01 MB | 435.91 kB | 458.89 kB | 121.78 kB | 126.71 kB | 2 | bundle.min.js
1.25 MB | 634.24 kB | 646.76 kB | 158.44 kB | 163.73 kB | 2 | three.js
1.25 MB | 634.23 kB | 646.76 kB | 158.43 kB | 163.73 kB | 2 | three.js
2.14 MB | 707.61 kB | 724.14 kB | 160.07 kB | 181.07 kB | 2 | victory.js