Skip to content

Commit

Permalink
minify: fold more shift operations when shorter
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed May 12, 2023
1 parent 7d11ef1 commit d686756
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 19 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,22 @@
# Changelog

## Unreleased

* Improved minification of binary shift operators

With this release, esbuild's minifier will now evaluate the `<<` and `>>>` operators if the resulting code would be shorter:

```js
// Original code
console.log(10 << 10, 10 << 20, -123 >>> 5, -123 >>> 10);

// Old output (with --minify)
console.log(10<<10,10<<20,-123>>>5,-123>>>10);

// New output (with --minify)
console.log(10240,10<<20,-123>>>5,4194303);
```

## 0.17.18

* Fix non-default JSON import error with `export {} from` ([#3070](https://github.com/evanw/esbuild/issues/3070))
Expand Down
8 changes: 4 additions & 4 deletions internal/bundler_tests/snapshots/snapshots_dce.txt
Expand Up @@ -276,9 +276,9 @@ console.log([
3 /* a */ === 6 /* b */,
3 /* a */ !== 6 /* b */
], [
6 /* b */ << 1,
12,
3,
6 /* b */ >>> 1
3
], [
2,
7,
Expand Down Expand Up @@ -314,9 +314,9 @@ console.log([
3 === 6,
3 !== 6
], [
6 << 1,
12,
3,
6 >>> 1
3
], [
2,
7,
Expand Down
44 changes: 33 additions & 11 deletions internal/js_ast/js_ast_helpers.go
Expand Up @@ -953,26 +953,48 @@ func extractNumericValues(left Expr, right Expr) (float64, float64, bool) {
return 0, 0, false
}

func ShouldFoldBinaryArithmeticWhenMinifying(op OpCode) bool {
switch op {
func approximatePrintedIntCharCount(intValue float64) int {
count := 1 + (int)(math.Max(0, math.Floor(math.Log10(math.Abs(intValue)))))
if intValue < 0 {
count++
}
return count
}

func ShouldFoldBinaryArithmeticWhenMinifying(binary *EBinary) bool {
switch binary.Op {
case
// Minification folds right signed shift operations since they are
// Minification always folds right signed shift operations since they are
// unlikely to result in larger output. Note: ">>>" could result in
// bigger output such as "-1 >>> 0" becoming "4294967295".
BinOpShr,

// Minification folds bitwise operations since they are unlikely to
// result in larger output.
// Minification always folds the following bitwise operations since they
// are unlikely to result in larger output.
BinOpBitwiseAnd,

// Minification folds bitwise operations since they are unlikely to
// result in larger output.
BinOpBitwiseOr,

// Minification folds bitwise operations since they are unlikely to
// result in larger output.
BinOpBitwiseXor:
return true

case BinOpShl:
// "1 << 3" => "8"
// "1 << 24" => "1 << 24" (since "1<<24" is shorter than "16777216")
if left, right, ok := extractNumericValues(binary.Left, binary.Right); ok {
leftLen := approximatePrintedIntCharCount(left)
rightLen := approximatePrintedIntCharCount(right)
resultLen := approximatePrintedIntCharCount(float64(ToInt32(left) << (ToUint32(right) & 31)))
return resultLen <= leftLen+2+rightLen
}

case BinOpUShr:
// "10 >>> 1" => "5"
// "-1 >>> 0" => "-1 >>> 0" (since "-1>>>0" is shorter than "4294967295")
if left, right, ok := extractNumericValues(binary.Left, binary.Right); ok {
leftLen := approximatePrintedIntCharCount(left)
rightLen := approximatePrintedIntCharCount(right)
resultLen := approximatePrintedIntCharCount(float64(ToUint32(left) >> (ToUint32(right) & 31)))
return resultLen <= leftLen+3+rightLen
}
}
return false
}
Expand Down
2 changes: 1 addition & 1 deletion internal/js_parser/js_parser.go
Expand Up @@ -14332,7 +14332,7 @@ func (v *binaryExprVisitor) visitRightAndFinish(p *parser) js_ast.Expr {
}
}

if p.shouldFoldTypeScriptConstantExpressions || (p.options.minifySyntax && js_ast.ShouldFoldBinaryArithmeticWhenMinifying(e.Op)) {
if p.shouldFoldTypeScriptConstantExpressions || (p.options.minifySyntax && js_ast.ShouldFoldBinaryArithmeticWhenMinifying(e)) {
if result := js_ast.FoldBinaryArithmetic(v.loc, e); result.Data != nil {
return result
}
Expand Down
13 changes: 11 additions & 2 deletions internal/js_parser/js_parser_test.go
Expand Up @@ -4225,9 +4225,18 @@ func TestMangleBinaryConstantFolding(t *testing.T) {
expectPrintedNormalAndMangle(t, "x = 3 instanceof 6", "x = 3 instanceof 6;\n", "x = 3 instanceof 6;\n")
expectPrintedNormalAndMangle(t, "x = (3, 6)", "x = (3, 6);\n", "x = 6;\n")

expectPrintedNormalAndMangle(t, "x = 10 << 1", "x = 10 << 1;\n", "x = 10 << 1;\n")
expectPrintedNormalAndMangle(t, "x = 10 << 0", "x = 10 << 0;\n", "x = 10;\n")
expectPrintedNormalAndMangle(t, "x = 10 << 1", "x = 10 << 1;\n", "x = 20;\n")
expectPrintedNormalAndMangle(t, "x = 10 << 16", "x = 10 << 16;\n", "x = 655360;\n")
expectPrintedNormalAndMangle(t, "x = 10 << 17", "x = 10 << 17;\n", "x = 10 << 17;\n")
expectPrintedNormalAndMangle(t, "x = 10 >> 0", "x = 10 >> 0;\n", "x = 10;\n")
expectPrintedNormalAndMangle(t, "x = 10 >> 1", "x = 10 >> 1;\n", "x = 5;\n")
expectPrintedNormalAndMangle(t, "x = 10 >>> 1", "x = 10 >>> 1;\n", "x = 10 >>> 1;\n")
expectPrintedNormalAndMangle(t, "x = 10 >>> 0", "x = 10 >>> 0;\n", "x = 10;\n")
expectPrintedNormalAndMangle(t, "x = 10 >>> 1", "x = 10 >>> 1;\n", "x = 5;\n")
expectPrintedNormalAndMangle(t, "x = -10 >>> 1", "x = -10 >>> 1;\n", "x = -10 >>> 1;\n")
expectPrintedNormalAndMangle(t, "x = -1 >>> 0", "x = -1 >>> 0;\n", "x = -1 >>> 0;\n")
expectPrintedNormalAndMangle(t, "x = -123 >>> 5", "x = -123 >>> 5;\n", "x = -123 >>> 5;\n")
expectPrintedNormalAndMangle(t, "x = -123 >>> 6", "x = -123 >>> 6;\n", "x = 67108862;\n")
expectPrintedNormalAndMangle(t, "x = 3 & 6", "x = 3 & 6;\n", "x = 2;\n")
expectPrintedNormalAndMangle(t, "x = 3 | 6", "x = 3 | 6;\n", "x = 7;\n")
expectPrintedNormalAndMangle(t, "x = 3 ^ 6", "x = 3 ^ 6;\n", "x = 5;\n")
Expand Down
2 changes: 1 addition & 1 deletion internal/js_printer/js_printer.go
Expand Up @@ -1649,7 +1649,7 @@ func (p *printer) lateConstantFoldUnaryOrBinaryExpr(expr js_ast.Expr) js_ast.Exp
binary := &js_ast.EBinary{Op: e.Op, Left: left, Right: right}

// Only fold certain operations (just like the parser)
if js_ast.ShouldFoldBinaryArithmeticWhenMinifying(e.Op) {
if js_ast.ShouldFoldBinaryArithmeticWhenMinifying(binary) {
if result := js_ast.FoldBinaryArithmetic(expr.Loc, binary); result.Data != nil {
return result
}
Expand Down

0 comments on commit d686756

Please sign in to comment.