Skip to content

Go 1.17 support #1043

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 50 commits into from
Sep 18, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
a69a95d
Bump Upstream Go version to 1.17rc1
flimzy Jul 22, 2021
5e8e181
Update third-party dependencies
flimzy Jul 29, 2021
c596904
Apply 'go fmt -w -s .' to conform to 1.17 standards
flimzy Jul 22, 2021
7517cba
Set runtime.Version() output as a constant for now
flimzy Jul 22, 2021
21b4595
math/rand: Rename test package from `rand` to `rand_test` to match up…
flimzy Jul 29, 2021
c8a1588
reflect: New implementation of `New`
flimzy Jul 29, 2021
1b42d68
ed25519: Disable some tests
flimzy Jul 29, 2021
845d590
Disable tests for broken packages:
flimzy Jul 22, 2021
b4beee2
Add (failing) test for slice-to-array-pointer conversion added in Go …
flimzy Jul 29, 2021
b153701
Copy 1.16's version of *os.File.WriteString, which doesn't use unsafe
flimzy Aug 5, 2021
5a893b7
Fix a few doc typos, and tighten up some code a bit
flimzy Aug 5, 2021
0c5d57d
Hack around go/build.defaultToolTags
flimzy Aug 5, 2021
9680b1b
Disable some fixedbug tests that don't work in GopherJS
flimzy Aug 5, 2021
02663f3
Support go1.17 slice to array pointer conversion.
nevkontakte Aug 8, 2021
bc3fd14
Implement reflect support for slice to array pointer conversion.
nevkontakte Aug 14, 2021
cc76e63
Preserve nil slices when converting between different slice types.
nevkontakte Aug 14, 2021
d58f2e3
Fix reflect.TestMethodPkgPath test.
nevkontakte Sep 6, 2021
a055c1d
reflect: ArrayOf() panics if array length is negative.
nevkontakte Sep 6, 2021
5f41a3b
Use desiredType instead of underlying when converting slice to array.
nevkontakte Sep 6, 2021
afc51e1
Update VFS.
nevkontakte Sep 6, 2021
5be4748
Merge pull request #1055 from nevkontakte/wip-go1.17
nevkontakte Sep 7, 2021
839156b
Rewrite index operator on array-pointer into an explicit dereference.
nevkontakte Sep 11, 2021
a31dbb8
Provide correct constructor for names array-pointer types.
nevkontakte Sep 11, 2021
d35909a
reflect: Fix conversion between different array pointer types.
nevkontakte Sep 12, 2021
cb1f7e8
reflect: Fix slice length check in Value.CanConvert.
nevkontakte Sep 12, 2021
54dda39
Update VFS and minified prelude.
nevkontakte Sep 12, 2021
6a42cad
Merge pull request #1057 from nevkontakte/wip-go1.17
nevkontakte Sep 13, 2021
4dda1bf
sync/atomic: Implement new Swap and CompareAndSwap methods of Value.
nevkontakte Sep 14, 2021
04eeecb
Update VFS with sync/atomic.
nevkontakte Sep 14, 2021
d80c496
Merge pull request #1058 from nevkontakte/wip-go1.17
nevkontakte Sep 15, 2021
80278a6
Get `hash/maphash` to work again
flimzy Sep 17, 2021
f97fb25
Merge pull request #1059 from gopherjs/hashmap
flimzy Sep 17, 2021
1ead41d
Ignore tests for internal/abi package.
nevkontakte Sep 15, 2021
6d23e7f
Fix internal/poll package for Go 1.17.
nevkontakte Sep 17, 2021
e835da2
Update VFS for `internal/poll`.
nevkontakte Sep 17, 2021
8b97c5a
Merge pull request #1061 from nevkontakte/wip-go1.17
nevkontakte Sep 17, 2021
54c7e77
Fix PkgPath of the unsafe.Pointer type.
nevkontakte Sep 17, 2021
ffaebd7
Update minified prelude.
nevkontakte Sep 17, 2021
d60ba2c
Fix compiler panic when assigning to a named pointer type.
nevkontakte Sep 17, 2021
a0dbb0e
Mark `fixedbugs/issue23017.go` as a known failure.
nevkontakte Sep 17, 2021
f7183f9
Merge pull request #1064 from nevkontakte/wip-go1.17
nevkontakte Sep 18, 2021
1c5f51f
runtime: Version() returns Go version provided by the compiler.
nevkontakte Sep 18, 2021
866eb02
Update VFS for `runtime`.
nevkontakte Sep 18, 2021
5890279
Update Go version to test against to 1.17.1.
nevkontakte Sep 18, 2021
41f8453
Merge pull request #1065 from nevkontakte/wip-go1.17
nevkontakte Sep 18, 2021
c05f513
Reduce the number of quickcheck iterations for edwards25519.
nevkontakte Sep 18, 2021
2550e90
Update VFS.
nevkontakte Sep 18, 2021
3873393
Merge pull request #1066 from nevkontakte/wip-go1.17
nevkontakte Sep 18, 2021
267d5db
Update README for Go 1.17.
nevkontakte Sep 18, 2021
7fa979d
Update build constraints on the compiler to require Go 1.17.
nevkontakte Sep 18, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Support go1.17 slice to array pointer conversion.
Conversion is fully supported for slices of numeric types, which is
probably the most common use case for this feature judging by the
discussion in the proposal.

However, it is only partially supported for slices of complex types
(e.g. strings), since they are backed by the JavaScript's built-in Array
type, which lacks ability to share backing memory among subarrays.
Conversion works in cases when the slice is converted into array that
exactly matches the slice's underlying array, but panics if we are
trying to convert a subslice. I feel like an explicit failure is better
than a chance of introducing subtle bugs.

It also seems that GopherJS internally doesn't really distinguish
between array types and pointer to array types, which makes the whole
implementation somewhat messy when it comes to nil vs non-nil pointers
to arrays.

Last but not least, I've moved pointer cache for numeric arrays into the
backing ArrayBuffer, which ensures that pointers are comparable between
different subarrays.
  • Loading branch information
nevkontakte committed Aug 25, 2021
commit 02663f34ada9b528baf23f75a174fb56a402223b
16 changes: 9 additions & 7 deletions compiler/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,6 @@ func (fc *funcContext) translateExpr(expr ast.Expr) *expression {
return fc.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, fc.typeName(t))
case *types.Interface:
return fc.formatExpr("$interfaceIsEqual(%s, %s)", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t))
case *types.Pointer:
if _, ok := u.Elem().Underlying().(*types.Array); ok {
return fc.formatExpr("$equal(%s, %s, %s)", fc.translateImplicitConversion(e.X, t), fc.translateImplicitConversion(e.Y, t), fc.typeName(u.Elem()))
}
case *types.Basic:
if isBoolean(u) {
if b, ok := analysis.BoolValue(e.X, fc.pkgCtx.Info.Info); ok && b {
Expand Down Expand Up @@ -1031,7 +1027,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type
case t.Kind() == types.UnsafePointer:
if unary, isUnary := expr.(*ast.UnaryExpr); isUnary && unary.Op == token.AND {
if indexExpr, isIndexExpr := unary.X.(*ast.IndexExpr); isIndexExpr {
return fc.formatExpr("$sliceToArray(%s)", fc.translateConversionToSlice(indexExpr.X, types.NewSlice(types.Typ[types.Uint8])))
return fc.formatExpr("$sliceToNativeArray(%s)", fc.translateConversionToSlice(indexExpr.X, types.NewSlice(types.Typ[types.Uint8])))
}
if ident, isIdent := unary.X.(*ast.Ident); isIdent && ident.Name == "_zero" {
return fc.formatExpr("new Uint8Array(0)")
Expand Down Expand Up @@ -1075,8 +1071,14 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type
break
}

switch u := t.Elem().Underlying().(type) {
switch ptrElType := t.Elem().Underlying().(type) {
case *types.Array: // (*[N]T)(expr) — converting expr to a pointer to an array.
if _, ok := exprType.Underlying().(*types.Slice); ok {
// GopherJS interprets pointer to an array as the array object itself
// due to its reference semantics, so the bellow coversion is correct.
return fc.formatExpr("$sliceToGoArray(%e, %s)", expr, fc.typeName(t))
}
// TODO(nevkontakte): Is this just for aliased types (e.g. `type a [4]byte`)?
return fc.translateExpr(expr)
case *types.Struct: // (*StructT)(expr) — converting expr to a pointer to a struct.
if fc.pkgCtx.Pkg.Path() == "syscall" && types.Identical(exprType, types.Typ[types.UnsafePointer]) {
Expand All @@ -1086,7 +1088,7 @@ func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type
// indeed pointing at a byte array.
array := fc.newVariable("_array")
target := fc.newVariable("_struct")
return fc.formatExpr("(%s = %e, %s = %e, %s, %s)", array, expr, target, fc.zeroValue(t.Elem()), fc.loadStruct(array, target, u), target)
return fc.formatExpr("(%s = %e, %s = %e, %s, %s)", array, expr, target, fc.zeroValue(t.Elem()), fc.loadStruct(array, target, ptrElType), target)
}
// Convert between structs of different types but identical layouts,
// for example:
Expand Down
4 changes: 2 additions & 2 deletions compiler/prelude/jsmapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,9 @@ var $externalize = function(v, t) {
return $externalize(v.$get(), t.elem);
case $kindSlice:
if ($needsExternalization(t.elem)) {
return $mapArray($sliceToArray(v), function(e) { return $externalize(e, t.elem); });
return $mapArray($sliceToNativeArray(v), function(e) { return $externalize(e, t.elem); });
}
return $sliceToArray(v);
return $sliceToNativeArray(v);
case $kindString:
if ($isASCII(v)) {
return v;
Expand Down
30 changes: 29 additions & 1 deletion compiler/prelude/prelude.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,41 @@ var $substring = function(str, low, high) {
return str.substring(low, high);
};

var $sliceToArray = function(slice) {
// Convert Go slice to an equivalent JS array type.
var $sliceToNativeArray = function(slice) {
if (slice.$array.constructor !== Array) {
return slice.$array.subarray(slice.$offset, slice.$offset + slice.$length);
}
return slice.$array.slice(slice.$offset, slice.$offset + slice.$length);
};

// Convert Go slice to a pointer to an underlying Go array.
var $sliceToGoArray = function(slice, arrayPtrType) {
var arrayType = arrayPtrType.elem;
if (arrayType !== undefined && slice.$length < arrayType.len) {
$throwRuntimeError("cannot convert slice with length " + slice.$length + " to pointer to array with length " + arrayType.len);
}
if (slice == slice.constructor.nil) {
return arrayPtrType.nil; // Nil slice converts to nil array pointer.
}
if (slice.$array.constructor !== Array) {
return slice.$array.subarray(slice.$offset, slice.$offset + slice.$length);
}
if (slice.$offset == 0 && slice.$length == slice.$capacity && slice.$length == arrayType.len) {
return slice.$array;
}
if (arrayType.len == 0) {
return new arrayType([]);
}

// Array.slice (unlike TypedArray.subarray) returns a copy of an array range,
// which is not sharing memory with the original one, which violates the spec
// for slice to array conversion. This is incompatible with the Go spec, in
// particular that the assignments to the array elements would be visible in
// the slice. Prefer to fail explicitly instead of creating subtle bugs.
$throwRuntimeError("gopherjs: non-numeric slice to underlying array conversion is not supported for subslices");
};

var $decodeRune = function(str, pos) {
var c0 = str.charCodeAt(pos);

Expand Down
2 changes: 1 addition & 1 deletion compiler/prelude/prelude_min.go

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions compiler/prelude/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,8 +637,17 @@ var $newDataPointer = function(data, constructor) {
};

var $indexPtr = function(array, index, constructor) {
array.$ptr = array.$ptr || {};
return array.$ptr[index] || (array.$ptr[index] = new constructor(function() { return array[index]; }, function(v) { array[index] = v; }));
if (array.buffer) {
// Pointers to the same underlying ArrayBuffer share cache.
var cache = array.buffer.$ptr = array.buffer.$ptr || {};
// Pointers of different primitive types are non-comparable and stored in different caches.
var typeCache = cache[array.name] = cache[array.name] || {};
var cacheIdx = array.BYTES_PER_ELEMENT * index + array.byteOffset;
return typeCache[cacheIdx] || (typeCache[cacheIdx] = new constructor(function() { return array[index]; }, function(v) { array[index] = v; }));
} else {
array.$ptr = array.$ptr || {};
return array.$ptr[index] || (array.$ptr[index] = new constructor(function() { return array[index]; }, function(v) { array[index] = v; }));
}
};

var $sliceType = function(elem) {
Expand Down
205 changes: 156 additions & 49 deletions tests/slice_to_array_ptr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,54 +2,161 @@ package tests

import "testing"

// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
func TestSliceToArrayPointerConversion(t *testing.T) {
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
s := make([]byte, 2, 4)
s0 := (*[0]byte)(s)
if s0 == nil {
t.Error("s0 should not be nil")
}
s2 := (*[2]byte)(s)
if &s2[0] != &s[0] {
t.Error("&s2[0] should match &s[0]")
}
r := func() (r interface{}) {
defer func() {
r = recover()
}()
s4 := (*[4]byte)(s)
_ = s4
return nil
}()
if r == nil {
t.Error("out-of-bounds conversion of s should panic")
}

(*s2)[0] = 'x'
if s[0] != 'x' {
t.Errorf("s[0] should be changed")
}

var q []string
q0 := (*[0]string)(q)
if q0 != nil {
t.Error("t0 should be nil")
}
r = func() (r interface{}) {
defer func() {
r = recover()
}()
q1 := (*[1]string)(q)
_ = q1
return nil
}
if r == nil {
t.Error("out-of-bounds conversion of q should panic")
}

u := make([]byte, 0)
u0 := (*[0]byte)(u)
if u0 == nil {
t.Error("u0 should not be nil")
}
// GopherJS uses TypedArray for numeric types and Array for everything else
// since those are substantially different types, the tests are repeated
// for both.
t.Run("Numeric", func(t *testing.T) {
s := make([]byte, 2, 4)
t.Run("NotNil", func(t *testing.T) {
s0 := (*[0]byte)(s)
if s0 == nil {
t.Error("s0 should not be nil")
}
})

t.Run("ElementPointerEquality", func(t *testing.T) {
s2 := (*[2]byte)(s)
if &s2[0] != &s[0] {
t.Error("&s2[0] should match &s[0]")
}
s3 := (*[1]byte)(s[1:])
if &s3[0] != &s[1] {
t.Error("&s3[0] should match &s[1]")
}
})

t.Run("SliceToLargerArray", func(t *testing.T) {
r := func() (r interface{}) {
defer func() { r = recover() }()
s4 := (*[4]byte)(s)
_ = s4
return nil
}()
if r == nil {
t.Error("out-of-bounds conversion of s should panic")
}
})

t.Run("SharedMemory", func(t *testing.T) {
s2 := (*[2]byte)(s)
(*s2)[0] = 'x'
if s[0] != 'x' {
t.Errorf("s[0] should be changed")
}

s3 := (*[1]byte)(s[1:])
(*s3)[0] = 'y'
if s[1] != 'y' {
t.Errorf("s[1] should be changed")
}
})

var q []byte
t.Run("NilSlice", func(t *testing.T) {
q0 := (*[0]byte)(q)
if q0 != nil {
t.Error("q0 should be nil")
}
})

t.Run("NilSliceToLargerArray", func(t *testing.T) {
r := func() (r interface{}) {
defer func() { r = recover() }()
q1 := (*[1]byte)(q)
_ = q1
return nil
}
if r == nil {
t.Error("out-of-bounds conversion of q should panic")
}
})

t.Run("ZeroLenSlice", func(t *testing.T) {
u := make([]byte, 0)
u0 := (*[0]byte)(u)
if u0 == nil {
t.Error("u0 should not be nil")
}
})
})

t.Run("String", func(t *testing.T) {
s := make([]string, 2, 2)
t.Run("NotNil", func(t *testing.T) {
s0 := (*[0]string)(s)
if s0 == nil {
t.Error("s0 should not be nil")
}
})

t.Run("ElementPointerEquality", func(t *testing.T) {
s2 := (*[2]string)(s)
if &s2[0] != &s[0] {
t.Error("&s2[0] should match &s[0]")
}

t.Skip("non-numeric slice to underlying array conversion is not supported for subslices")
s3 := (*[1]string)(s[1:])
if &s3[0] != &s[1] {
t.Error("&s3[0] should match &s[1]")
}
})

t.Run("SliceToLargerArray", func(t *testing.T) {
r := func() (r interface{}) {
defer func() { r = recover() }()
s4 := (*[4]string)(s)
_ = s4
return nil
}()
if r == nil {
t.Error("out-of-bounds conversion of s should panic")
}
})

t.Run("SharedMemory", func(t *testing.T) {
s2 := (*[2]string)(s)
(*s2)[0] = "x"
if s[0] != "x" {
t.Errorf("s[0] should be changed")
}

t.Skip("non-numeric slice to underlying array conversion is not supported for subslices")
s3 := (*[1]string)(s[1:])
(*s3)[0] = "y"
if s[1] != "y" {
t.Errorf("s[1] should be changed")
}
})

var q []string
t.Run("NilSlice", func(t *testing.T) {
q0 := (*[0]string)(q)
if q0 != nil {
t.Error("q0 should be nil")
}
})

t.Run("NilSliceToLargerArray", func(t *testing.T) {
r := func() (r interface{}) {
defer func() { r = recover() }()
q1 := (*[1]string)(q)
_ = q1
return nil
}
if r == nil {
t.Error("out-of-bounds conversion of q should panic")
}
})

t.Run("ZeroLenSlice", func(t *testing.T) {
u := make([]string, 0)
u0 := (*[0]string)(u)
if u0 == nil {
t.Error("u0 should not be nil")
}
})
})
}