mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(minifier): fold bitwise operation (#7908)
This PR implements constant evaluation for bitwise operations (`&`, `|`,
`^`).
I wanted to play around with the minifier a bit 🙂
This commit is contained in:
parent
dcaf674aa8
commit
075bd165a8
3 changed files with 208 additions and 18 deletions
|
|
@ -198,9 +198,16 @@ pub trait ConstantEvaluation<'a> {
|
|||
}
|
||||
|
||||
fn eval_binary_expression(&self, e: &BinaryExpression<'a>) -> Option<ConstantValue<'a>> {
|
||||
let left = &e.left;
|
||||
let right = &e.right;
|
||||
match e.operator {
|
||||
self.eval_binary_operation(e.operator, &e.left, &e.right)
|
||||
}
|
||||
|
||||
fn eval_binary_operation(
|
||||
&self,
|
||||
operator: BinaryOperator,
|
||||
left: &Expression<'a>,
|
||||
right: &Expression<'a>,
|
||||
) -> Option<ConstantValue<'a>> {
|
||||
match operator {
|
||||
BinaryOperator::Addition => {
|
||||
if left.may_have_side_effects() || right.may_have_side_effects() {
|
||||
return None;
|
||||
|
|
@ -230,7 +237,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
| BinaryOperator::Exponential => {
|
||||
let lval = self.eval_to_number(left)?;
|
||||
let rval = self.eval_to_number(right)?;
|
||||
let val = match e.operator {
|
||||
let val = match operator {
|
||||
BinaryOperator::Subtraction => lval - rval,
|
||||
BinaryOperator::Division => lval / rval,
|
||||
BinaryOperator::Remainder => {
|
||||
|
|
@ -264,7 +271,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
let right_val_int = right_val as u32;
|
||||
let bits = left_val.to_int_32();
|
||||
|
||||
let result_val: f64 = match e.operator {
|
||||
let result_val: f64 = match operator {
|
||||
BinaryOperator::ShiftLeft => f64::from(bits.wrapping_shl(right_val_int)),
|
||||
BinaryOperator::ShiftRight => f64::from(bits.wrapping_shr(right_val_int)),
|
||||
BinaryOperator::ShiftRightZeroFill => {
|
||||
|
|
@ -310,6 +317,34 @@ pub trait ConstantEvaluation<'a> {
|
|||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseOR | BinaryOperator::BitwiseXOR => {
|
||||
let left_num = self.get_side_free_number_value(left);
|
||||
let right_num = self.get_side_free_number_value(right);
|
||||
if let (Some(left_val), Some(right_val)) = (left_num, right_num) {
|
||||
let left_val_int = left_val.to_int_32();
|
||||
let right_val_int = right_val.to_int_32();
|
||||
|
||||
let result_val: f64 = match operator {
|
||||
BinaryOperator::BitwiseAnd => f64::from(left_val_int & right_val_int),
|
||||
BinaryOperator::BitwiseOR => f64::from(left_val_int | right_val_int),
|
||||
BinaryOperator::BitwiseXOR => f64::from(left_val_int ^ right_val_int),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
return Some(ConstantValue::Number(result_val));
|
||||
}
|
||||
let left_bitint = self.get_side_free_bigint_value(left);
|
||||
let right_bitint = self.get_side_free_bigint_value(right);
|
||||
if let (Some(left_val), Some(right_val)) = (left_bitint, right_bitint) {
|
||||
let result_val: BigInt = match operator {
|
||||
BinaryOperator::BitwiseAnd => left_val & right_val,
|
||||
BinaryOperator::BitwiseOR => left_val | right_val,
|
||||
BinaryOperator::BitwiseXOR => left_val ^ right_val,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
return Some(ConstantValue::BigInt(result_val));
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,19 +235,47 @@ impl<'a, 'b> PeepholeFoldConstants {
|
|||
ctx.eval_binary_expression(e).map(|v| ctx.value_to_expr(e.span, v))
|
||||
}
|
||||
BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseOR | BinaryOperator::BitwiseXOR => {
|
||||
// TODO:
|
||||
// self.try_fold_arithmetic_op(e.span, &e.left, &e.right, ctx)
|
||||
// if (result != subtree) {
|
||||
// return result;
|
||||
// }
|
||||
// return tryFoldLeftChildOp(subtree, left, right);
|
||||
None
|
||||
if let Some(v) = ctx.eval_binary_expression(e) {
|
||||
return Some(ctx.value_to_expr(e.span, v));
|
||||
}
|
||||
Self::try_fold_left_child_op(e, ctx)
|
||||
}
|
||||
op if op.is_equality() || op.is_compare() => Self::try_fold_comparison(e, ctx),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fold_left_child_op(
|
||||
e: &mut BinaryExpression<'a>,
|
||||
ctx: Ctx<'a, '_>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let op = e.operator;
|
||||
debug_assert!(matches!(
|
||||
op,
|
||||
BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseOR | BinaryOperator::BitwiseXOR
|
||||
));
|
||||
|
||||
let Expression::BinaryExpression(left) = &mut e.left else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let (v, expr_to_move);
|
||||
if let Some(result) = ctx.eval_binary_operation(op, &left.left, &e.right) {
|
||||
(v, expr_to_move) = (result, &mut left.right);
|
||||
} else if let Some(result) = ctx.eval_binary_operation(op, &left.right, &e.right) {
|
||||
(v, expr_to_move) = (result, &mut left.left);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(ctx.ast.expression_binary(
|
||||
e.span,
|
||||
ctx.ast.move_expression(expr_to_move),
|
||||
op,
|
||||
ctx.value_to_expr(Span::new(left.right.span().start, e.right.span().end), v),
|
||||
))
|
||||
}
|
||||
|
||||
fn try_fold_comparison(e: &BinaryExpression<'a>, ctx: Ctx<'a, 'b>) -> Option<Expression<'a>> {
|
||||
let left = &e.left;
|
||||
let right = &e.right;
|
||||
|
|
@ -1118,6 +1146,133 @@ mod test {
|
|||
test_same("void x()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_bitwise_op() {
|
||||
test("x = 1 & 1", "x = 1");
|
||||
test("x = 1 & 2", "x = 0");
|
||||
test("x = 3 & 1", "x = 1");
|
||||
test("x = 3 & 3", "x = 3");
|
||||
|
||||
test("x = 1 | 1", "x = 1");
|
||||
test("x = 1 | 2", "x = 3");
|
||||
test("x = 3 | 1", "x = 3");
|
||||
test("x = 3 | 3", "x = 3");
|
||||
|
||||
test("x = 1 ^ 1", "x = 0");
|
||||
test("x = 1 ^ 2", "x = 3");
|
||||
test("x = 3 ^ 1", "x = 2");
|
||||
test("x = 3 ^ 3", "x = 0");
|
||||
|
||||
test("x = -1 & 0", "x = 0");
|
||||
test("x = 0 & -1", "x = 0");
|
||||
test("x = 1 & 4", "x = 0");
|
||||
test("x = 2 & 3", "x = 2");
|
||||
|
||||
// make sure we fold only when we are supposed to -- not when doing so would
|
||||
// lose information or when it is performed on nonsensical arguments.
|
||||
test("x = 1 & 1.1", "x = 1");
|
||||
test("x = 1.1 & 1", "x = 1");
|
||||
test("x = 1 & 3000000000", "x = 0");
|
||||
test("x = 3000000000 & 1", "x = 0");
|
||||
|
||||
// Try some cases with | as well
|
||||
test("x = 1 | 4", "x = 5");
|
||||
test("x = 1 | 3", "x = 3");
|
||||
test("x = 1 | 1.1", "x = 1");
|
||||
// test_same("x = 1 | 3e9");
|
||||
|
||||
// these cases look strange because bitwise OR converts unsigned numbers to be signed
|
||||
test("x = 1 | 3000000001", "x = -1294967295");
|
||||
test("x = 4294967295 | 0", "x = -1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_bitwise_op2() {
|
||||
test("x = y & 1 & 1", "x = y & 1");
|
||||
test("x = y & 1 & 2", "x = y & 0");
|
||||
test("x = y & 3 & 1", "x = y & 1");
|
||||
test("x = 3 & y & 1", "x = y & 1");
|
||||
test("x = y & 3 & 3", "x = y & 3");
|
||||
test("x = 3 & y & 3", "x = y & 3");
|
||||
|
||||
test("x = y | 1 | 1", "x = y | 1");
|
||||
test("x = y | 1 | 2", "x = y | 3");
|
||||
test("x = y | 3 | 1", "x = y | 3");
|
||||
test("x = 3 | y | 1", "x = y | 3");
|
||||
test("x = y | 3 | 3", "x = y | 3");
|
||||
test("x = 3 | y | 3", "x = y | 3");
|
||||
|
||||
test("x = y ^ 1 ^ 1", "x = y ^ 0");
|
||||
test("x = y ^ 1 ^ 2", "x = y ^ 3");
|
||||
test("x = y ^ 3 ^ 1", "x = y ^ 2");
|
||||
test("x = 3 ^ y ^ 1", "x = y ^ 2");
|
||||
test("x = y ^ 3 ^ 3", "x = y ^ 0");
|
||||
test("x = 3 ^ y ^ 3", "x = y ^ 0");
|
||||
|
||||
test("x = Infinity | NaN", "x=0");
|
||||
test("x = 12 | NaN", "x=12");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_bitwise_op_with_big_int() {
|
||||
test("x = 1n & 1n", "x = 1n");
|
||||
test("x = 1n & 2n", "x = 0n");
|
||||
test("x = 3n & 1n", "x = 1n");
|
||||
test("x = 3n & 3n", "x = 3n");
|
||||
|
||||
test("x = 1n | 1n", "x = 1n");
|
||||
test("x = 1n | 2n", "x = 3n");
|
||||
test("x = 1n | 3n", "x = 3n");
|
||||
test("x = 3n | 1n", "x = 3n");
|
||||
test("x = 3n | 3n", "x = 3n");
|
||||
test("x = 1n | 4n", "x = 5n");
|
||||
|
||||
test("x = 1n ^ 1n", "x = 0n");
|
||||
test("x = 1n ^ 2n", "x = 3n");
|
||||
test("x = 3n ^ 1n", "x = 2n");
|
||||
test("x = 3n ^ 3n", "x = 0n");
|
||||
|
||||
test("x = -1n & 0n", "x = 0n");
|
||||
test("x = 0n & -1n", "x = 0n");
|
||||
test("x = 1n & 4n", "x = 0n");
|
||||
test("x = 2n & 3n", "x = 2n");
|
||||
|
||||
test("x = 1n & 3000000000n", "x = 0n");
|
||||
test("x = 3000000000n & 1n", "x = 0n");
|
||||
|
||||
// bitwise OR does not affect the sign of a bigint
|
||||
test("x = 1n | 3000000001n", "x = 3000000001n");
|
||||
test("x = 4294967295n | 0n", "x = 4294967295n");
|
||||
|
||||
test("x = y & 1n & 1n", "x = y & 1n");
|
||||
test("x = y & 1n & 2n", "x = y & 0n");
|
||||
test("x = y & 3n & 1n", "x = y & 1n");
|
||||
test("x = 3n & y & 1n", "x = y & 1n");
|
||||
test("x = y & 3n & 3n", "x = y & 3n");
|
||||
test("x = 3n & y & 3n", "x = y & 3n");
|
||||
|
||||
test("x = y | 1n | 1n", "x = y | 1n");
|
||||
test("x = y | 1n | 2n", "x = y | 3n");
|
||||
test("x = y | 3n | 1n", "x = y | 3n");
|
||||
test("x = 3n | y | 1n", "x = y | 3n");
|
||||
test("x = y | 3n | 3n", "x = y | 3n");
|
||||
test("x = 3n | y | 3n", "x = y | 3n");
|
||||
|
||||
test("x = y ^ 1n ^ 1n", "x = y ^ 0n");
|
||||
test("x = y ^ 1n ^ 2n", "x = y ^ 3n");
|
||||
test("x = y ^ 3n ^ 1n", "x = y ^ 2n");
|
||||
test("x = 3n ^ y ^ 1n", "x = y ^ 2n");
|
||||
test("x = y ^ 3n ^ 3n", "x = y ^ 0n");
|
||||
test("x = 3n ^ y ^ 3n", "x = y ^ 0n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_bitwise_op_additional() {
|
||||
test("x = null & 1", "x = 0");
|
||||
test("x = (2 ** 31 - 1) | 1", "x = 2147483647");
|
||||
test("x = (2 ** 31) | 1", "x = -2147483647");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fold_bit_shift() {
|
||||
test("x = 1 << 0", "x=1");
|
||||
|
|
|
|||
|
|
@ -5,23 +5,23 @@ Original | minified | minified | gzip | gzip | Fixture
|
|||
|
||||
173.90 kB | 61.61 kB | 59.82 kB | 19.55 kB | 19.33 kB | moment.js
|
||||
|
||||
287.63 kB | 92.61 kB | 90.07 kB | 32.27 kB | 31.95 kB | jquery.js
|
||||
287.63 kB | 92.60 kB | 90.07 kB | 32.26 kB | 31.95 kB | jquery.js
|
||||
|
||||
342.15 kB | 121.79 kB | 118.14 kB | 44.59 kB | 44.37 kB | vue.js
|
||||
|
||||
544.10 kB | 73.37 kB | 72.48 kB | 26.13 kB | 26.20 kB | lodash.js
|
||||
|
||||
555.77 kB | 276.22 kB | 270.13 kB | 91.15 kB | 90.80 kB | d3.js
|
||||
555.77 kB | 276.15 kB | 270.13 kB | 91.13 kB | 90.80 kB | d3.js
|
||||
|
||||
1.01 MB | 467.14 kB | 458.89 kB | 126.74 kB | 126.71 kB | bundle.min.js
|
||||
|
||||
1.25 MB | 662.69 kB | 646.76 kB | 164.02 kB | 163.73 kB | three.js
|
||||
1.25 MB | 662.53 kB | 646.76 kB | 163.97 kB | 163.73 kB | three.js
|
||||
|
||||
2.14 MB | 740.94 kB | 724.14 kB | 181.49 kB | 181.07 kB | victory.js
|
||||
2.14 MB | 740.87 kB | 724.14 kB | 181.46 kB | 181.07 kB | victory.js
|
||||
|
||||
3.20 MB | 1.02 MB | 1.01 MB | 332.09 kB | 331.56 kB | echarts.js
|
||||
3.20 MB | 1.02 MB | 1.01 MB | 332.10 kB | 331.56 kB | echarts.js
|
||||
|
||||
6.69 MB | 2.39 MB | 2.31 MB | 496.17 kB | 488.28 kB | antd.js
|
||||
|
||||
10.95 MB | 3.56 MB | 3.49 MB | 911.37 kB | 915.50 kB | typescript.js
|
||||
10.95 MB | 3.55 MB | 3.49 MB | 910.45 kB | 915.50 kB | typescript.js
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue