Skip to content

Commit

Permalink
fix #2383: cross-platform hex number consistency
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 11, 2022
1 parent 4a4fd6e commit f730c03
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

* Fix a cross-platform consistency bug ([#2383](https://github.com/evanw/esbuild/issues/2383))

Previously esbuild would minify `0xFFFF_FFFF_FFFF_FFFF` as `0xffffffffffffffff` (18 bytes) on arm64 chips and as `18446744073709552e3` (19 bytes) on x86_64 chips. The reason was that the number was converted to a 64-bit unsigned integer internally for printing as hexadecimal, the 64-bit floating-point number `0xFFFF_FFFF_FFFF_FFFF` is actually `0x1_0000_0000_0000_0180` (i.e. it's rounded up, not down), and converting `float64` to `uint64` is implementation-dependent in Go when the input is out of bounds. This was fixed by changing the upper limit for which esbuild uses hexadecimal numbers during minification to `0xFFFF_FFFF_FFFF_F800`, which is the next representable 64-bit floating-point number below `0x1_0000_0000_0000_0180`, and which fits in a `uint64`. As a result, esbuild will now consistently never minify `0xFFFF_FFFF_FFFF_FFFF` as `0xffffffffffffffff` anymore, which means the output should now be consistent across platforms.

## 0.14.49

* Keep inlined constants when direct `eval` is present ([#2361](https://github.com/evanw/esbuild/issues/2361))
Expand Down
11 changes: 9 additions & 2 deletions internal/js_printer/js_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2743,8 +2743,15 @@ func (p *printer) printNonNegativeFloat(absValue float64) {
}
}

// Numbers in this range can potentially be printed with one fewer byte as hex
if p.options.MinifyWhitespace && absValue >= 1_000_000_000_000 && absValue <= 0xFFFF_FFFF_FFFF_FFFF {
// Numbers in this range can potentially be printed with one fewer byte as
// hex. This compares against 0xFFFF_FFFF_FFFF_F800 instead of comparing
// against 0xFFFF_FFFF_FFFF_FFFF because 0xFFFF_FFFF_FFFF_FFFF when converted
// to float64 rounds up to 0x1_0000_0000_0000_0180, which can no longer fit
// into uint64. In Go, the result of converting float64 to uint64 outside of
// the uint64 range is implementation-dependent and is different on amd64 vs.
// arm64. The float64 value 0xFFFF_FFFF_FFFF_F800 is the biggest value that
// is below the float64 value 0x1_0000_0000_0000_0180, so we use that instead.
if p.options.MinifyWhitespace && absValue >= 1_000_000_000_000 && absValue <= 0xFFFF_FFFF_FFFF_F800 {
if asInt := uint64(absValue); absValue == float64(asInt) {
if hex := strconv.FormatUint(asInt, 16); 2+len(hex) < len(result) {
result = append(append(result[:0], '0', 'x'), hex...)
Expand Down
14 changes: 10 additions & 4 deletions internal/js_printer/js_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,18 @@ func TestNumber(t *testing.T) {
// Check the hex vs. decimal decision boundary when minifying
expectPrinted(t, "x = 999999999999", "x = 999999999999;\n")
expectPrinted(t, "x = 1000000000001", "x = 1000000000001;\n")
expectPrinted(t, "x = 0xFFFFFFFFFFFFF80", "x = 1152921504606846800;\n")
expectPrinted(t, "x = 0x1000000000000000", "x = 1152921504606847e3;\n")
expectPrinted(t, "x = 0x0FFF_FFFF_FFFF_FF80", "x = 1152921504606846800;\n")
expectPrinted(t, "x = 0x1000_0000_0000_0000", "x = 1152921504606847e3;\n")
expectPrinted(t, "x = 0xFFFF_FFFF_FFFF_F000", "x = 18446744073709548e3;\n")
expectPrinted(t, "x = 0xFFFF_FFFF_FFFF_F800", "x = 1844674407370955e4;\n")
expectPrinted(t, "x = 0xFFFF_FFFF_FFFF_FFFF", "x = 18446744073709552e3;\n")
expectPrintedMinify(t, "x = 999999999999", "x=999999999999;")
expectPrintedMinify(t, "x = 1000000000001", "x=0xe8d4a51001;")
expectPrintedMinify(t, "x = 0xFFFFFFFFFFFFF80", "x=0xfffffffffffff80;")
expectPrintedMinify(t, "x = 0x1000000000000000", "x=1152921504606847e3;")
expectPrintedMinify(t, "x = 0x0FFF_FFFF_FFFF_FF80", "x=0xfffffffffffff80;")
expectPrintedMinify(t, "x = 0x1000_0000_0000_0000", "x=1152921504606847e3;")
expectPrintedMinify(t, "x = 0xFFFF_FFFF_FFFF_F000", "x=0xfffffffffffff000;")
expectPrintedMinify(t, "x = 0xFFFF_FFFF_FFFF_F800", "x=1844674407370955e4;")
expectPrintedMinify(t, "x = 0xFFFF_FFFF_FFFF_FFFF", "x=18446744073709552e3;")
}

func TestArray(t *testing.T) {
Expand Down

0 comments on commit f730c03

Please sign in to comment.