From 7c8acdba4f8d6f02d5a22e46aec000523f6959c3 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Thu, 25 Apr 2024 13:59:07 -0600 Subject: [PATCH 1/5] Remove extra pruneImport --- build/build.go | 1 - 1 file changed, 1 deletion(-) diff --git a/build/build.go b/build/build.go index def9cd313..62b360c61 100644 --- a/build/build.go +++ b/build/build.go @@ -174,7 +174,6 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke overrides := make(map[string]overrideInfo) for _, file := range overlayFiles { augmentOverlayFile(file, overrides) - pruneImports(file) } delete(overrides, "init") From 7db30394a96edd49b1f7281c2b105418934e717c Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 30 Apr 2024 10:04:34 -0600 Subject: [PATCH 2/5] Fixing internalization of null slice and array fields --- compiler/prelude/jsmapping.js | 6 ++++++ tests/js_test.go | 39 +++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/compiler/prelude/jsmapping.js b/compiler/prelude/jsmapping.js index b22454bc3..f5317d626 100644 --- a/compiler/prelude/jsmapping.js +++ b/compiler/prelude/jsmapping.js @@ -236,6 +236,9 @@ var $internalize = (v, t, recv, seen, makeWrapper) => { case $kindFloat64: return parseFloat(v); case $kindArray: + if (v === null || v === undefined) { + $throwRuntimeError("cannot internalize "+v+" as a "+t.string); + } if (v.length !== t.len) { $throwRuntimeError("got array with wrong size from JavaScript native"); } @@ -331,6 +334,9 @@ var $internalize = (v, t, recv, seen, makeWrapper) => { return $internalize(v, t.elem, makeWrapper); } case $kindSlice: + if (v == null) { + return t.zero(); + } return new t($mapArray(v, e => { return $internalize(e, t.elem, makeWrapper); })); case $kindString: v = String(v); diff --git a/tests/js_test.go b/tests/js_test.go index 2ce43865f..6f6eaa542 100644 --- a/tests/js_test.go +++ b/tests/js_test.go @@ -829,6 +829,45 @@ func TestExternalize(t *testing.T) { } } +func TestInternalizeSlice(t *testing.T) { + tests := []struct { + name string + init []int + want string + }{ + { + name: `nil slice`, + init: []int(nil), + want: `[]int(nil)`, + }, + { + name: `empty slice`, + init: []int{}, + want: `[]int{}`, + }, + { + name: `non-empty slice`, + init: []int{42, 53, 64}, + want: `[]int{42, 53, 64}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := struct { + *js.Object + V []int `js:"V"` // V is externalized + }{Object: js.Global.Get("Object").New()} + b.V = tt.init + + result := fmt.Sprintf(`%#v`, b.V) // internalize b.V + if result != tt.want { + t.Errorf(`Unexpected result %q != %q`, result, tt.want) + } + }) + } +} + func TestInternalizeExternalizeNull(t *testing.T) { type S struct { *js.Object From 4f6746f066f3f0102ba47408ef28293384aeeb92 Mon Sep 17 00:00:00 2001 From: Nevkontakte Date: Mon, 6 May 2024 18:56:26 +0100 Subject: [PATCH 3/5] Fix float64 to uint{8,16,32} conversion. Before this change, GopherJS compiler emitted `(f >> 0)` expression to convert a float64 `f` to any non-64-bit unsigned integer type. This is incorrect, because `>>` is a signed bitshift operator in JS, so the returned value remained signed. Moreover, it did not take into account to bit size of the target type. By removing the switch cause, we make the compiler fall through to the default clause where `fc.fixNumber()` actually does the right thing, taking the target into account. Fixes #733. --- compiler/expressions.go | 2 -- tests/numeric_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 2768e3d2a..11b2840ba 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1128,8 +1128,6 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type return fc.fixNumber(fc.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), t) } return fc.fixNumber(fc.formatExpr("%s.$low", fc.translateExpr(expr)), t) - case isFloat(basicExprType): - return fc.formatParenExpr("%e >> 0", expr) case types.Identical(exprType, types.Typ[types.UnsafePointer]): return fc.translateExpr(expr) default: diff --git a/tests/numeric_test.go b/tests/numeric_test.go index df06db584..4eaa678c0 100644 --- a/tests/numeric_test.go +++ b/tests/numeric_test.go @@ -5,6 +5,8 @@ import ( "runtime" "testing" "testing/quick" + + "github.com/gopherjs/gopherjs/js" ) // naiveMul64 performs 64-bit multiplication without using the multiplication @@ -93,3 +95,26 @@ func BenchmarkMul64(b *testing.B) { } }) } + +func TestIssue733(t *testing.T) { + if runtime.GOOS != "js" { + t.Skip("test uses GopherJS-specific features") + } + + t.Run("sign", func(t *testing.T) { + f := float64(-1) + i := uint32(f) + underlying := js.InternalObject(i).Float() // Get the raw JS number behind i. + if want := float64(4294967295); underlying != want { + t.Errorf("Got: uint32(float64(%v)) = %v. Want: %v.", f, underlying, want) + } + }) + t.Run("truncation", func(t *testing.T) { + f := float64(300) + i := uint8(f) + underlying := js.InternalObject(i).Float() // Get the raw JS number behind i. + if want := float64(44); underlying != want { + t.Errorf("Got: uint32(float64(%v)) = %v. Want: %v.", f, underlying, want) + } + }) +} From 88efaee035cfd2e7cd6e3b843bbfbe45c825d9db Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 22 May 2024 09:40:18 -0600 Subject: [PATCH 4/5] Changed uint to use --- compiler/expressions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/expressions.go b/compiler/expressions.go index 11b2840ba..c748df59a 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -387,7 +387,7 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression { switch basic.Kind() { case types.Int32, types.Int: return fc.formatParenExpr("$imul(%e, %e)", e.X, e.Y) - case types.Uint32, types.Uintptr: + case types.Uint32, types.Uint, types.Uintptr: return fc.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y) } return fc.fixNumber(fc.formatExpr("%e * %e", e.X, e.Y), basic) From e0788e72bfef7b0fcf469da0f6135d2dcd3eab35 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Wed, 22 May 2024 14:52:24 -0600 Subject: [PATCH 5/5] Added a test for checking uint mul as 32bits --- tests/numeric_test.go | 83 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/numeric_test.go b/tests/numeric_test.go index 4eaa678c0..484968f41 100644 --- a/tests/numeric_test.go +++ b/tests/numeric_test.go @@ -1,6 +1,8 @@ package tests import ( + "fmt" + "math/bits" "math/rand" "runtime" "testing" @@ -118,3 +120,84 @@ func TestIssue733(t *testing.T) { } }) } + +// Test_32BitEnvironment tests that GopherJS behaves correctly +// as a 32-bit environment for integers. To simulate a 32 bit environment +// we have to use `$imul` instead of `*` to get the correct result. +func Test_32BitEnvironment(t *testing.T) { + if bits.UintSize != 32 { + t.Skip(`test is only relevant for 32-bit environment`) + } + + tests := []struct { + x, y, exp uint64 + }{ + { + x: 65535, // x = 2^16 - 1 + y: 65535, // same as x + exp: 4294836225, // x² works since it doesn't overflow 32 bits. + }, + { + x: 134217729, // x = 2^27 + 1, x < 2^32 and x > sqrt(2^53), so x² overflows 53 bits. + y: 134217729, // same as x + exp: 268435457, // x² mod 2^32 = (2^27 + 1)² mod 2^32 = (2^54 + 2^28 + 1) mod 2^32 = 2^28 + 1 + // In pure JS, `x * x >>> 0`, would result in 268,435,456 because it lost the least significant bit + // prior to being truncated, where in a real 32 bit environment, it would be 268,435,457 since + // the rollover removed the most significant bit and doesn't affect the least significant bit. + }, + { + x: 4294967295, // x = 2^32 - 1 another case where x² overflows 53 bits causing a loss of precision. + y: 4294967295, // same as x + exp: 1, // x² mod 2^32 = (2^32 - 1)² mod 2^32 = (2^64 - 2^33 + 1) mod 2^32 = 1 + // In pure JS, `x * x >>> 0`, would result in 0 because it lost the least significant bits. + }, + { + x: 4294967295, // x = 2^32 - 1 + y: 3221225473, // y = 2^31 + 2^30 + 1 + exp: 1073741823, // 2^32 - 1. + // In pure JS, `x * y >>> 0`, would result in 1,073,741,824. + }, + { + x: 4294967295, // x = 2^32 - 1 + y: 134217729, // y = 2^27 + 1 + exp: 4160749567, // In pure JS, `x * y >>> 0`, would result in 4,160,749,568. + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf(`#%d/uint32`, i), func(t *testing.T) { + x, y, exp := uint32(test.x), uint32(test.y), uint32(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/uintptr`, i), func(t *testing.T) { + x, y, exp := uintptr(test.x), uintptr(test.y), uintptr(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/uint`, i), func(t *testing.T) { + x, y, exp := uint(test.x), uint(test.y), uint(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/int32`, i), func(t *testing.T) { + x, y, exp := int32(test.x), int32(test.y), int32(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + + t.Run(fmt.Sprintf(`#%d/int`, i), func(t *testing.T) { + x, y, exp := int(test.x), int(test.y), int(test.exp) + if got := x * y; got != exp { + t.Errorf("got: %d\nwant: %d.", got, exp) + } + }) + } +}