Skip to content

Commit 555db48

Browse files
committed
fix #3645: constant folding for < > <= >=
1 parent 5650831 commit 555db48

4 files changed

Lines changed: 119 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@
2828
}
2929
```
3030

31+
* Constant folding for JavaScript inequality operators ([#3645](https://github.com/evanw/esbuild/issues/3645))
32+
33+
This release introduces constant folding for the `< > <= >=` operators. The minifier will now replace these operators with `true` or `false` when both sides are compile-time numeric or string constants:
34+
35+
```js
36+
// Original code
37+
console.log(1 < 2, '🍕' > '🧀')
38+
39+
// Old output (with --minify)
40+
console.log(1<2,"🍕">"🧀");
41+
42+
// New output (with --minify)
43+
console.log(!0,!1);
44+
```
45+
3146
* Fix cross-platform non-determinism with CSS color space transformations ([#3650](https://github.com/evanw/esbuild/issues/3650))
3247

3348
The Go compiler takes advantage of "fused multiply and add" (FMA) instructions on certain processors which do the operation `x*y + z` without intermediate rounding. This causes esbuild's CSS color space math to differ on different processors (currently `ppc64le` and `s390x`), which breaks esbuild's guarantee of deterministic output. To avoid this, esbuild's color space math now inserts a `float64()` cast around every single math operation. This tells the Go compiler not to use the FMA optimization.

internal/bundler_tests/snapshots/snapshots_dce.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,10 @@ console.log([
274274
3 /* a */ % 6 /* b */,
275275
3 /* a */ ** 6 /* b */
276276
], [
277-
3 /* a */ < 6 /* b */,
278-
3 /* a */ > 6 /* b */,
279-
3 /* a */ <= 6 /* b */,
280-
3 /* a */ >= 6 /* b */,
277+
!0,
278+
!1,
279+
!0,
280+
!1,
281281
3 /* a */ == 6 /* b */,
282282
3 /* a */ != 6 /* b */,
283283
3 /* a */ === 6 /* b */,
@@ -312,10 +312,10 @@ console.log([
312312
3 % 6,
313313
3 ** 6
314314
], [
315-
3 < 6,
316-
3 > 6,
317-
3 <= 6,
318-
3 >= 6,
315+
!0,
316+
!1,
317+
!0,
318+
!1,
319319
3 == 6,
320320
3 != 6,
321321
3 === 6,

internal/js_ast/js_ast_helpers.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,45 @@ func extractNumericValues(left Expr, right Expr) (float64, float64, bool) {
10861086
return 0, 0, false
10871087
}
10881088

1089+
func extractStringValue(data E) ([]uint16, bool) {
1090+
switch e := data.(type) {
1091+
case *EAnnotation:
1092+
return extractStringValue(e.Value.Data)
1093+
1094+
case *EInlinedEnum:
1095+
return extractStringValue(e.Value.Data)
1096+
1097+
case *EString:
1098+
return e.Value, true
1099+
}
1100+
1101+
return nil, false
1102+
}
1103+
1104+
func extractStringValues(left Expr, right Expr) ([]uint16, []uint16, bool) {
1105+
if a, ok := extractStringValue(left.Data); ok {
1106+
if b, ok := extractStringValue(right.Data); ok {
1107+
return a, b, true
1108+
}
1109+
}
1110+
return nil, nil, false
1111+
}
1112+
1113+
func stringCompareUCS2(a []uint16, b []uint16) int {
1114+
var n int
1115+
if len(a) < len(b) {
1116+
n = len(a)
1117+
} else {
1118+
n = len(b)
1119+
}
1120+
for i := 0; i < n; i++ {
1121+
if delta := int(a[i]) - int(b[i]); delta != 0 {
1122+
return delta
1123+
}
1124+
}
1125+
return len(a) - len(b)
1126+
}
1127+
10891128
func approximatePrintedIntCharCount(intValue float64) int {
10901129
count := 1 + (int)(math.Max(0, math.Floor(math.Log10(math.Abs(intValue)))))
10911130
if intValue < 0 {
@@ -1106,7 +1145,11 @@ func ShouldFoldBinaryArithmeticWhenMinifying(binary *EBinary) bool {
11061145
// are unlikely to result in larger output.
11071146
BinOpBitwiseAnd,
11081147
BinOpBitwiseOr,
1109-
BinOpBitwiseXor:
1148+
BinOpBitwiseXor,
1149+
BinOpLt,
1150+
BinOpGt,
1151+
BinOpLe,
1152+
BinOpGe:
11101153
return true
11111154

11121155
case BinOpAdd:
@@ -1221,6 +1264,38 @@ func FoldBinaryArithmetic(loc logger.Loc, e *EBinary) Expr {
12211264
if left, right, ok := extractNumericValues(e.Left, e.Right); ok {
12221265
return Expr{Loc: loc, Data: &ENumber{Value: float64(ToInt32(left) ^ ToInt32(right))}}
12231266
}
1267+
1268+
case BinOpLt:
1269+
if left, right, ok := extractNumericValues(e.Left, e.Right); ok {
1270+
return Expr{Loc: loc, Data: &EBoolean{Value: left < right}}
1271+
}
1272+
if left, right, ok := extractStringValues(e.Left, e.Right); ok {
1273+
return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) < 0}}
1274+
}
1275+
1276+
case BinOpGt:
1277+
if left, right, ok := extractNumericValues(e.Left, e.Right); ok {
1278+
return Expr{Loc: loc, Data: &EBoolean{Value: left > right}}
1279+
}
1280+
if left, right, ok := extractStringValues(e.Left, e.Right); ok {
1281+
return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) > 0}}
1282+
}
1283+
1284+
case BinOpLe:
1285+
if left, right, ok := extractNumericValues(e.Left, e.Right); ok {
1286+
return Expr{Loc: loc, Data: &EBoolean{Value: left <= right}}
1287+
}
1288+
if left, right, ok := extractStringValues(e.Left, e.Right); ok {
1289+
return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) <= 0}}
1290+
}
1291+
1292+
case BinOpGe:
1293+
if left, right, ok := extractNumericValues(e.Left, e.Right); ok {
1294+
return Expr{Loc: loc, Data: &EBoolean{Value: left >= right}}
1295+
}
1296+
if left, right, ok := extractStringValues(e.Left, e.Right); ok {
1297+
return Expr{Loc: loc, Data: &EBoolean{Value: stringCompareUCS2(left, right) >= 0}}
1298+
}
12241299
}
12251300

12261301
return Expr{}

internal/js_parser/js_parser_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4653,15 +4653,31 @@ func TestMangleBinaryConstantFolding(t *testing.T) {
46534653
expectPrintedNormalAndMangle(t, "x = -123 / 0", "x = -123 / 0;\n", "x = -Infinity;\n")
46544654
expectPrintedNormalAndMangle(t, "x = -123 / -0", "x = -123 / -0;\n", "x = Infinity;\n")
46554655

4656-
expectPrintedNormalAndMangle(t, "x = 3 < 6", "x = 3 < 6;\n", "x = 3 < 6;\n")
4657-
expectPrintedNormalAndMangle(t, "x = 3 > 6", "x = 3 > 6;\n", "x = 3 > 6;\n")
4658-
expectPrintedNormalAndMangle(t, "x = 3 <= 6", "x = 3 <= 6;\n", "x = 3 <= 6;\n")
4659-
expectPrintedNormalAndMangle(t, "x = 3 >= 6", "x = 3 >= 6;\n", "x = 3 >= 6;\n")
4656+
expectPrintedNormalAndMangle(t, "x = 3 < 6", "x = 3 < 6;\n", "x = true;\n")
4657+
expectPrintedNormalAndMangle(t, "x = 3 > 6", "x = 3 > 6;\n", "x = false;\n")
4658+
expectPrintedNormalAndMangle(t, "x = 3 <= 6", "x = 3 <= 6;\n", "x = true;\n")
4659+
expectPrintedNormalAndMangle(t, "x = 3 >= 6", "x = 3 >= 6;\n", "x = false;\n")
46604660
expectPrintedNormalAndMangle(t, "x = 3 == 6", "x = false;\n", "x = false;\n")
46614661
expectPrintedNormalAndMangle(t, "x = 3 != 6", "x = true;\n", "x = true;\n")
46624662
expectPrintedNormalAndMangle(t, "x = 3 === 6", "x = false;\n", "x = false;\n")
46634663
expectPrintedNormalAndMangle(t, "x = 3 !== 6", "x = true;\n", "x = true;\n")
46644664

4665+
expectPrintedNormalAndMangle(t, "x = 'a' < 'b'", "x = \"a\" < \"b\";\n", "x = true;\n")
4666+
expectPrintedNormalAndMangle(t, "x = 'a' > 'b'", "x = \"a\" > \"b\";\n", "x = false;\n")
4667+
expectPrintedNormalAndMangle(t, "x = 'a' <= 'b'", "x = \"a\" <= \"b\";\n", "x = true;\n")
4668+
expectPrintedNormalAndMangle(t, "x = 'a' >= 'b'", "x = \"a\" >= \"b\";\n", "x = false;\n")
4669+
4670+
expectPrintedNormalAndMangle(t, "x = 'ab' < 'abc'", "x = \"ab\" < \"abc\";\n", "x = true;\n")
4671+
expectPrintedNormalAndMangle(t, "x = 'ab' > 'abc'", "x = \"ab\" > \"abc\";\n", "x = false;\n")
4672+
expectPrintedNormalAndMangle(t, "x = 'ab' <= 'abc'", "x = \"ab\" <= \"abc\";\n", "x = true;\n")
4673+
expectPrintedNormalAndMangle(t, "x = 'ab' >= 'abc'", "x = \"ab\" >= \"abc\";\n", "x = false;\n")
4674+
4675+
// This checks for comparing by code point vs. by code unit
4676+
expectPrintedNormalAndMangle(t, "x = '𐙩' < 'ﬡ'", "x = \"𐙩\" < \"\";\n", "x = true;\n")
4677+
expectPrintedNormalAndMangle(t, "x = '𐙩' > 'ﬡ'", "x = \"𐙩\" > \"\";\n", "x = false;\n")
4678+
expectPrintedNormalAndMangle(t, "x = '𐙩' <= 'ﬡ'", "x = \"𐙩\" <= \"\";\n", "x = true;\n")
4679+
expectPrintedNormalAndMangle(t, "x = '𐙩' >= 'ﬡ'", "x = \"𐙩\" >= \"\";\n", "x = false;\n")
4680+
46654681
expectPrintedNormalAndMangle(t, "x = 3 in 6", "x = 3 in 6;\n", "x = 3 in 6;\n")
46664682
expectPrintedNormalAndMangle(t, "x = 3 instanceof 6", "x = 3 instanceof 6;\n", "x = 3 instanceof 6;\n")
46674683
expectPrintedNormalAndMangle(t, "x = (3, 6)", "x = (3, 6);\n", "x = 6;\n")

0 commit comments

Comments
 (0)