Skip to content

Commit

Permalink
compiler: upgrade to Go 1.13
Browse files Browse the repository at this point in the history
This is an initial cut of 1.13 support. internal/reflectlite is a new
package in Go 1.13 that presents a subset of the API of reflect.

internal/reflectlite support has been added according to the following
steps:

* in the build step, override the .GoFiles for internal/reflectlite to
be empty, i.e. we will only take the native (GopherJS) implementation
and won't augment any GOROOT definitions
* taking the current native (GopherJS) implementation of reflect
* taking the Go 1.13 implementation of reflect
* adding all files into natives/src/internal/reflectlite (marking the Go
1.13 reflect files as _original.go)
* progressively removing definitions from the *_original.go files until
we get back to a package that compiles. This involves removing certain
superfluous definitions (that only exist in the API of reflect) that
bring in unwanted imports, e.g. strconv).

We also then special case internal/reflectlite in the same places that
reflect is special-cased.

To handle the new core vendor of golang.org/x/net/dns/dnsmessage and
friends, we use .ImportMap from the output of go list to resolve the
correct package for any given import within a package.

WIP - we still need to fix the handling of import paths at test time.
  • Loading branch information
myitcv committed Sep 30, 2019
1 parent 5e18f2c commit 25f2f08
Show file tree
Hide file tree
Showing 20 changed files with 6,148 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:

environment:
NVM_VERSION: v0.33.11
GO_VERSION: go1.12.8
GO_VERSION: go1.13
NODE_VERSION: 10.13.0

steps:
Expand Down
53 changes: 41 additions & 12 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,21 +191,33 @@ func (s *Session) importWithSrcDir(bctx build.Context, path string, srcDir strin
return nil, err
}
} else {
if im, ok := s.modImportMap[srcDir]; ok {
if ip, ok := im[path]; ok {
path = ip
}
}
dir, ok := s.modLookup[path]
if !ok {
if path == "syscall/js" {
mode |= build.FindOnly
dir = filepath.Join(runtime.GOROOT(), "src", "syscall", "js")
} else {
return nil, fmt.Errorf("failed to find import directory for %v", path)
for d := range s.modImportMap {
for k, v := range s.modImportMap[d] {
fmt.Printf(">> %v: %v => %v\n", d, k, v)
}
}
return nil, fmt.Errorf("failed to find import directory for %v in %v", path, srcDir)
}
}

// set IgnoreVendor even in module mode to prevent go/build from doing
// anything with go list; we've already done that work.
pkg, err = bctx.ImportDir(dir, mode|build.IgnoreVendor)
if err != nil {
return nil, fmt.Errorf("build context ImportDir failed: %v", err)
if _, ok := err.(*build.NoGoError); !ok || path != "syscall/js" {
return nil, fmt.Errorf("build context ImportDir failed: %v", err)
}
}
// because ImportDir doesn't know the ImportPath, we need to set
// certain things manually
Expand All @@ -224,7 +236,7 @@ func (s *Session) importWithSrcDir(bctx build.Context, path string, srcDir strin
pkg.GoFiles = []string{"error.go"}
case "runtime/internal/sys":
pkg.GoFiles = []string{fmt.Sprintf("zgoos_%s.go", bctx.GOOS), "stubs.go", "zversion.go"}
case "runtime/pprof":
case "runtime/pprof", "internal/reflectlite":
pkg.GoFiles = nil
case "internal/poll":
pkg.GoFiles = exclude(pkg.GoFiles, "fd_poll_runtime.go")
Expand Down Expand Up @@ -567,6 +579,11 @@ type Session struct {
// a nil value implies we are not in module mode
modLookup map[string]string

// modImportMap is the information contained in the .ImportMap
// field for a given package, i.e. the final resolved import paths
// for packages
modImportMap map[string]map[string]string

// map of module path
mods map[string]string
}
Expand Down Expand Up @@ -652,7 +669,7 @@ func (s *Session) determineModLookup(tests bool, imports []string) error {
imports = append(imports, "runtime", "github.com/gopherjs/gopherjs/js", "github.com/gopherjs/gopherjs/nosync")

var stdout, stderr bytes.Buffer
golistCmd := exec.Command("go", "list", "-e", "-deps", `-f={{if or (eq .ForTest "") (eq .ForTest "`+imports[0]+`")}}{ {{with .Error}}"Error": "{{.Err}}",{{end}} "ImportPath": "{{.ImportPath}}", "Dir": "{{.Dir}}"{{with .Module}}, "Module": {"Path": "{{.Path}}", "Dir": "{{.Dir}}"}{{end}}}{{end}}`)
golistCmd := exec.Command("go", "list", "-e", "-deps", "-json")
if tests {
golistCmd.Args = append(golistCmd.Args, "-test")
}
Expand All @@ -670,17 +687,22 @@ func (s *Session) determineModLookup(tests bool, imports []string) error {
dec := json.NewDecoder(&stdout)

s.modLookup = make(map[string]string)
s.modImportMap = make(map[string]map[string]string)
s.mods = make(map[string]string)

for {
var entry struct {
Error string
ForTest string
ImportPath string
Dir string
ImportMap map[string]string
Module struct {
Path string
Dir string
}
Error struct {
Err string
}
}

if err := dec.Decode(&entry); err != nil {
Expand All @@ -689,25 +711,31 @@ func (s *Session) determineModLookup(tests bool, imports []string) error {
}
return fmt.Errorf("failed to decode list output: %v\n%s", err, stdout.Bytes())
}
if entry.ForTest != "" && entry.ForTest != imports[0] {
continue
}

// If a dependency relies on syscall/js we have a problem. All files
// in syscall/js are build constrained to GOOS=js GOARCH=wasm. Hence
// our go list will fail. And we can't go list with GOOS=js and GOARCH=wasm
// because we're not building for that target. Hence we need to more
// gracefully handle errors. We therefore report all errors _except_
// the failure to load syscall/js. WARNING - gross hack follows
if entry.Error != "" {
if entry.Error.Err != "" {
msg := fmt.Sprintf("build constraints exclude all Go files in " + filepath.Join(runtime.GOROOT(), "src", "syscall", "js"))
if !strings.HasSuffix(entry.Error, msg) {
if !strings.HasSuffix(entry.Error.Err, msg) {
return fmt.Errorf("failed to resolve dependencies: %v", entry.Error)
}
}

ipParts := strings.Split(entry.ImportPath, " ")
entry.ImportPath = ipParts[0]
if ipParts[0] == "internal/testenv" {
entry.ImportPath = ipParts[0]
}

s.modLookup[entry.ImportPath] = entry.Dir
s.mods[entry.Module.Path] = entry.Module.Dir
s.modImportMap[entry.Dir] = entry.ImportMap
}

return nil
Expand Down Expand Up @@ -866,6 +894,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
}

if hw != nil {
fmt.Fprintf(hw, "package dir: %v\n", pkg.Dir)
fmt.Fprintf(hw, "compiler binary hash: %v\n", compilerBinaryHash)

orderedBuildTags := append([]string{}, s.options.BuildTags...)
Expand Down Expand Up @@ -898,7 +927,7 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
continue
}

_, importedArchive, err := s.buildImportPathWithSrcDir(importedPkgPath, s.wd)
_, importedArchive, err := s.buildImportPathWithSrcDir(importedPkgPath, pkg.Dir)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -968,19 +997,19 @@ func (s *Session) BuildPackage(pkg *PackageData) (*compiler.Archive, error) {
localImportPathCache := make(map[string]*compiler.Archive)
importContext := &compiler.ImportContext{
Packages: s.Types,
Import: func(path string) (*compiler.Archive, error) {
ImportFrom: func(path, dir string) (*compiler.Archive, error) {
if archive, ok := localImportPathCache[path]; ok {
return archive, nil
}
_, archive, err := s.buildImportPathWithSrcDir(path, s.wd)
_, archive, err := s.buildImportPathWithSrcDir(path, dir)
if err != nil {
return nil, err
}
localImportPathCache[path] = archive
return archive, nil
},
}
archive, err := compiler.Compile(pkg.ImportPath, files, fset, importContext, s.options.Minify)
archive, err := compiler.Compile(pkg.ImportPath, pkg.Dir, files, fset, importContext, s.options.Minify)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (

var sizes32 = &types.StdSizes{WordSize: 4, MaxAlign: 8}
var reservedKeywords = make(map[string]bool)
var _ = ___GOPHERJS_REQUIRES_GO_VERSION_1_12___ // Compile error on other Go versions, because they're not supported.
var _ = ___GOPHERJS_REQUIRES_GO_VERSION_1_13___ // Compile error on other Go versions, because they're not supported.

func init() {
for _, keyword := range []string{"abstract", "arguments", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "undefined", "var", "void", "volatile", "while", "with", "yield"} {
Expand Down
2 changes: 1 addition & 1 deletion compiler/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,7 @@ func (c *funcContext) translateConversion(expr ast.Expr, desiredType types.Type)
return c.translateExpr(expr)
}

if c.p.Pkg.Path() == "reflect" {
if c.p.Pkg.Path() == "reflect" || c.p.Pkg.Path() == "internal/reflectlite" {
if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(c.p.TypeOf(call.Fun), types.Typ[types.UnsafePointer]) {
if ptr, isPtr := desiredType.(*types.Pointer); isPtr {
if named, isNamed := ptr.Elem().(*types.Named); isNamed {
Expand Down
71 changes: 67 additions & 4 deletions compiler/natives/fs_vfsdata.go

Large diffs are not rendered by default.

136 changes: 136 additions & 0 deletions compiler/natives/src/internal/reflectlite/deepequal_original.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// +build js

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Deep equality test via reflection

package reflectlite

import "unsafe"

// During deepValueEqual, must keep track of checks that are
// in progress. The comparison algorithm assumes that all
// checks in progress are true when it reencounters them.
// Visited comparisons are stored in a map indexed by visit.
type visit struct {
a1 unsafe.Pointer
a2 unsafe.Pointer
typ Type
}

// Tests for deep equality using reflected types. The map argument tracks
// comparisons that have already been seen, which allows short circuiting on
// recursive types.
func deepValueEqual(v1, v2 Value, visited map[visit]bool, depth int) bool {
if !v1.IsValid() || !v2.IsValid() {
return v1.IsValid() == v2.IsValid()
}
if v1.Type() != v2.Type() {
return false
}

// if depth > 10 { panic("deepValueEqual") } // for debugging

// We want to avoid putting more in the visited map than we need to.
// For any possible reference cycle that might be encountered,
// hard(t) needs to return true for at least one of the types in the cycle.
hard := func(k Kind) bool {
switch k {
case Map, Slice, Ptr, Interface:
return true
}
return false
}

if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
addr1 := unsafe.Pointer(v1.UnsafeAddr())
addr2 := unsafe.Pointer(v2.UnsafeAddr())
if uintptr(addr1) > uintptr(addr2) {
// Canonicalize order to reduce number of entries in visited.
// Assumes non-moving garbage collector.
addr1, addr2 = addr2, addr1
}

// Short circuit if references are already seen.
typ := v1.Type()
v := visit{addr1, addr2, typ}
if visited[v] {
return true
}

// Remember for later.
visited[v] = true
}

switch v1.Kind() {
case Array:
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case Slice:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.Pointer() == v2.Pointer() {
return true
}
for i := 0; i < v1.Len(); i++ {
if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) {
return false
}
}
return true
case Interface:
if v1.IsNil() || v2.IsNil() {
return v1.IsNil() == v2.IsNil()
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1)
case Ptr:
if v1.Pointer() == v2.Pointer() {
return true
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1)
case Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
if !deepValueEqual(v1.Field(i), v2.Field(i), visited, depth+1) {
return false
}
}
return true
case Map:
if v1.IsNil() != v2.IsNil() {
return false
}
if v1.Len() != v2.Len() {
return false
}
if v1.Pointer() == v2.Pointer() {
return true
}
for _, k := range v1.MapKeys() {
val1 := v1.MapIndex(k)
val2 := v2.MapIndex(k)
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited, depth+1) {
return false
}
}
return true
case Func:
if v1.IsNil() && v2.IsNil() {
return true
}
// Can't do better than this:
return false
default:
// Normal equality suffices
return valueInterface(v1, false) == valueInterface(v2, false)
}
}
47 changes: 47 additions & 0 deletions compiler/natives/src/internal/reflectlite/makefunc_original.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// +build js

// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// MakeFunc implementation.

package reflectlite

// makeFuncImpl is the closure value implementing the function
// returned by MakeFunc.
// The first three words of this type must be kept in sync with
// methodValue and runtime.reflectMethodValue.
// Any changes should be reflected in all three.
type makeFuncImpl struct {
code uintptr
stack *bitVector // ptrmap for both args and results
argLen uintptr // just args
ftyp *funcType
fn func([]Value) []Value
}

// makeFuncStub is an assembly function that is the code half of
// the function returned from MakeFunc. It expects a *callReflectFunc
// as its context register, and its job is to invoke callReflect(ctxt, frame)
// where ctxt is the context register and frame is a pointer to the first
// word in the passed-in argument frame.
func makeFuncStub()

// The first 3 words of this type must be kept in sync with
// makeFuncImpl and runtime.reflectMethodValue.
// Any changes should be reflected in all three.
type methodValue struct {
fn uintptr
stack *bitVector // ptrmap for both args and results
argLen uintptr // just args
method int
rcvr Value
}

// methodValueCall is an assembly function that is the code half of
// the function returned from makeMethodValue. It expects a *methodValue
// as its context register, and its job is to invoke callMethod(ctxt, frame)
// where ctxt is the context register and frame is a pointer to the first
// word in the passed-in argument frame.
func methodValueCall()
Loading

0 comments on commit 25f2f08

Please sign in to comment.