diff --git a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs index 6bb1c41a4..92cb7de23 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/mod.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/mod.rs @@ -198,9 +198,16 @@ pub trait ConstantEvaluation<'a> { } fn eval_binary_expression(&self, e: &BinaryExpression<'a>) -> Option> { - 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> { + 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, } } diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 06fb924a8..4adce50a4 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -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> { + 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> { 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"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index 04d64da6b..fdc3b083f 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -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