diff --git a/crates/oxc_minifier/src/compressor/fold.rs b/crates/oxc_minifier/src/compressor/fold.rs index 24142c544..4439aae1b 100644 --- a/crates/oxc_minifier/src/compressor/fold.rs +++ b/crates/oxc_minifier/src/compressor/fold.rs @@ -108,6 +108,14 @@ impl<'a> Compressor<'a> { &binary_expr.left, &binary_expr.right, ), + BinaryOperator::ShiftLeft + | BinaryOperator::ShiftRight + | BinaryOperator::ShiftRightZeroFill => self.try_fold_shift( + binary_expr.span, + binary_expr.operator, + &binary_expr.left, + &binary_expr.right, + ), _ => None, }, Expression::UnaryExpression(unary_expr) => match unary_expr.operator { @@ -513,4 +521,63 @@ impl<'a> Compressor<'a> { } None } + + /// ported from [closure-compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1114-L1162) + #[allow(clippy::cast_possible_truncation)] + fn try_fold_shift<'b>( + &mut self, + span: Span, + op: BinaryOperator, + left: &'b Expression<'a>, + right: &'b Expression<'a>, + ) -> Option> { + let left_num = get_side_free_number_value(left); + let right_num = get_side_free_number_value(right); + + if let Some(NumberValue::Number(left_val)) = left_num && + let Some(NumberValue::Number(right_val)) = right_num { + if left_val.fract() != 0.0 || right_val.fract() != 0.0 { + return None; + } + + // only the lower 5 bits are used when shifting, so don't do anything + // if the shift amount is outside [0,32) + if !(0.0..32.0).contains(&right_val) { + return None; + } + + let right_val_int = right_val as i32; + let bits = NumberLiteral::ecmascript_to_int32(left_val); + + let result_val: f64 = match op { + BinaryOperator::ShiftLeft => { + f64::from(bits << right_val_int) + } + BinaryOperator::ShiftRight => { + f64::from(bits >> right_val_int) + } + BinaryOperator::ShiftRightZeroFill => { + // JavaScript always treats the result of >>> as unsigned. + // We must force Rust to do the same here. + #[allow(clippy::cast_sign_loss)] + let res = bits as u32 >> right_val_int as u32; + f64::from(res) + } + _ => unreachable!("Unknown binary operator {:?}", op) + }; + + let value_raw = self.hir.new_str(result_val.to_string().as_str()); + + let number_literal = self.hir.number_literal( + span, + result_val, + value_raw, + NumberBase::Decimal, + ); + + return Some(self.hir.literal_number_expression(number_literal)); + } + + None + } } diff --git a/crates/oxc_minifier/tests/closure/fold_constants.rs b/crates/oxc_minifier/tests/closure/fold_constants.rs index 03e6a160d..f7834d787 100644 --- a/crates/oxc_minifier/tests/closure/fold_constants.rs +++ b/crates/oxc_minifier/tests/closure/fold_constants.rs @@ -234,3 +234,38 @@ fn test_fold_void() { test("void x", "void 0"); test_same("void x()"); } + +#[test] +fn test_fold_bit_shift() { + test("x = 1 << 0", "x=1"); + test("x = -1 << 0", "x=-1"); + test("x = 1 << 1", "x=2"); + test("x = 3 << 1", "x=6"); + test("x = 1 << 8", "x=256"); + + test("x = 1 >> 0", "x=1"); + test("x = -1 >> 0", "x=-1"); + test("x = 1 >> 1", "x=0"); + test("x = 2 >> 1", "x=1"); + test("x = 5 >> 1", "x=2"); + test("x = 127 >> 3", "x=15"); + test("x = 3 >> 1", "x=1"); + test("x = 3 >> 2", "x=0"); + test("x = 10 >> 1", "x=5"); + test("x = 10 >> 2", "x=2"); + test("x = 10 >> 5", "x=0"); + + test("x = 10 >>> 1", "x=5"); + test("x = 10 >>> 2", "x=2"); + test("x = 10 >>> 5", "x=0"); + test("x = -1 >>> 1", "x=2147483647"); // 0x7fffffff + test("x = -1 >>> 0", "x=4294967295"); // 0xffffffff + test("x = -2 >>> 0", "x=4294967294"); // 0xfffffffe + test("x = 0x90000000 >>> 28", "x=9"); + + test("x = 0xffffffff << 0", "x=-1"); + test("x = 0xffffffff << 4", "x=-16"); + test("1 << 32", "1<<32"); + test("1 << -1", "1<<-1"); + test("1 >> 32", "1>>32"); +} diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index b55ab0865..4846daeaa 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -11,15 +11,15 @@ Original -> Minified -> Gzip Brotli 555.77 kB -> 274.89 kB -> 91.40 kB 76.86 kB d3.js -977.19 kB -> 456.44 kB -> 123.71 kB 107.10 kB bundle.min.js +977.19 kB -> 456.43 kB -> 123.71 kB 107.19 kB bundle.min.js 1.25 MB -> 677.74 kB -> 166.58 kB 134.57 kB three.js 2.14 MB -> 743.86 kB -> 181.82 kB 138.89 kB victory.js -3.20 MB -> 1.03 MB -> 332.93 kB 268.96 kB echarts.js +3.20 MB -> 1.03 MB -> 332.93 kB 269.04 kB echarts.js 6.69 MB -> 2.40 MB -> 498.41 kB 390.35 kB antd.js -10.82 MB -> 3.53 MB -> 903.10 kB 692.91 kB typescript.js +10.82 MB -> 3.53 MB -> 903.09 kB 693.53 kB typescript.js