From 1b072b3ed56c18619587354f499fcda5279718a2 Mon Sep 17 00:00:00 2001
From: Michael Pratt
Date: Thu, 14 Oct 2021 18:18:49 -0400
Subject: [PATCH 038/406] runtime: consistently access pollDesc r/w Gs with
atomics
Both netpollblock and netpollunblock read gpp using a non-atomic load.
When consuming a ready event, netpollblock clears gpp using a non-atomic
store, thus skipping a barrier.
Thus on systems with weak memory ordering, a sequence like so this is
possible:
T1 T2
1. netpollblock: read gpp -> pdReady
2. netpollblock: store gpp -> 0
3. netpollunblock: read gpp -> pdReady
4. netpollunblock: return
i.e., without a happens-before edge between (2) and (3), netpollunblock
may read the stale value of gpp.
Switch these access to use atomic loads and stores in order to create
these edges.
For ease of future maintainance, I've simply changed rg and wg to always
be accessed atomically, though I don't believe pollOpen or pollClose
require atomics today.
Fixes #48925
Change-Id: I903ea667eea320277610b4f969129935731520c3
Reviewed-on: https://go-review.googlesource.com/c/go/+/355952
Trust: Michael Pratt
Run-TryBot: Michael Pratt
TryBot-Result: Go Bot
Reviewed-by: Michael Knyszek
Reviewed-by: David Chase
---
src/runtime/netpoll.go | 43 +++++++++++++++++++++++++-----------------
1 file changed, 26 insertions(+), 17 deletions(-)
diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go
index 239371158f..1008b4422c 100644
--- a/src/runtime/netpoll.go
+++ b/src/runtime/netpoll.go
@@ -78,6 +78,7 @@ type pollDesc struct {
// pollReset, pollWait, pollWaitCanceled and runtime·netpollready (IO readiness notification)
// proceed w/o taking the lock. So closing, everr, rg, rd, wg and wd are manipulated
// in a lock-free way by all operations.
+ // TODO(golang.org/issue/49008): audit these lock-free fields for continued correctness.
// NOTE(dvyukov): the following code uses uintptr to store *g (rg/wg),
// that will blow up when GC starts moving objects.
lock mutex // protects the following fields
@@ -86,11 +87,11 @@ type pollDesc struct {
everr bool // marks event scanning error happened
user uint32 // user settable cookie
rseq uintptr // protects from stale read timers
- rg uintptr // pdReady, pdWait, G waiting for read or nil
+ rg uintptr // pdReady, pdWait, G waiting for read or nil. Accessed atomically.
rt timer // read deadline timer (set if rt.f != nil)
rd int64 // read deadline
wseq uintptr // protects from stale write timers
- wg uintptr // pdReady, pdWait, G waiting for write or nil
+ wg uintptr // pdReady, pdWait, G waiting for write or nil. Accessed atomically.
wt timer // write deadline timer
wd int64 // write deadline
self *pollDesc // storage for indirect interface. See (*pollDesc).makeArg.
@@ -147,20 +148,22 @@ func poll_runtime_isPollServerDescriptor(fd uintptr) bool {
func poll_runtime_pollOpen(fd uintptr) (*pollDesc, int) {
pd := pollcache.alloc()
lock(&pd.lock)
- if pd.wg != 0 && pd.wg != pdReady {
+ wg := atomic.Loaduintptr(&pd.wg)
+ if wg != 0 && wg != pdReady {
throw("runtime: blocked write on free polldesc")
}
- if pd.rg != 0 && pd.rg != pdReady {
+ rg := atomic.Loaduintptr(&pd.rg)
+ if rg != 0 && rg != pdReady {
throw("runtime: blocked read on free polldesc")
}
pd.fd = fd
pd.closing = false
pd.everr = false
pd.rseq++
- pd.rg = 0
+ atomic.Storeuintptr(&pd.rg, 0)
pd.rd = 0
pd.wseq++
- pd.wg = 0
+ atomic.Storeuintptr(&pd.wg, 0)
pd.wd = 0
pd.self = pd
unlock(&pd.lock)
@@ -178,10 +181,12 @@ func poll_runtime_pollClose(pd *pollDesc) {
if !pd.closing {
throw("runtime: close polldesc w/o unblock")
}
- if pd.wg != 0 && pd.wg != pdReady {
+ wg := atomic.Loaduintptr(&pd.wg)
+ if wg != 0 && wg != pdReady {
throw("runtime: blocked write on closing polldesc")
}
- if pd.rg != 0 && pd.rg != pdReady {
+ rg := atomic.Loaduintptr(&pd.rg)
+ if rg != 0 && rg != pdReady {
throw("runtime: blocked read on closing polldesc")
}
netpollclose(pd.fd)
@@ -205,9 +210,9 @@ func poll_runtime_pollReset(pd *pollDesc, mode int) int {
return errcode
}
if mode == 'r' {
- pd.rg = 0
+ atomic.Storeuintptr(&pd.rg, 0)
} else if mode == 'w' {
- pd.wg = 0
+ atomic.Storeuintptr(&pd.wg, 0)
}
return pollNoError
}
@@ -417,6 +422,8 @@ func netpollgoready(gp *g, traceskip int) {
// returns true if IO is ready, or false if timedout or closed
// waitio - wait only for completed IO, ignore errors
+// Concurrent calls to netpollblock in the same mode are forbidden, as pollDesc
+// can hold only a single waiting goroutine for each mode.
func netpollblock(pd *pollDesc, mode int32, waitio bool) bool {
gpp := &pd.rg
if mode == 'w' {
@@ -425,17 +432,19 @@ func netpollblock(pd *pollDesc, mode int32, waitio bool) bool {
// set the gpp semaphore to pdWait
for {
- old := *gpp
- if old == pdReady {
- *gpp = 0
+ // Consume notification if already ready.
+ if atomic.Casuintptr(gpp, pdReady, 0) {
return true
}
- if old != 0 {
- throw("runtime: double wait")
- }
if atomic.Casuintptr(gpp, 0, pdWait) {
break
}
+
+ // Double check that this isn't corrupt; otherwise we'd loop
+ // forever.
+ if v := atomic.Loaduintptr(gpp); v != pdReady && v != 0 {
+ throw("runtime: double wait")
+ }
}
// need to recheck error states after setting gpp to pdWait
@@ -459,7 +468,7 @@ func netpollunblock(pd *pollDesc, mode int32, ioready bool) *g {
}
for {
- old := *gpp
+ old := atomic.Loaduintptr(gpp)
if old == pdReady {
return nil
}
From 4a7975e73a2815b93caf6697fec4f4e777e729a1 Mon Sep 17 00:00:00 2001
From: Dan Scales
Date: Fri, 15 Oct 2021 11:54:25 -0700
Subject: [PATCH 039/406] cmd/compile: convert to using a map in getInstInfo,
rather than SetImplicit()
SetImplicit() has an explicit meaning and really shouldn't be used in
this way - its use is left over from early prototype of the dictionary
code. Convert from using SetImplicit to just using a map during
traversal.
Change-Id: I3d257c101a859f000e159d7ced307d1b7cf990d4
Reviewed-on: https://go-review.googlesource.com/c/go/+/356310
Run-TryBot: Dan Scales
TryBot-Result: Go Bot
Reviewed-by: Keith Randall
Trust: Dan Scales
---
src/cmd/compile/internal/noder/stencil.go | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go
index cfa90e4399..3a1baeae88 100644
--- a/src/cmd/compile/internal/noder/stencil.go
+++ b/src/cmd/compile/internal/noder/stencil.go
@@ -1854,14 +1854,19 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
}
}
+ // Map to remember when we have seen an instantiated function value or method
+ // expression/value as part of a call, so we can determine when we encounter
+ // an uncalled function value or method expression/value.
+ callMap := make(map[ir.Node]bool)
+
var visitFunc func(ir.Node)
visitFunc = func(n ir.Node) {
- if n.Op() == ir.OFUNCINST && !n.(*ir.InstExpr).Implicit() {
+ if n.Op() == ir.OFUNCINST && !callMap[n] {
if hasShapeNodes(n.(*ir.InstExpr).Targs) {
infoPrint(" Closure&subdictionary required at generic function value %v\n", n.(*ir.InstExpr).X)
info.subDictCalls = append(info.subDictCalls, n)
}
- } else if (n.Op() == ir.OMETHEXPR || n.Op() == ir.OMETHVALUE) && !n.(*ir.SelectorExpr).Implicit() &&
+ } else if (n.Op() == ir.OMETHEXPR || n.Op() == ir.OMETHVALUE) && !callMap[n] &&
!types.IsInterfaceMethod(n.(*ir.SelectorExpr).Selection.Type) &&
len(deref(n.(*ir.SelectorExpr).X.Type()).RParams()) > 0 {
if hasShapeTypes(deref(n.(*ir.SelectorExpr).X.Type()).RParams()) {
@@ -1874,16 +1879,15 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
}
}
if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST {
- n.(*ir.CallExpr).X.(*ir.InstExpr).SetImplicit(true)
+ callMap[n.(*ir.CallExpr).X] = true
if hasShapeNodes(n.(*ir.CallExpr).X.(*ir.InstExpr).Targs) {
infoPrint(" Subdictionary at generic function/method call: %v - %v\n", n.(*ir.CallExpr).X.(*ir.InstExpr).X, n)
info.subDictCalls = append(info.subDictCalls, n)
}
}
if n.Op() == ir.OCALLMETH && n.(*ir.CallExpr).X.Op() == ir.ODOTMETH &&
- //n.(*ir.CallExpr).X.(*ir.SelectorExpr).Selection != nil &&
len(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) > 0 {
- n.(*ir.CallExpr).X.(*ir.SelectorExpr).SetImplicit(true)
+ callMap[n.(*ir.CallExpr).X] = true
if hasShapeTypes(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) {
infoPrint(" Subdictionary at generic method call: %v\n", n)
info.subDictCalls = append(info.subDictCalls, n)
@@ -1891,7 +1895,7 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
}
if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OXDOT &&
isShapeDeref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()) {
- n.(*ir.CallExpr).X.(*ir.SelectorExpr).SetImplicit(true)
+ callMap[n.(*ir.CallExpr).X] = true
infoPrint(" Optional subdictionary at generic bound call: %v\n", n)
info.subDictCalls = append(info.subDictCalls, n)
}
From ed1c8db308029bd82e6de4d547196f7f841236bb Mon Sep 17 00:00:00 2001
From: Dan Scales
Date: Fri, 15 Oct 2021 12:31:39 -0700
Subject: [PATCH 040/406] cmd/compile: cleanup code in getInstInfo to use
switch statement
Simple cleanup: convert a bunch of if's to a switch statement in
getInstInfo. Also, use a few extra variables to avoid repeated node
conversions (such as n.(*ir.CallExpr))
Change-Id: I7a2a4efb569415256a8bc9350fb100bd2d8cfb39
Reviewed-on: https://go-review.googlesource.com/c/go/+/356311
Run-TryBot: Dan Scales
TryBot-Result: Go Bot
Reviewed-by: Keith Randall
Trust: Dan Scales
---
src/cmd/compile/internal/noder/stencil.go | 115 ++++++++++++----------
1 file changed, 62 insertions(+), 53 deletions(-)
diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go
index 3a1baeae88..592de7017f 100644
--- a/src/cmd/compile/internal/noder/stencil.go
+++ b/src/cmd/compile/internal/noder/stencil.go
@@ -28,8 +28,8 @@ func assert(p bool) {
base.Assert(p)
}
-// Temporary - for outputting information on derived types, dictionaries, sub-dictionaries.
-// Turn off when running tests.
+// For outputting debug information on dictionary format and instantiated dictionaries
+// (type arg, derived types, sub-dictionary, and itab entries).
var infoPrintMode = false
func infoPrint(format string, a ...interface{}) {
@@ -1861,15 +1861,16 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
var visitFunc func(ir.Node)
visitFunc = func(n ir.Node) {
- if n.Op() == ir.OFUNCINST && !callMap[n] {
- if hasShapeNodes(n.(*ir.InstExpr).Targs) {
+ switch n.Op() {
+ case ir.OFUNCINST:
+ if !callMap[n] && hasShapeNodes(n.(*ir.InstExpr).Targs) {
infoPrint(" Closure&subdictionary required at generic function value %v\n", n.(*ir.InstExpr).X)
info.subDictCalls = append(info.subDictCalls, n)
}
- } else if (n.Op() == ir.OMETHEXPR || n.Op() == ir.OMETHVALUE) && !callMap[n] &&
- !types.IsInterfaceMethod(n.(*ir.SelectorExpr).Selection.Type) &&
- len(deref(n.(*ir.SelectorExpr).X.Type()).RParams()) > 0 {
- if hasShapeTypes(deref(n.(*ir.SelectorExpr).X.Type()).RParams()) {
+ case ir.OMETHEXPR, ir.OMETHVALUE:
+ if !callMap[n] && !types.IsInterfaceMethod(n.(*ir.SelectorExpr).Selection.Type) &&
+ len(deref(n.(*ir.SelectorExpr).X.Type()).RParams()) > 0 &&
+ hasShapeTypes(deref(n.(*ir.SelectorExpr).X.Type()).RParams()) {
if n.(*ir.SelectorExpr).X.Op() == ir.OTYPE {
infoPrint(" Closure&subdictionary required at generic meth expr %v\n", n)
} else {
@@ -1877,43 +1878,48 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
}
info.subDictCalls = append(info.subDictCalls, n)
}
- }
- if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST {
- callMap[n.(*ir.CallExpr).X] = true
- if hasShapeNodes(n.(*ir.CallExpr).X.(*ir.InstExpr).Targs) {
- infoPrint(" Subdictionary at generic function/method call: %v - %v\n", n.(*ir.CallExpr).X.(*ir.InstExpr).X, n)
+ case ir.OCALL:
+ ce := n.(*ir.CallExpr)
+ if ce.X.Op() == ir.OFUNCINST {
+ callMap[ce.X] = true
+ if hasShapeNodes(ce.X.(*ir.InstExpr).Targs) {
+ infoPrint(" Subdictionary at generic function/method call: %v - %v\n", ce.X.(*ir.InstExpr).X, n)
+ info.subDictCalls = append(info.subDictCalls, n)
+ }
+ }
+ if ce.X.Op() == ir.OXDOT &&
+ isShapeDeref(ce.X.(*ir.SelectorExpr).X.Type()) {
+ callMap[ce.X] = true
+ infoPrint(" Optional subdictionary at generic bound call: %v\n", n)
info.subDictCalls = append(info.subDictCalls, n)
}
- }
- if n.Op() == ir.OCALLMETH && n.(*ir.CallExpr).X.Op() == ir.ODOTMETH &&
- len(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) > 0 {
- callMap[n.(*ir.CallExpr).X] = true
- if hasShapeTypes(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) {
- infoPrint(" Subdictionary at generic method call: %v\n", n)
- info.subDictCalls = append(info.subDictCalls, n)
+ case ir.OCALLMETH:
+ ce := n.(*ir.CallExpr)
+ if ce.X.Op() == ir.ODOTMETH &&
+ len(deref(ce.X.(*ir.SelectorExpr).X.Type()).RParams()) > 0 {
+ callMap[ce.X] = true
+ if hasShapeTypes(deref(ce.X.(*ir.SelectorExpr).X.Type()).RParams()) {
+ infoPrint(" Subdictionary at generic method call: %v\n", n)
+ info.subDictCalls = append(info.subDictCalls, n)
+ }
}
- }
- if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OXDOT &&
- isShapeDeref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()) {
- callMap[n.(*ir.CallExpr).X] = true
- infoPrint(" Optional subdictionary at generic bound call: %v\n", n)
- info.subDictCalls = append(info.subDictCalls, n)
- }
- if n.Op() == ir.OCONVIFACE && n.Type().IsInterface() &&
- !n.Type().IsEmptyInterface() &&
- n.(*ir.ConvExpr).X.Type().HasShape() {
- infoPrint(" Itab for interface conv: %v\n", n)
- info.itabConvs = append(info.itabConvs, n)
- }
- if n.Op() == ir.OXDOT && n.(*ir.SelectorExpr).X.Type().IsShape() {
- infoPrint(" Itab for bound call: %v\n", n)
- info.itabConvs = append(info.itabConvs, n)
- }
- if (n.Op() == ir.ODOTTYPE || n.Op() == ir.ODOTTYPE2) && !n.(*ir.TypeAssertExpr).Type().IsInterface() && !n.(*ir.TypeAssertExpr).X.Type().IsEmptyInterface() {
- infoPrint(" Itab for dot type: %v\n", n)
- info.itabConvs = append(info.itabConvs, n)
- }
- if n.Op() == ir.OCLOSURE {
+ case ir.OCONVIFACE:
+ if n.Type().IsInterface() && !n.Type().IsEmptyInterface() &&
+ n.(*ir.ConvExpr).X.Type().HasShape() {
+ infoPrint(" Itab for interface conv: %v\n", n)
+ info.itabConvs = append(info.itabConvs, n)
+ }
+ case ir.OXDOT:
+ if n.(*ir.SelectorExpr).X.Type().IsShape() {
+ infoPrint(" Itab for bound call: %v\n", n)
+ info.itabConvs = append(info.itabConvs, n)
+ }
+ case ir.ODOTTYPE, ir.ODOTTYPE2:
+ if !n.(*ir.TypeAssertExpr).Type().IsInterface() && !n.(*ir.TypeAssertExpr).X.Type().IsEmptyInterface() {
+ infoPrint(" Itab for dot type: %v\n", n)
+ info.itabConvs = append(info.itabConvs, n)
+ }
+ case ir.OCLOSURE:
// Visit the closure body and add all relevant entries to the
// dictionary of the outer function (closure will just use
// the dictionary of the outer function).
@@ -1924,18 +1930,21 @@ func (g *genInst) getInstInfo(st *ir.Func, shapes []*types.Type, instInfo *instI
for _, n := range cfunc.Dcl {
n.DictIndex = uint16(findDictType(instInfo, n.Type()) + 1)
}
- }
- if n.Op() == ir.OSWITCH && n.(*ir.SwitchStmt).Tag != nil && n.(*ir.SwitchStmt).Tag.Op() == ir.OTYPESW && !n.(*ir.SwitchStmt).Tag.(*ir.TypeSwitchGuard).X.Type().IsEmptyInterface() {
- for _, cc := range n.(*ir.SwitchStmt).Cases {
- for _, c := range cc.List {
- if c.Op() == ir.OTYPE && c.Type().HasShape() {
- // Type switch from a non-empty interface - might need an itab.
- infoPrint(" Itab for type switch: %v\n", c)
- info.itabConvs = append(info.itabConvs, c)
- if info.type2switchType == nil {
- info.type2switchType = map[ir.Node]*types.Type{}
+ case ir.OSWITCH:
+ ss := n.(*ir.SwitchStmt)
+ if ss.Tag != nil && ss.Tag.Op() == ir.OTYPESW &&
+ !ss.Tag.(*ir.TypeSwitchGuard).X.Type().IsEmptyInterface() {
+ for _, cc := range ss.Cases {
+ for _, c := range cc.List {
+ if c.Op() == ir.OTYPE && c.Type().HasShape() {
+ // Type switch from a non-empty interface - might need an itab.
+ infoPrint(" Itab for type switch: %v\n", c)
+ info.itabConvs = append(info.itabConvs, c)
+ if info.type2switchType == nil {
+ info.type2switchType = map[ir.Node]*types.Type{}
+ }
+ info.type2switchType[c] = ss.Tag.(*ir.TypeSwitchGuard).X.Type()
}
- info.type2switchType[c] = n.(*ir.SwitchStmt).Tag.(*ir.TypeSwitchGuard).X.Type()
}
}
}
From 680caf15355057ca84857a2a291b6f5c44e73329 Mon Sep 17 00:00:00 2001
From: Richard Musiol
Date: Fri, 3 Sep 2021 21:58:57 +0200
Subject: [PATCH 041/406] misc/wasm: expect environment to provide polyfills
The list of environments to support with wasm_exec.js was becoming too
large to maintain. With this change, wasm_exec.js expects that the
environment provides all necessary polyfills.
The standardized "globalThis" is used for accessing the environment.
wasm_exec.js now only provides stub fallbacks for globalThis.fs and
globalThis.process.
All code specific to Node.js is now in a separate file.
Change-Id: I076febbd94d4d7845260faad972f450f74a7b983
Reviewed-on: https://go-review.googlesource.com/c/go/+/347353
Trust: Richard Musiol
Reviewed-by: Cherry Mui
---
misc/wasm/go_js_wasm_exec | 2 +-
misc/wasm/wasm_exec.js | 115 +++++-------------------------------
misc/wasm/wasm_exec_node.js | 49 +++++++++++++++
3 files changed, 66 insertions(+), 100 deletions(-)
create mode 100644 misc/wasm/wasm_exec_node.js
diff --git a/misc/wasm/go_js_wasm_exec b/misc/wasm/go_js_wasm_exec
index b700722dfe..fcbd0e4fc8 100755
--- a/misc/wasm/go_js_wasm_exec
+++ b/misc/wasm/go_js_wasm_exec
@@ -11,4 +11,4 @@ while [ -h "$SOURCE" ]; do
done
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
-exec node "$DIR/wasm_exec.js" "$@"
+exec node "$DIR/wasm_exec_node.js" "$@"
diff --git a/misc/wasm/wasm_exec.js b/misc/wasm/wasm_exec.js
index e2f3cda995..30044a6f85 100644
--- a/misc/wasm/wasm_exec.js
+++ b/misc/wasm/wasm_exec.js
@@ -1,49 +1,19 @@
// Copyright 2018 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.
+
"use strict";
(() => {
- // Map multiple JavaScript environments to a single common API,
- // preferring web standards over Node.js API.
- //
- // Environments considered:
- // - Browsers
- // - Node.js
- // - Electron
- // - Parcel
- // - Webpack
-
- if (typeof global !== "undefined") {
- // global already exists
- } else if (typeof window !== "undefined") {
- window.global = window;
- } else if (typeof self !== "undefined") {
- self.global = self;
- } else {
- throw new Error("cannot export Go (neither global, window nor self is defined)");
- }
-
- if (!global.require && typeof require !== "undefined") {
- global.require = require;
- }
-
- if (!global.fs && global.require) {
- const fs = require("fs");
- if (typeof fs === "object" && fs !== null && Object.keys(fs).length !== 0) {
- global.fs = fs;
- }
- }
-
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
- if (!global.fs) {
+ if (!globalThis.fs) {
let outputBuf = "";
- global.fs = {
+ globalThis.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
@@ -88,8 +58,8 @@
};
}
- if (!global.process) {
- global.process = {
+ if (!globalThis.process) {
+ globalThis.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
@@ -103,47 +73,26 @@
}
}
- if (!global.crypto && global.require) {
- const nodeCrypto = require("crypto");
- global.crypto = {
- getRandomValues(b) {
- nodeCrypto.randomFillSync(b);
- },
- };
- }
- if (!global.crypto) {
- throw new Error("global.crypto is not available, polyfill required (getRandomValues only)");
+ if (!globalThis.crypto) {
+ throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
}
- if (!global.performance) {
- global.performance = {
- now() {
- const [sec, nsec] = process.hrtime();
- return sec * 1000 + nsec / 1000000;
- },
- };
+ if (!globalThis.performance) {
+ throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
}
- if (!global.TextEncoder && global.require) {
- global.TextEncoder = require("util").TextEncoder;
- }
- if (!global.TextEncoder) {
- throw new Error("global.TextEncoder is not available, polyfill required");
+ if (!globalThis.TextEncoder) {
+ throw new Error("globalThis.TextEncoder is not available, polyfill required");
}
- if (!global.TextDecoder && global.require) {
- global.TextDecoder = require("util").TextDecoder;
+ if (!globalThis.TextDecoder) {
+ throw new Error("globalThis.TextDecoder is not available, polyfill required");
}
- if (!global.TextDecoder) {
- throw new Error("global.TextDecoder is not available, polyfill required");
- }
-
- // End of polyfills for common API.
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
- global.Go = class {
+ globalThis.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
@@ -518,7 +467,7 @@
null,
true,
false,
- global,
+ globalThis,
this,
];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
@@ -527,7 +476,7 @@
[null, 2],
[true, 3],
[false, 4],
- [global, 5],
+ [globalThis, 5],
[this, 6],
]);
this._idPool = []; // unused ids that have been garbage collected
@@ -602,36 +551,4 @@
};
}
}
-
- if (
- typeof module !== "undefined" &&
- global.require &&
- global.require.main === module &&
- global.process &&
- global.process.versions &&
- !global.process.versions.electron
- ) {
- if (process.argv.length < 3) {
- console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
- process.exit(1);
- }
-
- const go = new Go();
- go.argv = process.argv.slice(2);
- go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
- go.exit = process.exit;
- WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
- process.on("exit", (code) => { // Node.js exits if no event handler is pending
- if (code === 0 && !go.exited) {
- // deadlock, make Go print error and stack traces
- go._pendingEvent = { id: 0 };
- go._resume();
- }
- });
- return go.run(result.instance);
- }).catch((err) => {
- console.error(err);
- process.exit(1);
- });
- }
})();
diff --git a/misc/wasm/wasm_exec_node.js b/misc/wasm/wasm_exec_node.js
new file mode 100644
index 0000000000..f9200ca950
--- /dev/null
+++ b/misc/wasm/wasm_exec_node.js
@@ -0,0 +1,49 @@
+// Copyright 2021 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.
+
+"use strict";
+
+if (process.argv.length < 3) {
+ console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
+ process.exit(1);
+}
+
+globalThis.require = require;
+globalThis.fs = require("fs");
+globalThis.TextEncoder = require("util").TextEncoder;
+globalThis.TextDecoder = require("util").TextDecoder;
+
+globalThis.performance = {
+ now() {
+ const [sec, nsec] = process.hrtime();
+ return sec * 1000 + nsec / 1000000;
+ },
+};
+
+const crypto = require("crypto");
+globalThis.crypto = {
+ getRandomValues(b) {
+ crypto.randomFillSync(b);
+ },
+};
+
+require("./wasm_exec");
+
+const go = new Go();
+go.argv = process.argv.slice(2);
+go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
+go.exit = process.exit;
+WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
+ process.on("exit", (code) => { // Node.js exits if no event handler is pending
+ if (code === 0 && !go.exited) {
+ // deadlock, make Go print error and stack traces
+ go._pendingEvent = { id: 0 };
+ go._resume();
+ }
+ });
+ return go.run(result.instance);
+}).catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
From 640a49b8d45760b7c965fc1529dffc8e000cc846 Mon Sep 17 00:00:00 2001
From: Dan Scales
Date: Fri, 15 Oct 2021 15:05:36 -0700
Subject: [PATCH 042/406] test: add a test for parameterized embedded field
Make sure that an embedded field like "MyStruct[T]" works and can be
referenced via the name MyStruct.
Change-Id: I8be1f1184dd42c4e54e4144aff2fd85e30af722f
Reviewed-on: https://go-review.googlesource.com/c/go/+/356312
Trust: Dan Scales
Reviewed-by: Robert Griesemer
---
src/cmd/compile/internal/typecheck/subr.go | 3 --
test/typeparam/genembed2.go | 46 ++++++++++++++++++++++
2 files changed, 46 insertions(+), 3 deletions(-)
create mode 100644 test/typeparam/genembed2.go
diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go
index b3fc7459e1..b4d5302525 100644
--- a/src/cmd/compile/internal/typecheck/subr.go
+++ b/src/cmd/compile/internal/typecheck/subr.go
@@ -1324,9 +1324,6 @@ func (ts *Tsubster) tstruct(t *types.Type, force bool) *types.Type {
}
}
if newfields != nil {
- // TODO(danscales): make sure this works for the field
- // names of embedded types (which should keep the name of
- // the type param, not the instantiated type).
newfields[i] = types.NewField(f.Pos, f.Sym, t2)
newfields[i].Embedded = f.Embedded
newfields[i].Note = f.Note
diff --git a/test/typeparam/genembed2.go b/test/typeparam/genembed2.go
new file mode 100644
index 0000000000..6effd2e6bc
--- /dev/null
+++ b/test/typeparam/genembed2.go
@@ -0,0 +1,46 @@
+// run -gcflags=-G=3
+
+// Copyright 2021 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.
+
+// Test for declaration and use of a parameterized embedded field.
+
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+type MyStruct[T any] struct {
+ val T
+}
+
+type Lockable[T any] struct {
+ MyStruct[T]
+ mu sync.Mutex
+}
+
+// Get returns the value stored in a Lockable.
+func (l *Lockable[T]) Get() T {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ return l.MyStruct.val
+}
+
+// Set sets the value in a Lockable.
+func (l *Lockable[T]) Set(v T) {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ l.MyStruct = MyStruct[T]{v}
+}
+
+func main() {
+ var li Lockable[int]
+
+ li.Set(5)
+ if got, want := li.Get(), 5; got != want {
+ panic(fmt.Sprintf("got %d, want %d", got, want))
+ }
+}
From fa7d11a0e9c0ed469111ba5fdd86f7462a48ef49 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Sat, 16 Oct 2021 14:54:42 -0700
Subject: [PATCH 043/406] go/types, types2: add test case for missing return
The respective issue was fixed in types2 with CL 356189;
and the problem didn't exist in go/types. This CL simply
adds the test case to the type checkers as well.
For #49003.
Change-Id: Ib50ee8bb0ad21f2916f2b79d4f77593302899a3e
Reviewed-on: https://go-review.googlesource.com/c/go/+/356411
Trust: Robert Griesemer
Run-TryBot: Robert Griesemer
TryBot-Result: Go Bot
Reviewed-by: Cuong Manh Le
---
.../internal/types2/testdata/fixedbugs/issue49003.go | 10 ++++++++++
src/go/types/testdata/fixedbugs/issue49003.go | 10 ++++++++++
2 files changed, 20 insertions(+)
create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue49003.go
create mode 100644 src/go/types/testdata/fixedbugs/issue49003.go
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49003.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49003.go
new file mode 100644
index 0000000000..ece1a27bb9
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49003.go
@@ -0,0 +1,10 @@
+// Copyright 2021 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.
+
+package p
+
+func f(s string) int {
+ for range s {
+ }
+} // ERROR missing return
diff --git a/src/go/types/testdata/fixedbugs/issue49003.go b/src/go/types/testdata/fixedbugs/issue49003.go
new file mode 100644
index 0000000000..ece1a27bb9
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue49003.go
@@ -0,0 +1,10 @@
+// Copyright 2021 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.
+
+package p
+
+func f(s string) int {
+ for range s {
+ }
+} // ERROR missing return
From cf51fb5d680a9a1ca98af3361e65722d07bff111 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Sat, 16 Oct 2021 20:30:12 -0700
Subject: [PATCH 044/406] cmd/compile, types2: avoid confusing follow-on error
in invalid type assertion
This CL avoids a useless follow-on error (that gets reported before the
actual error due to source position). This addresses the first part of
the issue below.
Thanks to @cuonglm for the suggestion for the fix.
For #49005.
Change-Id: Ifdd83072a05c32e115dc58a0233868a64f336f3f
Reviewed-on: https://go-review.googlesource.com/c/go/+/356449
Trust: Robert Griesemer
Run-TryBot: Robert Griesemer
TryBot-Result: Go Bot
Reviewed-by: Cuong Manh Le
---
src/cmd/compile/internal/types2/typexpr.go | 7 +++++++
test/fixedbugs/issue49005a.go | 13 +++++++++++++
2 files changed, 20 insertions(+)
create mode 100644 test/fixedbugs/issue49005a.go
diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go
index 646becbdae..eae9330914 100644
--- a/src/cmd/compile/internal/types2/typexpr.go
+++ b/src/cmd/compile/internal/types2/typexpr.go
@@ -312,6 +312,13 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) {
typ := new(Pointer)
def.setUnderlying(typ)
typ.base = check.varType(e.X)
+ // If typ.base is invalid, it's unlikely that *base is particularly
+ // useful - even a valid dereferenciation will lead to an invalid
+ // type again, and in some cases we get unexpected follow-on errors
+ // (e.g., see #49005). Return an invalid type instead.
+ if typ.base == Typ[Invalid] {
+ return Typ[Invalid]
+ }
return typ
}
diff --git a/test/fixedbugs/issue49005a.go b/test/fixedbugs/issue49005a.go
new file mode 100644
index 0000000000..55d92c4650
--- /dev/null
+++ b/test/fixedbugs/issue49005a.go
@@ -0,0 +1,13 @@
+// errorcheck
+
+// Copyright 2021 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.
+
+package p
+
+type T interface{ M() }
+
+func F() T
+
+var _ = F().(*X) // ERROR "undefined: X"
From 74acbaf94ab3c7aaa7e22fda4f90920e90f11ead Mon Sep 17 00:00:00 2001
From: wdvxdr
Date: Wed, 13 Oct 2021 19:44:38 +0800
Subject: [PATCH 045/406] cmd/compile: allow inlining labeled for-statement and
switch-statement
After CL 349012 and CL 350911, we can fully handle these
labeled statements, so we can allow them when inlining.
Updates #14768
Change-Id: I0ab3fd3f8d7436b49b1aedd946516b33c63f5747
Reviewed-on: https://go-review.googlesource.com/c/go/+/355497
Run-TryBot: David Chase
TryBot-Result: Go Bot
Reviewed-by: Matthew Dempsky
Reviewed-by: Dan Scales
Reviewed-by: David Chase
Trust: Dan Scales
---
src/cmd/compile/internal/inline/inl.go | 21 ---------------------
test/inline.go | 6 ++----
2 files changed, 2 insertions(+), 25 deletions(-)
diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go
index 51270a3315..a2268a5465 100644
--- a/src/cmd/compile/internal/inline/inl.go
+++ b/src/cmd/compile/internal/inline/inl.go
@@ -390,27 +390,6 @@ func (v *hairyVisitor) doNode(n ir.Node) bool {
// These nodes don't produce code; omit from inlining budget.
return false
- case ir.OFOR, ir.OFORUNTIL:
- n := n.(*ir.ForStmt)
- if n.Label != nil {
- v.reason = "labeled control"
- return true
- }
- case ir.OSWITCH:
- n := n.(*ir.SwitchStmt)
- if n.Label != nil {
- v.reason = "labeled control"
- return true
- }
- // case ir.ORANGE, ir.OSELECT in "unhandled" above
-
- case ir.OBREAK, ir.OCONTINUE:
- n := n.(*ir.BranchStmt)
- if n.Label != nil {
- // Should have short-circuited due to labeled control error above.
- base.Fatalf("unexpected labeled break/continue: %v", n)
- }
-
case ir.OIF:
n := n.(*ir.IfStmt)
if ir.IsConst(n.Cond, constant.Bool) {
diff --git a/test/inline.go b/test/inline.go
index a73c0ba7b1..599d5233e0 100644
--- a/test/inline.go
+++ b/test/inline.go
@@ -135,8 +135,7 @@ func s1(x int) int { // ERROR "can inline s1"
return foo() // ERROR "inlining call to s1.func1"
}
-// can't currently inline functions with a break statement
-func switchBreak(x, y int) int {
+func switchBreak(x, y int) int { // ERROR "can inline switchBreak"
var n int
switch x {
case 0:
@@ -218,8 +217,7 @@ func for1(fn func() bool) { // ERROR "can inline for1" "fn does not escape"
}
}
-// BAD: for2 should be inlineable too.
-func for2(fn func() bool) { // ERROR "fn does not escape"
+func for2(fn func() bool) { // ERROR "can inline for2" "fn does not escape"
Loop:
for {
if fn() {
From c091767d87b7a6ef6016286bc0fae8add59b92de Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Fri, 15 Oct 2021 13:52:05 -0700
Subject: [PATCH 046/406] cmd/asm: report an error when trying to do spectre on
386
The compiler refuses to do spectre mitigation on 386, but the
assembler doesn't. Fix that.
Fixes #49006
Change-Id: I887b6f7ed7523a47f463706f06ca4c2c6e828b6b
Reviewed-on: https://go-review.googlesource.com/c/go/+/356190
Trust: Keith Randall
Run-TryBot: Keith Randall
TryBot-Result: Go Bot
Reviewed-by: Bryan C. Mills
---
src/cmd/internal/obj/x86/asm6.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go
index 43ce832b17..6555756fd3 100644
--- a/src/cmd/internal/obj/x86/asm6.go
+++ b/src/cmd/internal/obj/x86/asm6.go
@@ -2035,6 +2035,11 @@ type nopPad struct {
}
func span6(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
+ if ctxt.Retpoline && ctxt.Arch.Family == sys.I386 {
+ ctxt.Diag("-spectre=ret not supported on 386")
+ ctxt.Retpoline = false // don't keep printing
+ }
+
pjc := makePjcCtx(ctxt)
if s.P != nil {
From 3e5cc4d6f6befb284d7b2a5142a8b576bf5970ea Mon Sep 17 00:00:00 2001
From: wdvxdr
Date: Fri, 8 Oct 2021 16:06:42 +0800
Subject: [PATCH 047/406] cmd/compile: use MOVBE instruction for GOAMD64>=v3
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
encoding/binary benchmark on my laptop:
name old time/op new time/op delta
ReadSlice1000Int32s-8 4.42µs ± 5% 4.20µs ± 1% -4.94% (p=0.046 n=9+8)
ReadStruct-8 359ns ± 8% 368ns ± 5% +2.35% (p=0.041 n=9+10)
WriteStruct-8 349ns ± 1% 357ns ± 1% +2.15% (p=0.000 n=8+10)
ReadInts-8 235ns ± 1% 233ns ± 1% -1.01% (p=0.005 n=10+10)
WriteInts-8 265ns ± 1% 274ns ± 1% +3.45% (p=0.000 n=10+10)
WriteSlice1000Int32s-8 4.61µs ± 5% 4.59µs ± 5% ~ (p=0.986 n=10+10)
PutUint16-8 0.56ns ± 4% 0.57ns ± 4% ~ (p=0.101 n=10+10)
PutUint32-8 0.83ns ± 2% 0.56ns ± 6% -32.91% (p=0.000 n=10+10)
PutUint64-8 0.81ns ± 3% 0.62ns ± 4% -23.82% (p=0.000 n=10+10)
LittleEndianPutUint16-8 0.55ns ± 4% 0.55ns ± 3% ~ (p=0.926 n=10+10)
LittleEndianPutUint32-8 0.41ns ± 4% 0.42ns ± 3% ~ (p=0.148 n=10+9)
LittleEndianPutUint64-8 0.55ns ± 2% 0.56ns ± 6% ~ (p=0.897 n=10+10)
ReadFloats-8 60.4ns ± 4% 59.0ns ± 1% -2.25% (p=0.007 n=10+10)
WriteFloats-8 72.3ns ± 2% 71.5ns ± 7% ~ (p=0.089 n=10+10)
ReadSlice1000Float32s-8 4.21µs ± 3% 4.18µs ± 2% ~ (p=0.197 n=10+10)
WriteSlice1000Float32s-8 4.61µs ± 2% 4.68µs ± 7% ~ (p=1.000 n=8+10)
ReadSlice1000Uint8s-8 250ns ± 4% 247ns ± 4% ~ (p=0.324 n=10+10)
WriteSlice1000Uint8s-8 227ns ± 5% 229ns ± 2% ~ (p=0.193 n=10+7)
PutUvarint32-8 15.3ns ± 2% 15.4ns ± 4% ~ (p=0.782 n=10+10)
PutUvarint64-8 38.5ns ± 1% 38.6ns ± 5% ~ (p=0.396 n=8+10)
name old speed new speed delta
ReadSlice1000Int32s-8 890MB/s ±17% 953MB/s ± 1% +7.00% (p=0.027 n=10+8)
ReadStruct-8 209MB/s ± 8% 204MB/s ± 5% -2.42% (p=0.043 n=9+10)
WriteStruct-8 214MB/s ± 3% 210MB/s ± 1% -1.75% (p=0.003 n=9+10)
ReadInts-8 127MB/s ± 1% 129MB/s ± 1% +1.01% (p=0.006 n=10+10)
WriteInts-8 113MB/s ± 1% 109MB/s ± 1% -3.34% (p=0.000 n=10+10)
WriteSlice1000Int32s-8 868MB/s ± 5% 872MB/s ± 5% ~ (p=1.000 n=10+10)
PutUint16-8 3.55GB/s ± 4% 3.50GB/s ± 4% ~ (p=0.093 n=10+10)
PutUint32-8 4.83GB/s ± 2% 7.21GB/s ± 6% +49.16% (p=0.000 n=10+10)
PutUint64-8 9.89GB/s ± 3% 12.99GB/s ± 4% +31.26% (p=0.000 n=10+10)
LittleEndianPutUint16-8 3.65GB/s ± 4% 3.65GB/s ± 4% ~ (p=0.912 n=10+10)
LittleEndianPutUint32-8 9.74GB/s ± 3% 9.63GB/s ± 3% ~ (p=0.222 n=9+9)
LittleEndianPutUint64-8 14.4GB/s ± 2% 14.3GB/s ± 5% ~ (p=0.912 n=10+10)
ReadFloats-8 199MB/s ± 4% 203MB/s ± 1% +2.27% (p=0.007 n=10+10)
WriteFloats-8 166MB/s ± 2% 168MB/s ± 7% ~ (p=0.089 n=10+10)
ReadSlice1000Float32s-8 949MB/s ± 3% 958MB/s ± 2% ~ (p=0.218 n=10+10)
WriteSlice1000Float32s-8 867MB/s ± 2% 857MB/s ± 6% ~ (p=1.000 n=8+10)
ReadSlice1000Uint8s-8 4.00GB/s ± 4% 4.06GB/s ± 4% ~ (p=0.353 n=10+10)
WriteSlice1000Uint8s-8 4.40GB/s ± 4% 4.36GB/s ± 2% ~ (p=0.193 n=10+7)
PutUvarint32-8 262MB/s ± 2% 260MB/s ± 4% ~ (p=0.739 n=10+10)
PutUvarint64-8 208MB/s ± 1% 207MB/s ± 5% ~ (p=0.408 n=8+10)
Updates #45453
Change-Id: Ifda0d48d54665cef45d46d3aad974062633142c4
Reviewed-on: https://go-review.googlesource.com/c/go/+/354670
Run-TryBot: Alberto Donizetti
TryBot-Result: Go Bot
Reviewed-by: Keith Randall
Trust: Matthew Dempsky
---
src/cmd/compile/internal/amd64/ssa.go | 7 +-
.../compile/internal/amd64/versions_test.go | 27 +-
src/cmd/compile/internal/ssa/gen/AMD64.rules | 26 ++
src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 6 +
src/cmd/compile/internal/ssa/opGen.go | 64 +++++
src/cmd/compile/internal/ssa/rewriteAMD64.go | 243 ++++++++++++++++++
test/codegen/memcombine.go | 36 ++-
7 files changed, 389 insertions(+), 20 deletions(-)
diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go
index 0e74574422..b0e5c34030 100644
--- a/src/cmd/compile/internal/amd64/ssa.go
+++ b/src/cmd/compile/internal/amd64/ssa.go
@@ -772,7 +772,9 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.From.Val = math.Float64frombits(uint64(v.AuxInt))
p.To.Type = obj.TYPE_REG
p.To.Reg = x
- case ssa.OpAMD64MOVQload, ssa.OpAMD64MOVSSload, ssa.OpAMD64MOVSDload, ssa.OpAMD64MOVLload, ssa.OpAMD64MOVWload, ssa.OpAMD64MOVBload, ssa.OpAMD64MOVBQSXload, ssa.OpAMD64MOVWQSXload, ssa.OpAMD64MOVLQSXload, ssa.OpAMD64MOVOload:
+ case ssa.OpAMD64MOVQload, ssa.OpAMD64MOVLload, ssa.OpAMD64MOVWload, ssa.OpAMD64MOVBload, ssa.OpAMD64MOVOload,
+ ssa.OpAMD64MOVSSload, ssa.OpAMD64MOVSDload, ssa.OpAMD64MOVBQSXload, ssa.OpAMD64MOVWQSXload, ssa.OpAMD64MOVLQSXload,
+ ssa.OpAMD64MOVBEQload, ssa.OpAMD64MOVBELload:
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_MEM
p.From.Reg = v.Args[0].Reg()
@@ -788,7 +790,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Reg = v.Reg()
case ssa.OpAMD64MOVQstore, ssa.OpAMD64MOVSSstore, ssa.OpAMD64MOVSDstore, ssa.OpAMD64MOVLstore, ssa.OpAMD64MOVWstore, ssa.OpAMD64MOVBstore, ssa.OpAMD64MOVOstore,
ssa.OpAMD64ADDQmodify, ssa.OpAMD64SUBQmodify, ssa.OpAMD64ANDQmodify, ssa.OpAMD64ORQmodify, ssa.OpAMD64XORQmodify,
- ssa.OpAMD64ADDLmodify, ssa.OpAMD64SUBLmodify, ssa.OpAMD64ANDLmodify, ssa.OpAMD64ORLmodify, ssa.OpAMD64XORLmodify:
+ ssa.OpAMD64ADDLmodify, ssa.OpAMD64SUBLmodify, ssa.OpAMD64ANDLmodify, ssa.OpAMD64ORLmodify, ssa.OpAMD64XORLmodify,
+ ssa.OpAMD64MOVBEQstore, ssa.OpAMD64MOVBELstore:
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_REG
p.From.Reg = v.Args[1].Reg()
diff --git a/src/cmd/compile/internal/amd64/versions_test.go b/src/cmd/compile/internal/amd64/versions_test.go
index de677f3a69..ee1a8ca3aa 100644
--- a/src/cmd/compile/internal/amd64/versions_test.go
+++ b/src/cmd/compile/internal/amd64/versions_test.go
@@ -53,7 +53,9 @@ func TestGoAMD64v1(t *testing.T) {
opcodes := map[string]bool{}
var features []string
for feature, opcodeList := range featureToOpcodes {
- features = append(features, fmt.Sprintf("cpu.%s=off", feature))
+ if runtimeFeatures[feature] {
+ features = append(features, fmt.Sprintf("cpu.%s=off", feature))
+ }
for _, op := range opcodeList {
opcodes[op] = true
}
@@ -204,14 +206,28 @@ func clobber(t *testing.T, src string, dst *os.File, opcodes map[string]bool) {
f.Close()
}
+func setOf(keys ...string) map[string]bool {
+ m := make(map[string]bool, len(keys))
+ for _, key := range keys {
+ m[key] = true
+ }
+ return m
+}
+
+var runtimeFeatures = setOf(
+ "adx", "aes", "avx", "avx2", "bmi1", "bmi2", "erms", "fma",
+ "pclmulqdq", "popcnt", "rdtscp", "sse3", "sse41", "sse42", "ssse3",
+)
+
var featureToOpcodes = map[string][]string{
// Note: we include *q, *l, and plain opcodes here.
// go tool objdump doesn't include a [QL] on popcnt instructions, until CL 351889
// native objdump doesn't include [QL] on linux.
- "popcnt": []string{"popcntq", "popcntl", "popcnt"},
- "bmi1": []string{"andnq", "andnl", "andn", "blsiq", "blsil", "blsi", "blsmskq", "blsmskl", "blsmsk", "blsrq", "blsrl", "blsr", "tzcntq", "tzcntl", "tzcnt"},
- "sse41": []string{"roundsd"},
- "fma": []string{"vfmadd231sd"},
+ "popcnt": {"popcntq", "popcntl", "popcnt"},
+ "bmi1": {"andnq", "andnl", "andn", "blsiq", "blsil", "blsi", "blsmskq", "blsmskl", "blsmsk", "blsrq", "blsrl", "blsr", "tzcntq", "tzcntl", "tzcnt"},
+ "sse41": {"roundsd"},
+ "fma": {"vfmadd231sd"},
+ "movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"},
}
// Test to use POPCNT instruction, if available
@@ -364,5 +380,4 @@ func TestFMA(t *testing.T) {
t.Errorf("FMA(%f,%f,%f) = %f, want %f", tt.x, tt.y, tt.z, got, tt.want)
}
}
-
}
diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules
index 8b73ee14ea..507d701999 100644
--- a/src/cmd/compile/internal/ssa/gen/AMD64.rules
+++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules
@@ -2219,3 +2219,29 @@
(AND(Q|L) x (ADD(Q|L)const [-1] x)) && buildcfg.GOAMD64 >= 3 => (BLSR(Q|L) x)
(BSWAP(Q|L) (BSWAP(Q|L) p)) => p
+
+// CPUID feature: MOVBE.
+(MOV(Q|L)store [i] {s} p x:(BSWAP(Q|L) w) mem) && x.Uses == 1 && buildcfg.GOAMD64 >= 3 => (MOVBE(Q|L)store [i] {s} p w mem)
+(BSWAP(Q|L) x:(MOV(Q|L)load [i] {s} p mem)) && x.Uses == 1 && buildcfg.GOAMD64 >= 3 => (MOVBE(Q|L)load [i] {s} p mem)
+(BSWAP(Q|L) (MOVBE(Q|L)load [i] {s} p m)) => (MOV(Q|L)load [i] {s} p m)
+(MOVBE(Q|L)store [i] {s} p (BSWAP(Q|L) x) m) => (MOV(Q|L)store [i] {s} p x m)
+
+(ORQ x0:(MOVBELload [i0] {s} p mem)
+ sh:(SHLQconst [32] x1:(MOVBELload [i1] {s} p mem)))
+ && i0 == i1+4
+ && x0.Uses == 1
+ && x1.Uses == 1
+ && sh.Uses == 1
+ && mergePoint(b,x0,x1) != nil
+ && clobber(x0, x1, sh)
+ => @mergePoint(b,x0,x1) (MOVBEQload [i1] {s} p mem)
+
+(ORQ x0:(MOVBELload [i] {s} p0 mem)
+ sh:(SHLQconst [32] x1:(MOVBELload [i] {s} p1 mem)))
+ && x0.Uses == 1
+ && x1.Uses == 1
+ && sh.Uses == 1
+ && sequentialAddresses(p1, p0, 4)
+ && mergePoint(b,x0,x1) != nil
+ && clobber(x0, x1, sh)
+ => @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p0 mem)
diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
index 731454c761..e3c94e4b2e 100644
--- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
+++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
@@ -922,6 +922,12 @@ func init() {
// and BSFQ(0) is undefined. Same for TZCNTL(0)==32
{name: "TZCNTQ", argLength: 1, reg: gp11, asm: "TZCNTQ", clobberFlags: true},
{name: "TZCNTL", argLength: 1, reg: gp11, asm: "TZCNTL", clobberFlags: true},
+
+ // CPUID feature: MOVBE
+ {name: "MOVBELload", argLength: 2, reg: gpload, asm: "MOVBEL", aux: "SymOff", typ: "UInt32", faultOnNilArg0: true, symEffect: "Read"}, // load and swap 4 bytes from arg0+auxint+aux. arg1=mem. Zero extend.
+ {name: "MOVBELstore", argLength: 3, reg: gpstore, asm: "MOVBEL", aux: "SymOff", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // swap and store 4 bytes in arg1 to arg0+auxint+aux. arg2=mem
+ {name: "MOVBEQload", argLength: 2, reg: gpload, asm: "MOVBEQ", aux: "SymOff", typ: "UInt64", faultOnNilArg0: true, symEffect: "Read"}, // load and swap 8 bytes from arg0+auxint+aux. arg1=mem
+ {name: "MOVBEQstore", argLength: 3, reg: gpstore, asm: "MOVBEQ", aux: "SymOff", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // swap and store 8 bytes in arg1 to arg0+auxint+aux. arg2=mem
}
var AMD64blocks = []blockData{
diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go
index 640e517fe7..091f43f40a 100644
--- a/src/cmd/compile/internal/ssa/opGen.go
+++ b/src/cmd/compile/internal/ssa/opGen.go
@@ -1043,6 +1043,10 @@ const (
OpAMD64BLSRL
OpAMD64TZCNTQ
OpAMD64TZCNTL
+ OpAMD64MOVBELload
+ OpAMD64MOVBELstore
+ OpAMD64MOVBEQload
+ OpAMD64MOVBEQstore
OpARMADD
OpARMADDconst
@@ -13780,6 +13784,66 @@ var opcodeTable = [...]opInfo{
},
},
},
+ {
+ name: "MOVBELload",
+ auxType: auxSymOff,
+ argLen: 2,
+ faultOnNilArg0: true,
+ symEffect: SymRead,
+ asm: x86.AMOVBEL,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ outputs: []outputInfo{
+ {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
+ },
+ },
+ },
+ {
+ name: "MOVBELstore",
+ auxType: auxSymOff,
+ argLen: 3,
+ faultOnNilArg0: true,
+ symEffect: SymWrite,
+ asm: x86.AMOVBEL,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ },
+ },
+ {
+ name: "MOVBEQload",
+ auxType: auxSymOff,
+ argLen: 2,
+ faultOnNilArg0: true,
+ symEffect: SymRead,
+ asm: x86.AMOVBEQ,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ outputs: []outputInfo{
+ {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
+ },
+ },
+ },
+ {
+ name: "MOVBEQstore",
+ auxType: auxSymOff,
+ argLen: 3,
+ faultOnNilArg0: true,
+ symEffect: SymWrite,
+ asm: x86.AMOVBEQ,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ },
+ },
{
name: "ADD",
diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go
index 201fbf2954..88b545a465 100644
--- a/src/cmd/compile/internal/ssa/rewriteAMD64.go
+++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go
@@ -222,6 +222,10 @@ func rewriteValueAMD64(v *Value) bool {
return rewriteValueAMD64_OpAMD64LEAQ4(v)
case OpAMD64LEAQ8:
return rewriteValueAMD64_OpAMD64LEAQ8(v)
+ case OpAMD64MOVBELstore:
+ return rewriteValueAMD64_OpAMD64MOVBELstore(v)
+ case OpAMD64MOVBEQstore:
+ return rewriteValueAMD64_OpAMD64MOVBEQstore(v)
case OpAMD64MOVBQSX:
return rewriteValueAMD64_OpAMD64MOVBQSX(v)
case OpAMD64MOVBQSXload:
@@ -3623,6 +3627,43 @@ func rewriteValueAMD64_OpAMD64BSWAPL(v *Value) bool {
v.copyOf(p)
return true
}
+ // match: (BSWAPL x:(MOVLload [i] {s} p mem))
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBELload [i] {s} p mem)
+ for {
+ x := v_0
+ if x.Op != OpAMD64MOVLload {
+ break
+ }
+ i := auxIntToInt32(x.AuxInt)
+ s := auxToSym(x.Aux)
+ mem := x.Args[1]
+ p := x.Args[0]
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBELload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, mem)
+ return true
+ }
+ // match: (BSWAPL (MOVBELload [i] {s} p m))
+ // result: (MOVLload [i] {s} p m)
+ for {
+ if v_0.Op != OpAMD64MOVBELload {
+ break
+ }
+ i := auxIntToInt32(v_0.AuxInt)
+ s := auxToSym(v_0.Aux)
+ m := v_0.Args[1]
+ p := v_0.Args[0]
+ v.reset(OpAMD64MOVLload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, m)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64BSWAPQ(v *Value) bool {
@@ -3637,6 +3678,43 @@ func rewriteValueAMD64_OpAMD64BSWAPQ(v *Value) bool {
v.copyOf(p)
return true
}
+ // match: (BSWAPQ x:(MOVQload [i] {s} p mem))
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBEQload [i] {s} p mem)
+ for {
+ x := v_0
+ if x.Op != OpAMD64MOVQload {
+ break
+ }
+ i := auxIntToInt32(x.AuxInt)
+ s := auxToSym(x.Aux)
+ mem := x.Args[1]
+ p := x.Args[0]
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBEQload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, mem)
+ return true
+ }
+ // match: (BSWAPQ (MOVBEQload [i] {s} p m))
+ // result: (MOVQload [i] {s} p m)
+ for {
+ if v_0.Op != OpAMD64MOVBEQload {
+ break
+ }
+ i := auxIntToInt32(v_0.AuxInt)
+ s := auxToSym(v_0.Aux)
+ m := v_0.Args[1]
+ p := v_0.Args[0]
+ v.reset(OpAMD64MOVQload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, m)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64BTCLconst(v *Value) bool {
@@ -9395,6 +9473,52 @@ func rewriteValueAMD64_OpAMD64LEAQ8(v *Value) bool {
}
return false
}
+func rewriteValueAMD64_OpAMD64MOVBELstore(v *Value) bool {
+ v_2 := v.Args[2]
+ v_1 := v.Args[1]
+ v_0 := v.Args[0]
+ // match: (MOVBELstore [i] {s} p (BSWAPL x) m)
+ // result: (MOVLstore [i] {s} p x m)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ if v_1.Op != OpAMD64BSWAPL {
+ break
+ }
+ x := v_1.Args[0]
+ m := v_2
+ v.reset(OpAMD64MOVLstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, x, m)
+ return true
+ }
+ return false
+}
+func rewriteValueAMD64_OpAMD64MOVBEQstore(v *Value) bool {
+ v_2 := v.Args[2]
+ v_1 := v.Args[1]
+ v_0 := v.Args[0]
+ // match: (MOVBEQstore [i] {s} p (BSWAPQ x) m)
+ // result: (MOVQstore [i] {s} p x m)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ if v_1.Op != OpAMD64BSWAPQ {
+ break
+ }
+ x := v_1.Args[0]
+ m := v_2
+ v.reset(OpAMD64MOVQstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, x, m)
+ return true
+ }
+ return false
+}
func rewriteValueAMD64_OpAMD64MOVBQSX(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
@@ -12225,6 +12349,28 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool {
v.AddArg3(ptr, val, mem)
return true
}
+ // match: (MOVLstore [i] {s} p x:(BSWAPL w) mem)
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBELstore [i] {s} p w mem)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ x := v_1
+ if x.Op != OpAMD64BSWAPL {
+ break
+ }
+ w := x.Args[0]
+ mem := v_2
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBELstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, w, mem)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64MOVLstoreconst(v *Value) bool {
@@ -13164,6 +13310,28 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool {
v.AddArg3(ptr, val, mem)
return true
}
+ // match: (MOVQstore [i] {s} p x:(BSWAPQ w) mem)
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBEQstore [i] {s} p w mem)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ x := v_1
+ if x.Op != OpAMD64BSWAPQ {
+ break
+ }
+ w := x.Args[0]
+ mem := v_2
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBEQstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, w, mem)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64MOVQstoreconst(v *Value) bool {
@@ -18657,6 +18825,81 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value) bool {
}
break
}
+ // match: (ORQ x0:(MOVBELload [i0] {s} p mem) sh:(SHLQconst [32] x1:(MOVBELload [i1] {s} p mem)))
+ // cond: i0 == i1+4 && x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && mergePoint(b,x0,x1) != nil && clobber(x0, x1, sh)
+ // result: @mergePoint(b,x0,x1) (MOVBEQload [i1] {s} p mem)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ x0 := v_0
+ if x0.Op != OpAMD64MOVBELload {
+ continue
+ }
+ i0 := auxIntToInt32(x0.AuxInt)
+ s := auxToSym(x0.Aux)
+ mem := x0.Args[1]
+ p := x0.Args[0]
+ sh := v_1
+ if sh.Op != OpAMD64SHLQconst || auxIntToInt8(sh.AuxInt) != 32 {
+ continue
+ }
+ x1 := sh.Args[0]
+ if x1.Op != OpAMD64MOVBELload {
+ continue
+ }
+ i1 := auxIntToInt32(x1.AuxInt)
+ if auxToSym(x1.Aux) != s {
+ continue
+ }
+ _ = x1.Args[1]
+ if p != x1.Args[0] || mem != x1.Args[1] || !(i0 == i1+4 && x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && mergePoint(b, x0, x1) != nil && clobber(x0, x1, sh)) {
+ continue
+ }
+ b = mergePoint(b, x0, x1)
+ v0 := b.NewValue0(x1.Pos, OpAMD64MOVBEQload, typ.UInt64)
+ v.copyOf(v0)
+ v0.AuxInt = int32ToAuxInt(i1)
+ v0.Aux = symToAux(s)
+ v0.AddArg2(p, mem)
+ return true
+ }
+ break
+ }
+ // match: (ORQ x0:(MOVBELload [i] {s} p0 mem) sh:(SHLQconst [32] x1:(MOVBELload [i] {s} p1 mem)))
+ // cond: x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && sequentialAddresses(p1, p0, 4) && mergePoint(b,x0,x1) != nil && clobber(x0, x1, sh)
+ // result: @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p0 mem)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ x0 := v_0
+ if x0.Op != OpAMD64MOVBELload {
+ continue
+ }
+ i := auxIntToInt32(x0.AuxInt)
+ s := auxToSym(x0.Aux)
+ mem := x0.Args[1]
+ p0 := x0.Args[0]
+ sh := v_1
+ if sh.Op != OpAMD64SHLQconst || auxIntToInt8(sh.AuxInt) != 32 {
+ continue
+ }
+ x1 := sh.Args[0]
+ if x1.Op != OpAMD64MOVBELload || auxIntToInt32(x1.AuxInt) != i || auxToSym(x1.Aux) != s {
+ continue
+ }
+ _ = x1.Args[1]
+ p1 := x1.Args[0]
+ if mem != x1.Args[1] || !(x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && sequentialAddresses(p1, p0, 4) && mergePoint(b, x0, x1) != nil && clobber(x0, x1, sh)) {
+ continue
+ }
+ b = mergePoint(b, x0, x1)
+ v0 := b.NewValue0(x1.Pos, OpAMD64MOVBEQload, typ.UInt64)
+ v.copyOf(v0)
+ v0.AuxInt = int32ToAuxInt(i)
+ v0.Aux = symToAux(s)
+ v0.AddArg2(p0, mem)
+ return true
+ }
+ break
+ }
return false
}
func rewriteValueAMD64_OpAMD64ORQconst(v *Value) bool {
diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go
index 2a0c534df0..97e1d4bdfb 100644
--- a/test/codegen/memcombine.go
+++ b/test/codegen/memcombine.go
@@ -70,7 +70,8 @@ func load_le16_idx(b []byte, idx int) {
}
func load_be64(b []byte) {
- // amd64:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v3:`MOVBEQ`
// s390x:`MOVD\s\(.*\),`
// arm64:`REV`,`MOVD\s\(R[0-9]+\),`,-`MOV[BHW]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`,-`MOV[BHW]Z`
@@ -78,7 +79,8 @@ func load_be64(b []byte) {
}
func load_be64_idx(b []byte, idx int) {
- // amd64:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v3: `MOVBEQ`
// s390x:`MOVD\s\(.*\)\(.*\*1\),`
// arm64:`REV`,`MOVD\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[WHB]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`,-`MOV[BHW]Z`
@@ -86,7 +88,8 @@ func load_be64_idx(b []byte, idx int) {
}
func load_be32(b []byte) {
- // amd64:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v3: `MOVBEL`
// s390x:`MOVWZ\s\(.*\),`
// arm64:`REVW`,`MOVWU\s\(R[0-9]+\),`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`,-`MOV[BH]Z`
@@ -94,7 +97,8 @@ func load_be32(b []byte) {
}
func load_be32_idx(b []byte, idx int) {
- // amd64:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v3: `MOVBEL`
// s390x:`MOVWZ\s\(.*\)\(.*\*1\),`
// arm64:`REVW`,`MOVWU\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[HB]`,-`REV16W`
// ppc64le:`MOVWBR`,-`MOV[BH]Z`
@@ -179,7 +183,8 @@ func load_be_byte4_uint32(s []byte) uint32 {
func load_be_byte4_uint32_inv(s []byte) uint32 {
// arm64:`MOVWU\t\(R[0-9]+\)`,`REVW`,-`ORR`,-`REV16W`,-`MOV[BH]`
- // amd64:`MOVL\s\([A-Z]+\)`,`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v1,amd64/v2:`MOVL\s\([A-Z]+\)`,`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v3: `MOVBEL`
return uint32(s[3]) | uint32(s[2])<<8 | uint32(s[1])<<16 | uint32(s[0])<<24
}
@@ -191,7 +196,8 @@ func load_be_byte8_uint64(s []byte) uint64 {
func load_be_byte8_uint64_inv(s []byte) uint64 {
// arm64:`MOVD\t\(R[0-9]+\)`,`REV`,-`ORR`,-`REVW`,-`REV16W`,-`MOV[BHW]`
- // amd64:`MOVQ\s\([A-Z]+\),\s[A-Z]+`,`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v1,amd64/v2:`MOVQ\s\([A-Z]+\),\s[A-Z]+`,`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v3: `MOVBEQ`
// ppc64le:`MOVDBR\t\(R[0-9]+\)`,-`MOV[BHW]Z`
return uint64(s[7]) | uint64(s[6])<<8 | uint64(s[5])<<16 | uint64(s[4])<<24 | uint64(s[3])<<32 | uint64(s[2])<<40 | uint64(s[1])<<48 | uint64(s[0])<<56
}
@@ -409,7 +415,8 @@ func store_le16_idx(b []byte, idx int) {
}
func store_be64(b []byte) {
- // amd64:`BSWAPQ`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`SHR.`
+ // amd64/v3: `MOVBEQ`
// arm64:`MOVD`,`REV`,-`MOV[WBH]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`
// s390x:`MOVD\s.*\(.*\)$`,-`SRW\s`,-`SRD\s`
@@ -417,7 +424,8 @@ func store_be64(b []byte) {
}
func store_be64_idx(b []byte, idx int) {
- // amd64:`BSWAPQ`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`SHR.`
+ // amd64/v3:`MOVBEQ`
// arm64:`REV`,`MOVD\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BHW]`,-`REV16W`,-`REVW`
// ppc64le:`MOVDBR`
// s390x:`MOVD\s.*\(.*\)\(.*\*1\)$`,-`SRW\s`,-`SRD\s`
@@ -425,7 +433,8 @@ func store_be64_idx(b []byte, idx int) {
}
func store_be32(b []byte) {
- // amd64:`BSWAPL`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.`
+ // amd64/v3:`MOVBEL`
// arm64:`MOVW`,`REVW`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`
// s390x:`MOVW\s.*\(.*\)$`,-`SRW\s`,-`SRD\s`
@@ -445,7 +454,8 @@ func store_be32_load(b, x *[8]byte) {
}
func store_be32_idx(b []byte, idx int) {
- // amd64:`BSWAPL`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.`
+ // amd64/v3:`MOVBEL`
// arm64:`REVW`,`MOVW\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`
// s390x:`MOVW\s.*\(.*\)\(.*\*1\)$`,-`SRW\s`,-`SRD\s`
@@ -508,14 +518,16 @@ func store_be_byte_2(b []byte, val uint16) {
func store_be_byte_4(b []byte, val uint32) {
_ = b[4]
// arm64:`REVW`,`MOVW\sR[0-9]+,\s1\(R[0-9]+\)`,-`MOVB`,-`MOVH`,-`REV16W`
- // amd64:`MOVL\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`
+ // amd64/v1,amd64/v2:`MOVL\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`
+ // amd64/v3:`MOVBEL\s[A-Z]+,\s1\([A-Z]+\)`
b[1], b[2], b[3], b[4] = byte(val>>24), byte(val>>16), byte(val>>8), byte(val)
}
func store_be_byte_8(b []byte, val uint64) {
_ = b[8]
// arm64:`REV`,`MOVD\sR[0-9]+,\s1\(R[0-9]+\)`,-`MOVB`,-`MOVH`,-`MOVW`,-`REV16W`,-`REVW`
- // amd64:`MOVQ\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`,-`MOVL`
+ // amd64/v1,amd64/v2:`MOVQ\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`,-`MOVL`
+ // amd64/v3:`MOVBEQ\s[A-Z]+,\s1\([A-Z]+\)`, -`MOVBEL`
b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8] = byte(val>>56), byte(val>>48), byte(val>>40), byte(val>>32), byte(val>>24), byte(val>>16), byte(val>>8), byte(val)
}
From 417100ec1b08bb1046e207fd08de105944668d27 Mon Sep 17 00:00:00 2001
From: Katie Hockman
Date: Mon, 18 Oct 2021 10:24:00 -0400
Subject: [PATCH 048/406] cmd/go: fix broken fuzz test
Fixes test breakage caused by CL 355691.
Change-Id: I85fcb1491dc39c45342f4cae91fdfda6aedecd1a
Reviewed-on: https://go-review.googlesource.com/c/go/+/356530
Trust: Katie Hockman
Run-TryBot: Katie Hockman
TryBot-Result: Go Bot
Reviewed-by: Bryan C. Mills
---
.../go/testdata/script/test_fuzz_minimize.txt | 52 -----------------
.../script/test_fuzz_minimize_interesting.txt | 56 ++++++++++++++++++-
2 files changed, 53 insertions(+), 55 deletions(-)
diff --git a/src/cmd/go/testdata/script/test_fuzz_minimize.txt b/src/cmd/go/testdata/script/test_fuzz_minimize.txt
index 56abc68104..8b11621bbd 100644
--- a/src/cmd/go/testdata/script/test_fuzz_minimize.txt
+++ b/src/cmd/go/testdata/script/test_fuzz_minimize.txt
@@ -34,21 +34,6 @@ stdout FAIL
stdout 'there was an Error'
stdout FAIL
-# Test that minimization occurs for a crash that appears while minimizing a
-# newly found interesting input. There must be only one worker for this test to
-# be flaky like we want.
-! go test -fuzz=FuzzMinimizerCrashInMinimization -run=FuzzMinimizerCrashInMinimization -fuzztime=10000x -parallel=1 .
-! stdout '^ok'
-stdout 'got the minimum size!'
-stdout 'flaky failure'
-stdout FAIL
-
-# Make sure the crash that was written will fail when run with go test
-! go test -run=FuzzMinimizerCrashInMinimization .
-
-# Clear testdata.
-rm testdata
-
# Test that minimization is working for recoverable errors.
! go test -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -fuzztime=10000x .
! stdout '^ok'
@@ -97,25 +82,10 @@ stdout FAIL
module example.com/y
go 1.16
--- y.go --
-package y
-
-import (
- "bytes"
- "io"
-)
-
-func Y(w io.Writer, b []byte) {
- if !bytes.Equal(b, []byte("y")) {
- w.Write([]byte("not equal"))
- }
-}
-- y_test.go --
package y
import (
- "bytes"
- "io"
"os"
"testing"
)
@@ -161,28 +131,6 @@ func FuzzMinimizerNonrecoverable(f *testing.F) {
os.Exit(99)
})
}
-
-func FuzzMinimizerCrashInMinimization(f *testing.F) {
- seed := make([]byte, 1000)
- f.Add(seed)
- f.Fuzz(func(t *testing.T, b []byte) {
- if len(b) < 50 || len(b) > 1100 {
- // Make sure that b is large enough that it can be minimized
- return
- }
- if !bytes.Equal(b, seed) {
- // This should have hit a new edge, and the interesting input
- // should be attempting minimization
- Y(io.Discard, b)
- }
- if len(b) < 350 {
- t.Error("flaky failure")
- }
- if len(b) == 50 {
- t.Log("got the minimum size!")
- }
- })
-}
-- empty/empty.go --
package empty
-- check_testdata/check_testdata.go --
diff --git a/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt b/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
index 5e1d90d8d9..fc66201eb3 100644
--- a/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
+++ b/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
@@ -18,22 +18,72 @@
go test -c -fuzz=. # Build using shared build cache for speed.
env GOCACHE=$WORK/gocache
-exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=. -test.fuzztime=1000x
-go run check_cache.go $GOCACHE/fuzz/FuzzMin
+exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinCache -test.fuzztime=1000x
+go run check_cache.go $GOCACHE/fuzz/FuzzMinCache
+
+# Test that minimization occurs for a crash that appears while minimizing a
+# newly found interesting input. There must be only one worker for this test to
+# be flaky like we want.
+go test -c -fuzz=. # Build using shared build cache for speed.
+env GOCACHE=$WORK/gocache
+! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.fuzztime=10000x -test.parallel=1
+! stdout '^ok'
+stdout 'got the minimum size!'
+stdout 'flaky failure'
+stdout FAIL
+
+# Make sure the crash that was written will fail when run with go test
+! go test -run=FuzzMinimizerCrashInMinimization .
-- go.mod --
module fuzz
go 1.17
+-- y.go --
+package fuzz
+
+import (
+ "bytes"
+ "io"
+)
+
+func Y(w io.Writer, b []byte) {
+ if !bytes.Equal(b, []byte("y")) {
+ w.Write([]byte("not equal"))
+ }
+}
-- fuzz_test.go --
package fuzz
import (
"bytes"
+ "io"
"testing"
)
-func FuzzMin(f *testing.F) {
+func FuzzMinimizerCrashInMinimization(f *testing.F) {
+ seed := make([]byte, 1000)
+ f.Add(seed)
+ f.Fuzz(func(t *testing.T, b []byte) {
+ if len(b) < 50 || len(b) > 1100 {
+ // Make sure that b is large enough that it can be minimized
+ return
+ }
+ if !bytes.Equal(b, seed) {
+ // This should have hit a new edge, and the interesting input
+ // should be attempting minimization
+ Y(io.Discard, b)
+ }
+ if len(b) < 350 {
+ t.Error("flaky failure")
+ }
+ if len(b) == 50 {
+ t.Log("got the minimum size!")
+ }
+ })
+}
+
+func FuzzMinCache(f *testing.F) {
seed := bytes.Repeat([]byte("a"), 20)
f.Add(seed)
f.Fuzz(func(t *testing.T, buf []byte) {
From 42515418a99f2540427a5d32189f6d44fb06939e Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Sat, 18 Sep 2021 10:24:31 +0700
Subject: [PATCH 049/406] reflect: correct documentation of Value.UnsafeAddr
The doc mentions that "UnsafeAddr returns a _pointer_ to v's data", but
it returns a uintptr instead, which don't have pointer semantic.
Change-Id: I557d5597cbc485356ca803eb496a99d6db8c63ba
Reviewed-on: https://go-review.googlesource.com/c/go/+/350690
Trust: Cuong Manh Le
Run-TryBot: Cuong Manh Le
TryBot-Result: Go Bot
Reviewed-by: Ian Lance Taylor
Reviewed-by: Matthew Dempsky
---
src/reflect/value.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 449f3bbb3c..6bc02c1c8c 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -2473,7 +2473,7 @@ func (v Value) Uint() uint64 {
// which ensures cmd/compile can recognize unsafe.Pointer(v.UnsafeAddr())
// and make an exception.
-// UnsafeAddr returns a pointer to v's data.
+// UnsafeAddr returns a pointer to v's data, as a uintptr.
// It is for advanced clients that also import the "unsafe" package.
// It panics if v is not addressable.
func (v Value) UnsafeAddr() uintptr {
From 543a513304c35d42b162790a6e70191aac68225c Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Sun, 17 Oct 2021 01:18:20 +0700
Subject: [PATCH 050/406] reflect: add test that method values have the same
code pointers
Updates #40592
Change-Id: I16252dd57aceb5c49ddc11d8c12c601ca87ca902
Reviewed-on: https://go-review.googlesource.com/c/go/+/356252
Trust: Cuong Manh Le
Run-TryBot: Cuong Manh Le
TryBot-Result: Go Bot
Reviewed-by: Keith Randall
---
src/reflect/all_test.go | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go
index 8642d60f8b..0370906f7d 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -2511,6 +2511,11 @@ func TestMethodValue(t *testing.T) {
p := Point{3, 4}
var i int64
+ // Check that method value have the same underlying code pointers.
+ if p1, p2 := ValueOf(Point{1, 1}).Method(1), ValueOf(Point{2, 2}).Method(1); p1.Pointer() != p2.Pointer() {
+ t.Errorf("methodValueCall mismatched: %v - %v", p1, p2)
+ }
+
// Curried method of value.
tfunc := TypeOf((func(int) int)(nil))
v := ValueOf(p).Method(1)
From 4d550727f8b85e9f8866f22c8a02b8f56fa64159 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Sat, 18 Sep 2021 10:32:22 +0700
Subject: [PATCH 051/406] reflect: add Value.UnsafePointer
Allowing eliminates a class of possible misuse of unsafe.Pointer, and
allow callers to migrate from Value.Addr and Value.Pointer, thus they
can be now deprecated.
Fixes #40592
Change-Id: I798e507c748922cac5cc1c1971c1b2cc7095a068
Reviewed-on: https://go-review.googlesource.com/c/go/+/350691
Trust: Cuong Manh Le
Run-TryBot: Cuong Manh Le
TryBot-Result: Go Bot
Reviewed-by: Keith Randall
Reviewed-by: Ian Lance Taylor
---
src/reflect/all_test.go | 22 +++++-----
src/reflect/deepequal.go | 6 +--
src/reflect/set_test.go | 6 +--
src/reflect/type.go | 4 +-
src/reflect/value.go | 94 ++++++++++++++++++++++++----------------
5 files changed, 75 insertions(+), 57 deletions(-)
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go
index 0370906f7d..141cc8f73d 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -3223,11 +3223,11 @@ func (*outer) M() {}
func TestNestedMethods(t *testing.T) {
typ := TypeOf((*outer)(nil))
- if typ.NumMethod() != 1 || typ.Method(0).Func.Pointer() != ValueOf((*outer).M).Pointer() {
+ if typ.NumMethod() != 1 || typ.Method(0).Func.UnsafePointer() != ValueOf((*outer).M).UnsafePointer() {
t.Errorf("Wrong method table for outer: (M=%p)", (*outer).M)
for i := 0; i < typ.NumMethod(); i++ {
m := typ.Method(i)
- t.Errorf("\t%d: %s %#x\n", i, m.Name, m.Func.Pointer())
+ t.Errorf("\t%d: %s %p\n", i, m.Name, m.Func.UnsafePointer())
}
}
}
@@ -3266,11 +3266,11 @@ func (i *InnerInt) M() int {
func TestEmbeddedMethods(t *testing.T) {
typ := TypeOf((*OuterInt)(nil))
- if typ.NumMethod() != 1 || typ.Method(0).Func.Pointer() != ValueOf((*OuterInt).M).Pointer() {
+ if typ.NumMethod() != 1 || typ.Method(0).Func.UnsafePointer() != ValueOf((*OuterInt).M).UnsafePointer() {
t.Errorf("Wrong method table for OuterInt: (m=%p)", (*OuterInt).M)
for i := 0; i < typ.NumMethod(); i++ {
m := typ.Method(i)
- t.Errorf("\t%d: %s %#x\n", i, m.Name, m.Func.Pointer())
+ t.Errorf("\t%d: %s %p\n", i, m.Name, m.Func.UnsafePointer())
}
}
@@ -3528,11 +3528,11 @@ func TestSlice(t *testing.T) {
rv := ValueOf(&xs).Elem()
rv = rv.Slice(3, 4)
- ptr2 := rv.Pointer()
+ ptr2 := rv.UnsafePointer()
rv = rv.Slice(5, 5)
- ptr3 := rv.Pointer()
+ ptr3 := rv.UnsafePointer()
if ptr3 != ptr2 {
- t.Errorf("xs.Slice(3,4).Slice3(5,5).Pointer() = %#x, want %#x", ptr3, ptr2)
+ t.Errorf("xs.Slice(3,4).Slice3(5,5).UnsafePointer() = %p, want %p", ptr3, ptr2)
}
}
@@ -3575,11 +3575,11 @@ func TestSlice3(t *testing.T) {
rv = ValueOf(&xs).Elem()
rv = rv.Slice3(3, 5, 7)
- ptr2 := rv.Pointer()
+ ptr2 := rv.UnsafePointer()
rv = rv.Slice3(4, 4, 4)
- ptr3 := rv.Pointer()
+ ptr3 := rv.UnsafePointer()
if ptr3 != ptr2 {
- t.Errorf("xs.Slice3(3,5,7).Slice3(4,4,4).Pointer() = %#x, want %#x", ptr3, ptr2)
+ t.Errorf("xs.Slice3(3,5,7).Slice3(4,4,4).UnsafePointer() = %p, want %p", ptr3, ptr2)
}
}
@@ -6818,7 +6818,7 @@ func verifyGCBitsSlice(t *testing.T, typ Type, cap int, bits []byte) {
// repeat a bitmap for a small array or executing a repeat in
// a GC program.
val := MakeSlice(typ, 0, cap)
- data := NewAt(ArrayOf(cap, typ), unsafe.Pointer(val.Pointer()))
+ data := NewAt(ArrayOf(cap, typ), val.UnsafePointer())
heapBits := GCBits(data.Interface())
// Repeat the bitmap for the slice size, trimming scalars in
// the last element.
diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go
index 94174dec04..7f1ecb2809 100644
--- a/src/reflect/deepequal.go
+++ b/src/reflect/deepequal.go
@@ -102,7 +102,7 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
if v1.Len() != v2.Len() {
return false
}
- if v1.Pointer() == v2.Pointer() {
+ if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
// Special case for []byte, which is common.
@@ -121,7 +121,7 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
case Ptr:
- if v1.Pointer() == v2.Pointer() {
+ if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
@@ -139,7 +139,7 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {
if v1.Len() != v2.Len() {
return false
}
- if v1.Pointer() == v2.Pointer() {
+ if v1.UnsafePointer() == v2.UnsafePointer() {
return true
}
for _, k := range v1.MapKeys() {
diff --git a/src/reflect/set_test.go b/src/reflect/set_test.go
index a633e6eee2..566dc7fb65 100644
--- a/src/reflect/set_test.go
+++ b/src/reflect/set_test.go
@@ -79,7 +79,7 @@ func TestImplicitMapConversion(t *testing.T) {
if x != b2 {
t.Errorf("#5 after SetMapIndex(b1, b2): %p (!= %p), %t (map=%v)", x, b2, ok, m)
}
- if p := mv.MapIndex(ValueOf(b1)).Elem().Pointer(); p != uintptr(unsafe.Pointer(b2)) {
+ if p := mv.MapIndex(ValueOf(b1)).Elem().UnsafePointer(); p != unsafe.Pointer(b2) {
t.Errorf("#5 MapIndex(b1) = %#x want %p", p, b2)
}
}
@@ -94,7 +94,7 @@ func TestImplicitMapConversion(t *testing.T) {
if x != c2 {
t.Errorf("#6 after SetMapIndex(c1, c2): %p (!= %p), %t (map=%v)", x, c2, ok, m)
}
- if p := mv.MapIndex(ValueOf(c1)).Pointer(); p != ValueOf(c2).Pointer() {
+ if p := mv.MapIndex(ValueOf(c1)).UnsafePointer(); p != ValueOf(c2).UnsafePointer() {
t.Errorf("#6 MapIndex(c1) = %#x want %p", p, c2)
}
}
@@ -110,7 +110,7 @@ func TestImplicitMapConversion(t *testing.T) {
if x != b2 {
t.Errorf("#7 after SetMapIndex(b1, b2): %p (!= %p), %t (map=%v)", x, b2, ok, m)
}
- if p := mv.MapIndex(ValueOf(b1)).Pointer(); p != uintptr(unsafe.Pointer(b2)) {
+ if p := mv.MapIndex(ValueOf(b1)).UnsafePointer(); p != unsafe.Pointer(b2) {
t.Errorf("#7 MapIndex(b1) = %#x want %p", p, b2)
}
}
diff --git a/src/reflect/type.go b/src/reflect/type.go
index afb802e641..96f589ca9c 100644
--- a/src/reflect/type.go
+++ b/src/reflect/type.go
@@ -2666,8 +2666,8 @@ func StructOf(fields []StructField) Type {
{Name: "M", Type: ArrayOf(len(methods), TypeOf(methods[0]))},
}))
- typ = (*structType)(unsafe.Pointer(tt.Elem().Field(0).UnsafeAddr()))
- ut = (*uncommonType)(unsafe.Pointer(tt.Elem().Field(1).UnsafeAddr()))
+ typ = (*structType)(tt.Elem().Field(0).Addr().UnsafePointer())
+ ut = (*uncommonType)(tt.Elem().Field(1).Addr().UnsafePointer())
copy(tt.Elem().Field(2).Slice(0, len(methods)).Interface().([]method), methods)
}
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 6bc02c1c8c..a272714ac9 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -1933,44 +1933,10 @@ func (v Value) OverflowUint(x uint64) bool {
// If v's Kind is Slice, the returned pointer is to the first
// element of the slice. If the slice is nil the returned value
// is 0. If the slice is empty but non-nil the return value is non-zero.
+//
+// Deprecated: use uintptr(Value.UnsafePointer()) to get the equivalent result.
func (v Value) Pointer() uintptr {
- // TODO: deprecate
- k := v.kind()
- switch k {
- case Ptr:
- if v.typ.ptrdata == 0 {
- // Handle pointers to go:notinheap types directly,
- // so we never materialize such pointers as an
- // unsafe.Pointer. (Such pointers are always indirect.)
- // See issue 42076.
- return *(*uintptr)(v.ptr)
- }
- fallthrough
- case Chan, Map, UnsafePointer:
- return uintptr(v.pointer())
- case Func:
- if v.flag&flagMethod != 0 {
- // As the doc comment says, the returned pointer is an
- // underlying code pointer but not necessarily enough to
- // identify a single function uniquely. All method expressions
- // created via reflect have the same underlying code pointer,
- // so their Pointers are equal. The function used here must
- // match the one used in makeMethodValue.
- f := methodValueCall
- return **(**uintptr)(unsafe.Pointer(&f))
- }
- p := v.pointer()
- // Non-nil func value points at data block.
- // First word of data block is actual code.
- if p != nil {
- p = *(*unsafe.Pointer)(p)
- }
- return uintptr(p)
-
- case Slice:
- return (*SliceHeader)(v.ptr).Data
- }
- panic(&ValueError{"reflect.Value.Pointer", v.kind()})
+ return uintptr(v.UnsafePointer())
}
// Recv receives and returns a value from the channel v.
@@ -2476,8 +2442,9 @@ func (v Value) Uint() uint64 {
// UnsafeAddr returns a pointer to v's data, as a uintptr.
// It is for advanced clients that also import the "unsafe" package.
// It panics if v is not addressable.
+//
+// Deprecated: use uintptr(Value.Addr().UnsafePointer()) to get the equivalent result.
func (v Value) UnsafeAddr() uintptr {
- // TODO: deprecate
if v.typ == nil {
panic(&ValueError{"reflect.Value.UnsafeAddr", Invalid})
}
@@ -2487,6 +2454,57 @@ func (v Value) UnsafeAddr() uintptr {
return uintptr(v.ptr)
}
+// UnsafePointer returns v's value as a unsafe.Pointer.
+// It panics if v's Kind is not Chan, Func, Map, Ptr, Slice, or UnsafePointer.
+//
+// If v's Kind is Func, the returned pointer is an underlying
+// code pointer, but not necessarily enough to identify a
+// single function uniquely. The only guarantee is that the
+// result is zero if and only if v is a nil func Value.
+//
+// If v's Kind is Slice, the returned pointer is to the first
+// element of the slice. If the slice is nil the returned value
+// is nil. If the slice is empty but non-nil the return value is non-nil.
+func (v Value) UnsafePointer() unsafe.Pointer {
+ k := v.kind()
+ switch k {
+ case Ptr:
+ if v.typ.ptrdata == 0 {
+ // Since it is a not-in-heap pointer, all pointers to the heap are
+ // forbidden! See comment in Value.Elem and issue #48399.
+ if !verifyNotInHeapPtr(*(*uintptr)(v.ptr)) {
+ panic("reflect: reflect.Value.UnsafePointer on an invalid notinheap pointer")
+ }
+ return *(*unsafe.Pointer)(v.ptr)
+ }
+ fallthrough
+ case Chan, Map, UnsafePointer:
+ return v.pointer()
+ case Func:
+ if v.flag&flagMethod != 0 {
+ // As the doc comment says, the returned pointer is an
+ // underlying code pointer but not necessarily enough to
+ // identify a single function uniquely. All method expressions
+ // created via reflect have the same underlying code pointer,
+ // so their Pointers are equal. The function used here must
+ // match the one used in makeMethodValue.
+ f := methodValueCall
+ return **(**unsafe.Pointer)(unsafe.Pointer(&f))
+ }
+ p := v.pointer()
+ // Non-nil func value points at data block.
+ // First word of data block is actual code.
+ if p != nil {
+ p = *(*unsafe.Pointer)(p)
+ }
+ return p
+
+ case Slice:
+ return (*unsafeheader.Slice)(v.ptr).Data
+ }
+ panic(&ValueError{"reflect.Value.UnsafePointer", v.kind()})
+}
+
// StringHeader is the runtime representation of a string.
// It cannot be used safely or portably and its representation may
// change in a later release.
From 394a1ad2956f0397e83b5f0234ea7e972a307848 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Mon, 18 Oct 2021 10:26:18 -0700
Subject: [PATCH 052/406] cmd/compile: allow importing and exporting of
ODYANMICDOTTYPE[2]
Fixes #49027
Change-Id: I4520b5c754027bfffbc5cd92c9c27002b248c99a
Reviewed-on: https://go-review.googlesource.com/c/go/+/356569
Trust: Keith Randall
Trust: Dan Scales
Run-TryBot: Keith Randall
Reviewed-by: Dan Scales
---
src/cmd/compile/internal/typecheck/iexport.go | 8 ++++++
src/cmd/compile/internal/typecheck/iimport.go | 5 ++++
test/typeparam/issue49027.dir/a.go | 21 ++++++++++++++++
test/typeparam/issue49027.dir/main.go | 25 +++++++++++++++++++
test/typeparam/issue49027.go | 7 ++++++
5 files changed, 66 insertions(+)
create mode 100644 test/typeparam/issue49027.dir/a.go
create mode 100644 test/typeparam/issue49027.dir/main.go
create mode 100644 test/typeparam/issue49027.go
diff --git a/src/cmd/compile/internal/typecheck/iexport.go b/src/cmd/compile/internal/typecheck/iexport.go
index 3c0b8bc319..e3dd10a56b 100644
--- a/src/cmd/compile/internal/typecheck/iexport.go
+++ b/src/cmd/compile/internal/typecheck/iexport.go
@@ -1888,6 +1888,14 @@ func (w *exportWriter) expr(n ir.Node) {
w.expr(n.X)
w.typ(n.Type())
+ case ir.ODYNAMICDOTTYPE, ir.ODYNAMICDOTTYPE2:
+ n := n.(*ir.DynamicTypeAssertExpr)
+ w.op(n.Op())
+ w.pos(n.Pos())
+ w.expr(n.X)
+ w.expr(n.T)
+ w.typ(n.Type())
+
case ir.OINDEX, ir.OINDEXMAP:
n := n.(*ir.IndexExpr)
if go117ExportTypes {
diff --git a/src/cmd/compile/internal/typecheck/iimport.go b/src/cmd/compile/internal/typecheck/iimport.go
index df49d74a40..52236ce837 100644
--- a/src/cmd/compile/internal/typecheck/iimport.go
+++ b/src/cmd/compile/internal/typecheck/iimport.go
@@ -1457,6 +1457,11 @@ func (r *importReader) node() ir.Node {
}
return n
+ case ir.ODYNAMICDOTTYPE, ir.ODYNAMICDOTTYPE2:
+ n := ir.NewDynamicTypeAssertExpr(r.pos(), op, r.expr(), r.expr())
+ n.SetType(r.typ())
+ return n
+
case ir.OINDEX, ir.OINDEXMAP:
n := ir.NewIndexExpr(r.pos(), r.expr(), r.expr())
if go117ExportTypes {
diff --git a/test/typeparam/issue49027.dir/a.go b/test/typeparam/issue49027.dir/a.go
new file mode 100644
index 0000000000..d3ec27deab
--- /dev/null
+++ b/test/typeparam/issue49027.dir/a.go
@@ -0,0 +1,21 @@
+// Copyright 2021 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.
+
+package a
+
+func Conv(v interface{}) string {
+ return conv[string](v)
+}
+
+func conv[T any](v interface{}) T {
+ return v.(T)
+}
+
+func Conv2(v interface{}) (string, bool) {
+ return conv2[string](v)
+}
+func conv2[T any](v interface{}) (T, bool) {
+ x, ok := v.(T)
+ return x, ok
+}
diff --git a/test/typeparam/issue49027.dir/main.go b/test/typeparam/issue49027.dir/main.go
new file mode 100644
index 0000000000..d0dc33d734
--- /dev/null
+++ b/test/typeparam/issue49027.dir/main.go
@@ -0,0 +1,25 @@
+// Copyright 2021 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.
+
+package main
+
+import (
+ "a"
+ "fmt"
+)
+
+func main() {
+ s := "foo"
+ x := a.Conv(s)
+ if x != s {
+ panic(fmt.Sprintf("got %s wanted %s", x, s))
+ }
+ y, ok := a.Conv2(s)
+ if !ok {
+ panic("conversion failed")
+ }
+ if y != s {
+ panic(fmt.Sprintf("got %s wanted %s", y, s))
+ }
+}
diff --git a/test/typeparam/issue49027.go b/test/typeparam/issue49027.go
new file mode 100644
index 0000000000..76930e5e4f
--- /dev/null
+++ b/test/typeparam/issue49027.go
@@ -0,0 +1,7 @@
+// rundir -G=3
+
+// Copyright 2021 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.
+
+package ignored
From 267abbe3ba9dda96a8694e8aad8892e3ba2be60e Mon Sep 17 00:00:00 2001
From: Gusted
Date: Mon, 18 Oct 2021 17:38:22 +0000
Subject: [PATCH 053/406] html/template: remove unused `mode` field on `Tree`
struct
This changes Go, to remove this unused field on the `Tree` struct. Which seems to replaced by the non-private field `Mode`.
Change-Id: I5b384424cf60aa0af36eb8aad1d8db3f99b9838e
GitHub-Last-Rev: 4b033f967b55ff99df8a969ac1c91c358b82b726
GitHub-Pull-Request: golang/go#48028
Reviewed-on: https://go-review.googlesource.com/c/go/+/345789
Trust: Cherry Mui
Run-TryBot: Cherry Mui
TryBot-Result: Go Bot
Reviewed-by: Rob Pike
---
src/text/template/parse/parse.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/text/template/parse/parse.go b/src/text/template/parse/parse.go
index d92bed5d3d..64b29a2e16 100644
--- a/src/text/template/parse/parse.go
+++ b/src/text/template/parse/parse.go
@@ -32,7 +32,6 @@ type Tree struct {
treeSet map[string]*Tree
actionLine int // line of left delim starting action
rangeDepth int
- mode Mode
}
// A mode value is a set of flags (or 0). Modes control parser behavior.
From 6c0daa733192031eab23d09ed6515c4cd959aa92 Mon Sep 17 00:00:00 2001
From: Richard Musiol
Date: Sat, 16 Oct 2021 17:17:21 +0200
Subject: [PATCH 054/406] syscall/js: remove Wrapper interface
This change removes the js.Wrapper interface for performance reasons.
See proposal #44006 for details.
This is a breaking change, but syscall/js is exempt from Go's
compatibility promise.
Fixes #44006
Change-Id: I968cd14b1e61cc72ea9f84240b6bd29e8b8ae673
Reviewed-on: https://go-review.googlesource.com/c/go/+/356430
Trust: Richard Musiol
Reviewed-by: Cherry Mui
Reviewed-by: Emmanuel Odeke
---
src/syscall/js/func.go | 2 --
src/syscall/js/js.go | 17 +++--------------
2 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/src/syscall/js/func.go b/src/syscall/js/func.go
index ab23e5fbfc..dde1e68100 100644
--- a/src/syscall/js/func.go
+++ b/src/syscall/js/func.go
@@ -15,8 +15,6 @@ var (
nextFuncID uint32 = 1
)
-var _ Wrapper = Func{} // Func must implement Wrapper
-
// Func is a wrapped Go function to be called by JavaScript.
type Func struct {
Value // the JavaScript function that invokes the Go function
diff --git a/src/syscall/js/js.go b/src/syscall/js/js.go
index d805d69166..0da0ddfa0f 100644
--- a/src/syscall/js/js.go
+++ b/src/syscall/js/js.go
@@ -28,12 +28,6 @@ type ref uint64
// nanHead are the upper 32 bits of a ref which are set if the value is not encoded as an IEEE 754 number (see above).
const nanHead = 0x7FF80000
-// Wrapper is implemented by types that are backed by a JavaScript value.
-type Wrapper interface {
- // JSValue returns a JavaScript value associated with an object.
- JSValue() Value
-}
-
// Value represents a JavaScript value. The zero value is the JavaScript value "undefined".
// Values can be checked for equality with the Equal method.
type Value struct {
@@ -51,11 +45,6 @@ const (
typeFlagFunction
)
-// JSValue implements Wrapper interface.
-func (v Value) JSValue() Value {
- return v
-}
-
func makeValue(r ref) Value {
var gcPtr *ref
typeFlag := (r >> 32) & 7
@@ -162,10 +151,10 @@ func Global() Value {
// Panics if x is not one of the expected types.
func ValueOf(x interface{}) Value {
switch x := x.(type) {
- case Value: // should precede Wrapper to avoid a loop
+ case Value:
return x
- case Wrapper:
- return x.JSValue()
+ case Func:
+ return x.Value
case nil:
return valueNull
case bool:
From 33b3260c1e765ef66500ce155c6d5a526d8852e9 Mon Sep 17 00:00:00 2001
From: Lynn Boger
Date: Thu, 14 Oct 2021 10:48:08 -0500
Subject: [PATCH 055/406] cmd/compile/internal/ssagen: set BitLen32 as
intrinsic on PPC64
It was noticed through some other investigation that BitLen32
was not generating the best code and found that it wasn't recognized
as an intrinsic. This corrects that and enables the test for PPC64.
Change-Id: Iab496a8830c8552f507b7292649b1b660f3848b5
Reviewed-on: https://go-review.googlesource.com/c/go/+/355872
Run-TryBot: Lynn Boger
TryBot-Result: Go Bot
Trust: Lynn Boger
Reviewed-by: Cherry Mui
---
src/cmd/compile/internal/ssagen/ssa.go | 4 ++--
test/codegen/mathbits.go | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go
index 08114b7828..5a958a569d 100644
--- a/src/cmd/compile/internal/ssagen/ssa.go
+++ b/src/cmd/compile/internal/ssagen/ssa.go
@@ -4421,7 +4421,7 @@ func InitTables() {
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
return s.newValue1(ssa.OpBitLen32, types.Types[types.TINT], args[0])
},
- sys.AMD64, sys.ARM64)
+ sys.AMD64, sys.ARM64, sys.PPC64)
addF("math/bits", "Len32",
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
if s.config.PtrSize == 4 {
@@ -4430,7 +4430,7 @@ func InitTables() {
x := s.newValue1(ssa.OpZeroExt32to64, types.Types[types.TUINT64], args[0])
return s.newValue1(ssa.OpBitLen64, types.Types[types.TINT], x)
},
- sys.ARM, sys.S390X, sys.MIPS, sys.PPC64, sys.Wasm)
+ sys.ARM, sys.S390X, sys.MIPS, sys.Wasm)
addF("math/bits", "Len16",
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
if s.config.PtrSize == 4 {
diff --git a/test/codegen/mathbits.go b/test/codegen/mathbits.go
index 2d8790c1de..859490c363 100644
--- a/test/codegen/mathbits.go
+++ b/test/codegen/mathbits.go
@@ -93,6 +93,8 @@ func Len32(n uint32) int {
// arm:"CLZ" arm64:"CLZ"
// mips:"CLZ"
// wasm:"I64Clz"
+ // ppc64: "CNTLZW"
+ // ppc64le: "CNTLZW"
return bits.Len32(n)
}
From 425db64811285fd0b35ed12eaed7568ec547da78 Mon Sep 17 00:00:00 2001
From: Damien Neil
Date: Fri, 6 Aug 2021 13:23:13 -0700
Subject: [PATCH 056/406] bufio: use underlying ReadFrom even when data is
buffered
When (*bufio.Writer).ReadFrom is called with a partially filled buffer,
fill out and flush the buffer and then call the underlying writer's
ReadFrom method if present.
Fixes #44815.
Change-Id: I15b3ef0746d0d60fd62041189a9b9df11254dd29
Reviewed-on: https://go-review.googlesource.com/c/go/+/340530
Trust: Damien Neil
Run-TryBot: Damien Neil
TryBot-Result: Go Bot
Reviewed-by: Ian Lance Taylor
---
src/bufio/bufio.go | 19 ++++++++--------
src/bufio/bufio_test.go | 48 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 58 insertions(+), 9 deletions(-)
diff --git a/src/bufio/bufio.go b/src/bufio/bufio.go
index a58df25494..063a7785f3 100644
--- a/src/bufio/bufio.go
+++ b/src/bufio/bufio.go
@@ -745,19 +745,14 @@ func (b *Writer) WriteString(s string) (int, error) {
}
// ReadFrom implements io.ReaderFrom. If the underlying writer
-// supports the ReadFrom method, and b has no buffered data yet,
-// this calls the underlying ReadFrom without buffering.
+// supports the ReadFrom method, this calls the underlying ReadFrom.
+// If there is buffered data and an underlying ReadFrom, this fills
+// the buffer and writes it before calling ReadFrom.
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
if b.err != nil {
return 0, b.err
}
- if b.Buffered() == 0 {
- if w, ok := b.wr.(io.ReaderFrom); ok {
- n, err = w.ReadFrom(r)
- b.err = err
- return n, err
- }
- }
+ readerFrom, readerFromOK := b.wr.(io.ReaderFrom)
var m int
for {
if b.Available() == 0 {
@@ -765,6 +760,12 @@ func (b *Writer) ReadFrom(r io.Reader) (n int64, err error) {
return n, err1
}
}
+ if readerFromOK && b.Buffered() == 0 {
+ nn, err := readerFrom.ReadFrom(r)
+ b.err = err
+ n += nn
+ return n, err
+ }
nr := 0
for nr < maxConsecutiveEmptyReads {
m, err = r.Read(b.buf[b.n:])
diff --git a/src/bufio/bufio_test.go b/src/bufio/bufio_test.go
index 8e8a8a1778..66b3e70053 100644
--- a/src/bufio/bufio_test.go
+++ b/src/bufio/bufio_test.go
@@ -1351,6 +1351,54 @@ func TestWriterReadFromErrNoProgress(t *testing.T) {
}
}
+type readFromWriter struct {
+ buf []byte
+ writeBytes int
+ readFromBytes int
+}
+
+func (w *readFromWriter) Write(p []byte) (int, error) {
+ w.buf = append(w.buf, p...)
+ w.writeBytes += len(p)
+ return len(p), nil
+}
+
+func (w *readFromWriter) ReadFrom(r io.Reader) (int64, error) {
+ b, err := io.ReadAll(r)
+ w.buf = append(w.buf, b...)
+ w.readFromBytes += len(b)
+ return int64(len(b)), err
+}
+
+// Test that calling (*Writer).ReadFrom with a partially-filled buffer
+// fills the buffer before switching over to ReadFrom.
+func TestWriterReadFromWithBufferedData(t *testing.T) {
+ const bufsize = 16
+
+ input := createTestInput(64)
+ rfw := &readFromWriter{}
+ w := NewWriterSize(rfw, bufsize)
+
+ const writeSize = 8
+ if n, err := w.Write(input[:writeSize]); n != writeSize || err != nil {
+ t.Errorf("w.Write(%v bytes) = %v, %v; want %v, nil", writeSize, n, err, writeSize)
+ }
+ n, err := w.ReadFrom(bytes.NewReader(input[writeSize:]))
+ if wantn := len(input[writeSize:]); int(n) != wantn || err != nil {
+ t.Errorf("io.Copy(w, %v bytes) = %v, %v; want %v, nil", wantn, n, err, wantn)
+ }
+ if err := w.Flush(); err != nil {
+ t.Errorf("w.Flush() = %v, want nil", err)
+ }
+
+ if got, want := rfw.writeBytes, bufsize; got != want {
+ t.Errorf("wrote %v bytes with Write, want %v", got, want)
+ }
+ if got, want := rfw.readFromBytes, len(input)-bufsize; got != want {
+ t.Errorf("wrote %v bytes with ReadFrom, want %v", got, want)
+ }
+}
+
func TestReadZero(t *testing.T) {
for _, size := range []int{100, 2} {
t.Run(fmt.Sprintf("bufsize=%d", size), func(t *testing.T) {
From 3befaf0cdb18420f45acfa7cee725297aa550faf Mon Sep 17 00:00:00 2001
From: jiahua wang
Date: Sat, 16 Oct 2021 23:19:57 +0800
Subject: [PATCH 057/406] net/url: fix stale RFC 3986 links
The URLs for RFC 3986 have been changed from:
http://tools.ietf.org/html/rfc3986
to:
https://datatracker.ietf.org/doc/html/rfc3986
Change-Id: I0662557d91bbb7de51d7ca4bc64e838741cd9074
Reviewed-on: https://go-review.googlesource.com/c/go/+/356429
Run-TryBot: Ian Lance Taylor
TryBot-Result: Go Bot
Reviewed-by: Emmanuel Odeke
Trust: Cherry Mui
---
src/net/url/url_test.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go
index 5059d34bf1..6807d58f1a 100644
--- a/src/net/url/url_test.go
+++ b/src/net/url/url_test.go
@@ -1172,7 +1172,7 @@ var resolveReferenceTests = []struct {
{"http://foo.com/bar/baz", "quux/./dotdot/../dotdot/../dot/./tail/..", "http://foo.com/bar/quux/dot/"},
// Remove any dot-segments prior to forming the target URI.
- // http://tools.ietf.org/html/rfc3986#section-5.2.4
+ // https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
{"http://foo.com/dot/./dotdot/../foo/bar", "../baz", "http://foo.com/dot/baz"},
// Triple dot isn't special
@@ -1192,7 +1192,7 @@ var resolveReferenceTests = []struct {
{"http://foo.com/foo%2dbar/", "./baz-quux", "http://foo.com/foo%2dbar/baz-quux"},
// RFC 3986: Normal Examples
- // http://tools.ietf.org/html/rfc3986#section-5.4.1
+ // https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.1
{"http://a/b/c/d;p?q", "g:h", "g:h"},
{"http://a/b/c/d;p?q", "g", "http://a/b/c/g"},
{"http://a/b/c/d;p?q", "./g", "http://a/b/c/g"},
@@ -1218,7 +1218,7 @@ var resolveReferenceTests = []struct {
{"http://a/b/c/d;p?q", "../../g", "http://a/g"},
// RFC 3986: Abnormal Examples
- // http://tools.ietf.org/html/rfc3986#section-5.4.2
+ // https://datatracker.ietf.org/doc/html/rfc3986#section-5.4.2
{"http://a/b/c/d;p?q", "../../../g", "http://a/g"},
{"http://a/b/c/d;p?q", "../../../../g", "http://a/g"},
{"http://a/b/c/d;p?q", "/./g", "http://a/g"},
From 73971784dc586a5db3b81dfdd41954f650d493ac Mon Sep 17 00:00:00 2001
From: Robert Findley
Date: Fri, 15 Oct 2021 10:52:55 -0400
Subject: [PATCH 058/406] go/types: add support for inferring type instances
Add constraint type inference for type instances, to be consistent with
inference of function values.
Fixes #47990
Change-Id: Ib99b5215cb2da5c10badc4de7e9e60ca0e48489f
Reviewed-on: https://go-review.googlesource.com/c/go/+/356489
Trust: Robert Findley
Run-TryBot: Robert Findley
TryBot-Result: Go Bot
Reviewed-by: Robert Griesemer
---
src/go/types/call.go | 32 +++++++-
src/go/types/instantiate.go | 50 ------------
src/go/types/named.go | 3 +-
.../{tinference.go2 => funcinference.go2} | 30 +++----
src/go/types/testdata/check/typeinference.go2 | 47 +++++++++++
src/go/types/testdata/check/typeinst2.go2 | 4 +
.../types/testdata/check/typeinstcycles.go2 | 11 +++
src/go/types/typexpr.go | 79 ++++++++++++++++---
8 files changed, 175 insertions(+), 81 deletions(-)
rename src/go/types/testdata/check/{tinference.go2 => funcinference.go2} (77%)
create mode 100644 src/go/types/testdata/check/typeinference.go2
create mode 100644 src/go/types/testdata/check/typeinstcycles.go2
diff --git a/src/go/types/call.go b/src/go/types/call.go
index a642f6f295..4731c69619 100644
--- a/src/go/types/call.go
+++ b/src/go/types/call.go
@@ -60,7 +60,7 @@ func (check *Checker) funcInst(x *operand, ix *typeparams.IndexExpr) {
}
// instantiate function signature
- res := check.instantiate(x.Pos(), sig, targs, poslist).(*Signature)
+ res := check.instantiateSignature(x.Pos(), sig, targs, poslist)
assert(res.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(ix.Orig, targs, res)
x.typ = res
@@ -68,6 +68,34 @@ func (check *Checker) funcInst(x *operand, ix *typeparams.IndexExpr) {
x.expr = ix.Orig
}
+func (check *Checker) instantiateSignature(pos token.Pos, typ *Signature, targs []Type, posList []token.Pos) (res *Signature) {
+ assert(check != nil)
+ assert(len(targs) == typ.TypeParams().Len())
+
+ if trace {
+ check.trace(pos, "-- instantiating %s with %s", typ, targs)
+ check.indent++
+ defer func() {
+ check.indent--
+ check.trace(pos, "=> %s (under = %s)", res, res.Underlying())
+ }()
+ }
+
+ inst := check.instance(pos, typ, targs, check.conf.Context).(*Signature)
+ assert(len(posList) <= len(targs))
+ tparams := typ.TypeParams().list()
+ if i, err := check.verify(pos, tparams, targs); err != nil {
+ // best position for error reporting
+ pos := pos
+ if i < len(posList) {
+ pos = posList[i]
+ }
+ check.softErrorf(atPos(pos), _Todo, err.Error())
+ }
+
+ return inst
+}
+
func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
ix := typeparams.UnpackIndexExpr(call.Fun)
if ix != nil {
@@ -352,7 +380,7 @@ func (check *Checker) arguments(call *ast.CallExpr, sig *Signature, targs []Type
}
// compute result signature
- rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature)
+ rsig = check.instantiateSignature(call.Pos(), sig, targs, nil)
assert(rsig.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(call.Fun, targs, rsig)
diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go
index 2a255bcb87..65c935a192 100644
--- a/src/go/types/instantiate.go
+++ b/src/go/types/instantiate.go
@@ -49,56 +49,6 @@ func Instantiate(ctxt *Context, typ Type, targs []Type, validate bool) (Type, er
return inst, err
}
-// instantiate creates an instance and defers verification of constraints to
-// later in the type checking pass. For Named types the resulting instance will
-// be unexpanded.
-func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, posList []token.Pos) (res Type) {
- assert(check != nil)
- if trace {
- check.trace(pos, "-- instantiating %s with %s", typ, NewTypeList(targs))
- check.indent++
- defer func() {
- check.indent--
- var under Type
- if res != nil {
- // Calling under() here may lead to endless instantiations.
- // Test case: type T[P any] T[P]
- // TODO(gri) investigate if that's a bug or to be expected.
- under = safeUnderlying(res)
- }
- check.trace(pos, "=> %s (under = %s)", res, under)
- }()
- }
-
- inst := check.instance(pos, typ, targs, check.conf.Context)
-
- assert(len(posList) <= len(targs))
- check.later(func() {
- // Collect tparams again because lazily loaded *Named types may not have
- // had tparams set up above.
- var tparams []*TypeParam
- switch t := typ.(type) {
- case *Named:
- tparams = t.TypeParams().list()
- case *Signature:
- tparams = t.TypeParams().list()
- }
- // Avoid duplicate errors; instantiate will have complained if tparams
- // and targs do not have the same length.
- if len(tparams) == len(targs) {
- if i, err := check.verify(pos, tparams, targs); err != nil {
- // best position for error reporting
- pos := pos
- if i < len(posList) {
- pos = posList[i]
- }
- check.softErrorf(atPos(pos), _Todo, err.Error())
- }
- }
- })
- return inst
-}
-
// instance creates a type or function instance using the given original type
// typ and arguments targs. For Named types the resulting instance will be
// unexpanded.
diff --git a/src/go/types/named.go b/src/go/types/named.go
index 82b2afcb63..595863a01b 100644
--- a/src/go/types/named.go
+++ b/src/go/types/named.go
@@ -241,7 +241,8 @@ func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParam
check := n.check
- if check.validateTArgLen(instPos, n.orig.tparams.Len(), n.targs.Len()) {
+ // Mismatching arg and tparam length may be checked elsewhere.
+ if n.orig.tparams.Len() == n.targs.Len() {
// We must always have a context, to avoid infinite recursion.
ctxt = check.bestContext(ctxt)
h := ctxt.typeHash(n.orig, n.targs.list())
diff --git a/src/go/types/testdata/check/tinference.go2 b/src/go/types/testdata/check/funcinference.go2
similarity index 77%
rename from src/go/types/testdata/check/tinference.go2
rename to src/go/types/testdata/check/funcinference.go2
index 28516ef639..f04b76ca1a 100644
--- a/src/go/types/testdata/check/tinference.go2
+++ b/src/go/types/testdata/check/funcinference.go2
@@ -2,29 +2,25 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tinferenceB
+package funcInference
import "strconv"
type any interface{}
-// TODO(rFindley) the below partially applied function types should probably
-// not be permitted (spec question).
+func f0[A any, B interface{~*C}, C interface{~*D}, D interface{~*A}](A, B, C, D) {}
+func _() {
+ f := f0[string]
+ f("a", nil, nil, nil)
+ f0("a", nil, nil, nil)
+}
-// Embedding stand-alone type parameters is not permitted for now. Disabled.
-// func f0[A any, B interface{~C}, C interface{~D}, D interface{~A}](A, B, C, D)
-// func _() {
-// f := f0[string]
-// f("a", "b", "c", "d")
-// f0("a", "b", "c", "d")
-// }
-//
-// func f1[A any, B interface{~A}](A, B)
-// func _() {
-// f := f1[int]
-// f(int(0), int(0))
-// f1(int(0), int(0))
-// }
+func f1[A any, B interface{~*A}](A, B) {}
+func _() {
+ f := f1[int]
+ f(int(0), new(int))
+ f1(int(0), new(int))
+}
func f2[A any, B interface{~[]A}](A, B) {}
func _() {
diff --git a/src/go/types/testdata/check/typeinference.go2 b/src/go/types/testdata/check/typeinference.go2
new file mode 100644
index 0000000000..8876ccaa4e
--- /dev/null
+++ b/src/go/types/testdata/check/typeinference.go2
@@ -0,0 +1,47 @@
+// Copyright 2021 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.
+
+package typeInference
+
+// basic inference
+type Tb[P ~*Q, Q any] int
+func _() {
+ var x Tb[*int]
+ var y Tb[*int, int]
+ x = y
+ _ = x
+}
+
+// recursive inference
+type Tr[A any, B ~*C, C ~*D, D ~*A] int
+func _() {
+ var x Tr[string]
+ var y Tr[string, ***string, **string, *string]
+ var z Tr[int, ***int, **int, *int]
+ x = y
+ x = z // ERROR cannot use z .* as Tr
+ _ = x
+}
+
+// other patterns of inference
+type To0[A any, B ~[]A] int
+type To1[A any, B ~struct{a A}] int
+type To2[A any, B ~[][]A] int
+type To3[A any, B ~[3]*A] int
+type To4[A any, B any, C ~struct{a A; b B}] int
+func _() {
+ var _ To0[int]
+ var _ To1[int]
+ var _ To2[int]
+ var _ To3[int]
+ var _ To4[int, string]
+}
+
+// failed inference
+type Tf0[A, B any] int
+type Tf1[A any, B ~struct{a A; c C}, C any] int
+func _() {
+ var _ Tf0 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [int]
+ var _ Tf1 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 3 type parameters */ [int]
+}
diff --git a/src/go/types/testdata/check/typeinst2.go2 b/src/go/types/testdata/check/typeinst2.go2
index 95c249d529..88913785c8 100644
--- a/src/go/types/testdata/check/typeinst2.go2
+++ b/src/go/types/testdata/check/typeinst2.go2
@@ -255,3 +255,7 @@ var _ = f0_[int]
var _ = f0_[bool /* ERROR does not satisfy I0_ */ ]
var _ = f0_[string /* ERROR does not satisfy I0_ */ ]
var _ = f0_[float64 /* ERROR does not satisfy I0_ */ ]
+
+// Using a function instance as a type is an error.
+var _ f0 // ERROR not a type
+var _ f0 /* ERROR not a type */ [int]
diff --git a/src/go/types/testdata/check/typeinstcycles.go2 b/src/go/types/testdata/check/typeinstcycles.go2
new file mode 100644
index 0000000000..74fe19195a
--- /dev/null
+++ b/src/go/types/testdata/check/typeinstcycles.go2
@@ -0,0 +1,11 @@
+// Copyright 2021 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.
+
+package p
+
+import "unsafe"
+
+func F1[T any](_ [unsafe.Sizeof(F1[int])]T) (res T) { return }
+func F2[T any](_ T) (res [unsafe.Sizeof(F2[string])]int) { return }
+func F3[T any](_ [unsafe.Sizeof(F1[string])]int) {}
diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go
index 71623c336e..e812c3d5d5 100644
--- a/src/go/types/typexpr.go
+++ b/src/go/types/typexpr.go
@@ -265,7 +265,6 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) {
if !check.allowVersion(check.pkg, 1, 18) {
check.softErrorf(inNode(e, ix.Lbrack), _Todo, "type instantiation requires go1.18 or later")
}
- // TODO(rfindley): type instantiation should require go1.18
return check.instantiatedType(ix.X, ix.Indices, def)
case *ast.ParenExpr:
@@ -375,13 +374,24 @@ func (check *Checker) typInternal(e0 ast.Expr, def *Named) (T Type) {
return typ
}
-func (check *Checker) instantiatedType(x ast.Expr, targsx []ast.Expr, def *Named) Type {
+func (check *Checker) instantiatedType(x ast.Expr, targsx []ast.Expr, def *Named) (res Type) {
+ if trace {
+ check.trace(x.Pos(), "-- instantiating %s with %s", x, targsx)
+ check.indent++
+ defer func() {
+ check.indent--
+ // Don't format the underlying here. It will always be nil.
+ check.trace(x.Pos(), "=> %s", res)
+ }()
+ }
+
gtyp := check.genericType(x, true)
if gtyp == Typ[Invalid] {
return gtyp // error already reported
}
- base, _ := gtyp.(*Named)
- if base == nil {
+
+ origin, _ := gtyp.(*Named)
+ if origin == nil {
panic(fmt.Sprintf("%v: cannot instantiate %v", x.Pos(), gtyp))
}
@@ -398,17 +408,64 @@ func (check *Checker) instantiatedType(x ast.Expr, targsx []ast.Expr, def *Named
posList[i] = arg.Pos()
}
- typ := check.instantiate(x.Pos(), base, targs, posList)
- def.setUnderlying(typ)
- check.recordInstance(x, targs, typ)
+ // create the instance
+ h := check.conf.Context.typeHash(origin, targs)
+ // targs may be incomplete, and require inference. In any case we should de-duplicate.
+ inst := check.conf.Context.typeForHash(h, nil)
+ // If inst is non-nil, we can't just return here. Inst may have been
+ // constructed via recursive substitution, in which case we wouldn't do the
+ // validation below. Ensure that the validation (and resulting errors) runs
+ // for each instantiated type in the source.
+ if inst == nil {
+ tname := NewTypeName(x.Pos(), origin.obj.pkg, origin.obj.name, nil)
+ inst = check.newNamed(tname, origin, nil, nil, nil) // underlying, methods and tparams are set when named is resolved
+ inst.targs = NewTypeList(targs)
+ inst = check.conf.Context.typeForHash(h, inst)
+ }
+ def.setUnderlying(inst)
- // make sure we check instantiation works at least once
- // and that the resulting type is valid
+ inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, []*Func) {
+ tparams := origin.TypeParams().list()
+
+ inferred := targs
+ if len(targs) < len(tparams) {
+ // If inference fails, len(inferred) will be 0, and inst.underlying will
+ // be set to Typ[Invalid] in expandNamed.
+ inferred = check.infer(x, tparams, targs, nil, nil)
+ if len(inferred) > len(targs) {
+ inst.targs = NewTypeList(inferred)
+ }
+ }
+
+ check.recordInstance(x, inferred, inst)
+ return expandNamed(ctxt, n, x.Pos())
+ }
+
+ // origin.tparams may not be set up, so we need to do expansion later.
check.later(func() {
- check.validType(typ, nil)
+ // This is an instance from the source, not from recursive substitution,
+ // and so it must be resolved during type-checking so that we can report
+ // errors.
+ inst.resolve(check.conf.Context)
+ // Since check is non-nil, we can still mutate inst. Unpinning the resolver
+ // frees some memory.
+ inst.resolver = nil
+
+ if check.validateTArgLen(x.Pos(), inst.tparams.Len(), inst.targs.Len()) {
+ if i, err := check.verify(x.Pos(), inst.tparams.list(), inst.targs.list()); err != nil {
+ // best position for error reporting
+ pos := x.Pos()
+ if i < len(posList) {
+ pos = posList[i]
+ }
+ check.softErrorf(atPos(pos), _Todo, err.Error())
+ }
+ }
+
+ check.validType(inst, nil)
})
- return typ
+ return inst
}
// arrayLength type-checks the array length expression e
From 323e009c757229bdf58f68fde1c5bf07e9e65b61 Mon Sep 17 00:00:00 2001
From: Robert Findley
Date: Fri, 15 Oct 2021 16:39:39 -0400
Subject: [PATCH 059/406] go/types: ensure named types are expanded after
type-checking
Rather than using Checker.later in newNamed, add a Checker.defTypes
field to track named types that have been created during type-checking,
and use this to expand named types as a final phase in type checking.
We have encountered several bugs related to infinite recursion while
expanding named types, because (I would argue) we have two conflicting
requirements in the type checker: ensuring that we eventually collapse
underlying chains, and yet allowing lazy substitution of the underlying
type in instances. The former is necessary for correctness, and to
ensure that we detect cycles during the type-checking pass. The latter
is necessary to allow infinitely expanding patterns of instances through
underlying or method definitions.
I believe this CL reconciles these conflicting requirements, by creating
a boundary between types that are encountered in the source during
type checking, and instances that are created by recursive evaluation.
At the end of the type checking pass, Checker.defTypes should contain
all possible origin types for instantiation. Once we compute the true
underlying for these origin types, any remaining instances that are
unresolved are guaranteed to have an origin with a valid underlying.
Therefore, we can return from the type-checking pass without calling
under() for these remaining instances.
Fixes #48703
Fixes #48974
Change-Id: I1474f514e2ab71c1ad4c3704fe32bfba11d59394
Reviewed-on: https://go-review.googlesource.com/c/go/+/356490
Trust: Robert Findley
Run-TryBot: Robert Findley
TryBot-Result: Go Bot
Reviewed-by: Robert Griesemer
---
src/go/types/check.go | 27 +++++++++++++++++++
src/go/types/named.go | 23 ++++++----------
.../types/testdata/fixedbugs/issue48703.go2 | 27 +++++++++++++++++++
.../types/testdata/fixedbugs/issue48974.go2 | 22 +++++++++++++++
4 files changed, 84 insertions(+), 15 deletions(-)
create mode 100644 src/go/types/testdata/fixedbugs/issue48703.go2
create mode 100644 src/go/types/testdata/fixedbugs/issue48974.go2
diff --git a/src/go/types/check.go b/src/go/types/check.go
index 46a0000940..3fc9c03917 100644
--- a/src/go/types/check.go
+++ b/src/go/types/check.go
@@ -113,6 +113,7 @@ type Checker struct {
untyped map[ast.Expr]exprInfo // map of expressions without final type
delayed []func() // stack of delayed action segments; segments are processed in FIFO order
objPath []Object // path of object dependencies during type inference (for cycle reporting)
+ defTypes []*Named // defined types created during type checking, for final validation.
// context within which the current object is type-checked
// (valid only for the duration of type-checking a specific object)
@@ -269,6 +270,8 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
check.processDelayed(0) // incl. all functions
+ check.expandDefTypes()
+
check.initOrder()
if !check.conf.DisableUnusedImportCheck {
@@ -285,6 +288,7 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
check.pkgPathMap = nil
check.seenPkgMap = nil
check.recvTParamMap = nil
+ check.defTypes = nil
// TODO(rFindley) There's more memory we should release at this point.
@@ -306,6 +310,29 @@ func (check *Checker) processDelayed(top int) {
check.delayed = check.delayed[:top]
}
+func (check *Checker) expandDefTypes() {
+ // Ensure that every defined type created in the course of type-checking has
+ // either non-*Named underlying, or is unresolved.
+ //
+ // This guarantees that we don't leak any types whose underlying is *Named,
+ // because any unresolved instances will lazily compute their underlying by
+ // substituting in the underlying of their origin. The origin must have
+ // either been imported or type-checked and expanded here, and in either case
+ // its underlying will be fully expanded.
+ for i := 0; i < len(check.defTypes); i++ {
+ n := check.defTypes[i]
+ switch n.underlying.(type) {
+ case nil:
+ if n.resolver == nil {
+ panic("nil underlying")
+ }
+ case *Named:
+ n.under() // n.under may add entries to check.defTypes
+ }
+ n.check = nil
+ }
+}
+
func (check *Checker) record(x *operand) {
// convert x into a user-friendly set of values
// TODO(gri) this code can be simplified
diff --git a/src/go/types/named.go b/src/go/types/named.go
index 595863a01b..c81383810e 100644
--- a/src/go/types/named.go
+++ b/src/go/types/named.go
@@ -65,22 +65,9 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tpar
if obj.typ == nil {
obj.typ = typ
}
- // Ensure that typ is always expanded, at which point the check field can be
- // nilled out.
- //
- // Note that currently we cannot nil out check inside typ.under(), because
- // it's possible that typ is expanded multiple times.
- //
- // TODO(rFindley): clean this up so that under is the only function mutating
- // named types.
+ // Ensure that typ is always expanded and sanity-checked.
if check != nil {
- check.later(func() {
- switch typ.under().(type) {
- case *Named:
- panic("unexpanded underlying type")
- }
- typ.check = nil
- })
+ check.defTypes = append(check.defTypes, typ)
}
return typ
}
@@ -241,6 +228,12 @@ func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParam
check := n.check
+ if _, unexpanded := n.orig.underlying.(*Named); unexpanded {
+ // We should only get an unexpanded underlying here during type checking
+ // (for example, in recursive type declarations).
+ assert(check != nil)
+ }
+
// Mismatching arg and tparam length may be checked elsewhere.
if n.orig.tparams.Len() == n.targs.Len() {
// We must always have a context, to avoid infinite recursion.
diff --git a/src/go/types/testdata/fixedbugs/issue48703.go2 b/src/go/types/testdata/fixedbugs/issue48703.go2
new file mode 100644
index 0000000000..8a32c1ecf2
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue48703.go2
@@ -0,0 +1,27 @@
+// Copyright 2021 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.
+
+package p
+
+import "unsafe"
+
+// The actual example from the issue.
+type List[P any] struct{}
+
+func (_ List[P]) m() (_ List[List[P]]) { return }
+
+// Other types of recursion through methods.
+type R[P any] int
+
+func (*R[R /* ERROR must be an identifier */ [int]]) m0() {}
+func (R[P]) m1(R[R[P]]) {}
+func (R[P]) m2(R[*P]) {}
+func (R[P]) m3([unsafe.Sizeof(new(R[P]))]int) {}
+func (R[P]) m4([unsafe.Sizeof(new(R[R[P]]))]int) {}
+
+// Mutual recursion
+type M[P any] int
+
+func (R[P]) m5(M[M[P]]) {}
+func (M[P]) m(R[R[P]]) {}
diff --git a/src/go/types/testdata/fixedbugs/issue48974.go2 b/src/go/types/testdata/fixedbugs/issue48974.go2
new file mode 100644
index 0000000000..ca4b6d9321
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue48974.go2
@@ -0,0 +1,22 @@
+// Copyright 2021 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.
+
+package p
+
+type Fooer interface {
+ Foo()
+}
+
+type Fooable[F Fooer] struct {
+ ptr F
+}
+
+func (f *Fooable[F]) Adapter() *Fooable[*FooerImpl[F]] {
+ return &Fooable[*FooerImpl[F]]{&FooerImpl[F]{}}
+}
+
+type FooerImpl[F Fooer] struct {
+}
+
+func (fi *FooerImpl[F]) Foo() {}
From eba0e866fafe3f8223d654a29fb953e02c07364a Mon Sep 17 00:00:00 2001
From: Robert Findley
Date: Sun, 17 Oct 2021 18:27:53 -0400
Subject: [PATCH 060/406] go/types: delay expansion of underlying in typeDecl
Even after type-checking the RHS of a type declaration, we may not yet
be able to expand, if the RHS is itself an instance (see #49043).
We can instead rely on the mechanisms we have in place for delayed
expansion.
Fixes #49043
Change-Id: Ibffa4c1b1163c824b5c7e151aaac35f3e8c84ec7
Reviewed-on: https://go-review.googlesource.com/c/go/+/356533
Trust: Robert Findley
Run-TryBot: Robert Findley
Reviewed-by: Robert Griesemer
---
src/go/types/decl.go | 23 +++++-------------
src/go/types/named.go | 17 ++++++++++++-
src/go/types/testdata/check/typeinst.go2 | 2 +-
.../types/testdata/fixedbugs/issue49043.go2 | 24 +++++++++++++++++++
4 files changed, 47 insertions(+), 19 deletions(-)
create mode 100644 src/go/types/testdata/fixedbugs/issue49043.go2
diff --git a/src/go/types/decl.go b/src/go/types/decl.go
index 4aa49b17ca..3e97fbbccd 100644
--- a/src/go/types/decl.go
+++ b/src/go/types/decl.go
@@ -647,22 +647,11 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) {
assert(rhs != nil)
named.fromRHS = rhs
- // The underlying type of named may be itself a named type that is
- // incomplete:
- //
- // type (
- // A B
- // B *C
- // C A
- // )
- //
- // The type of C is the (named) type of A which is incomplete,
- // and which has as its underlying type the named type B.
- // Determine the (final, unnamed) underlying type by resolving
- // any forward chain.
- // TODO(gri) Investigate if we can just use named.fromRHS here
- // and rely on lazy computation of the underlying type.
- named.underlying = under(named)
+ // If the underlying was not set while type-checking the right-hand side, it
+ // is invalid and an error should have been reported elsewhere.
+ if named.underlying == nil {
+ named.underlying = Typ[Invalid]
+ }
// If the RHS is a type parameter, it must be from this type declaration.
if tpar, _ := named.underlying.(*TypeParam); tpar != nil && tparamIndex(named.TypeParams().list(), tpar) < 0 {
@@ -776,7 +765,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
// and field names must be distinct."
base := asNamed(obj.typ) // shouldn't fail but be conservative
if base != nil {
- u := safeUnderlying(base) // base should be expanded, but use safeUnderlying to be conservative
+ u := base.under()
if t, _ := u.(*Struct); t != nil {
for _, fld := range t.fields {
if fld.name != "_" {
diff --git a/src/go/types/named.go b/src/go/types/named.go
index c81383810e..393d40b127 100644
--- a/src/go/types/named.go
+++ b/src/go/types/named.go
@@ -132,6 +132,18 @@ func (t *Named) String() string { return TypeString(t, nil) }
// chain before returning it. If no underlying type is found or a cycle
// is detected, the result is Typ[Invalid]. If a cycle is detected and
// n0.check != nil, the cycle is reported.
+//
+// This is necessary because the underlying type of named may be itself a
+// named type that is incomplete:
+//
+// type (
+// A B
+// B *C
+// C A
+// )
+//
+// The type of C is the (named) type of A which is incomplete,
+// and which has as its underlying type the named type B.
func (n0 *Named) under() Type {
u := n0.Underlying()
@@ -141,7 +153,9 @@ func (n0 *Named) under() Type {
var n1 *Named
switch u1 := u.(type) {
case nil:
- return Typ[Invalid]
+ // After expansion via Underlying(), we should never encounter a nil
+ // underlying.
+ panic("nil underlying")
default:
// common case
return u
@@ -225,6 +239,7 @@ func (check *Checker) bestContext(ctxt *Context) *Context {
// The underlying type will be Typ[Invalid] if there was an error.
func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParamList, underlying Type, methods []*Func) {
n.orig.resolve(ctxt)
+ assert(n.orig.underlying != nil)
check := n.check
diff --git a/src/go/types/testdata/check/typeinst.go2 b/src/go/types/testdata/check/typeinst.go2
index 4a8918ab86..f4f6c0264b 100644
--- a/src/go/types/testdata/check/typeinst.go2
+++ b/src/go/types/testdata/check/typeinst.go2
@@ -57,5 +57,5 @@ var _ T3[int] = T3[int](List[int]{1, 2, 3})
// Self-recursive generic types are not permitted
-type self1[P any] self1 /* ERROR illegal cycle */ [P]
+type self1 /* ERROR illegal cycle */ [P any] self1[P]
type self2[P any] *self2[P] // this is ok
diff --git a/src/go/types/testdata/fixedbugs/issue49043.go2 b/src/go/types/testdata/fixedbugs/issue49043.go2
new file mode 100644
index 0000000000..c37b0f1267
--- /dev/null
+++ b/src/go/types/testdata/fixedbugs/issue49043.go2
@@ -0,0 +1,24 @@
+// Copyright 2021 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.
+
+package p
+
+// The example from the issue.
+type (
+ N /* ERROR illegal cycle */ [P any] M[P]
+ M[P any] N[P]
+)
+
+// A slightly more complicated case.
+type (
+ A /* ERROR illegal cycle */ [P any] B[P]
+ B[P any] C[P]
+ C[P any] A[P]
+)
+
+// Confusing but valid (note that `type T *T` is valid).
+type (
+ N1[P any] *M1[P]
+ M1[P any] *N1[P]
+)
From 8838a3b53fccc7b3aa83312326bfd38bcb8f2281 Mon Sep 17 00:00:00 2001
From: Joel Sing
Date: Fri, 8 Oct 2021 20:28:58 +1100
Subject: [PATCH 061/406] cmd/internal/obj/riscv: fix and enable test in short
mode
The branch test only takes a few seconds so enable it in short mode. Also fix a
typo that currently prevents the code from compiling.
Change-Id: I25fc646b002a66c042c61fb1f56d5efa8e47903c
Reviewed-on: https://go-review.googlesource.com/c/go/+/356249
Trust: Joel Sing
Reviewed-by: Cherry Mui
Run-TryBot: Cherry Mui
TryBot-Result: Go Bot
---
src/cmd/internal/obj/riscv/asm_test.go | 3 ---
src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/cmd/internal/obj/riscv/asm_test.go b/src/cmd/internal/obj/riscv/asm_test.go
index f8f7b4f2ce..684c6b61f2 100644
--- a/src/cmd/internal/obj/riscv/asm_test.go
+++ b/src/cmd/internal/obj/riscv/asm_test.go
@@ -134,9 +134,6 @@ TEXT _stub(SB),$0-0
}
func TestBranch(t *testing.T) {
- if testing.Short() {
- t.Skip("Skipping in short mode")
- }
if runtime.GOARCH != "riscv64" {
t.Skip("Requires riscv64 to run")
}
diff --git a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go
index de412c64a7..68d9589bf2 100644
--- a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go
+++ b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go
@@ -32,7 +32,7 @@ func testGoBGTU(a, b int64) bool { return uint64(a) > uint64(b) }
func testGoBLE(a, b int64) bool { return a <= b }
func testGoBLEU(a, b int64) bool { return uint64(a) <= uint64(b) }
func testGoBLT(a, b int64) bool { return a < b }
-func testGoBLTZ(a, b int64) bool { return uint64(a) < uint64(b) }
+func testGoBLTU(a, b int64) bool { return uint64(a) < uint64(b) }
func TestBranchCondition(t *testing.T) {
tests := []struct {
From ee92daae25029882979eb694bd7246491e364d3c Mon Sep 17 00:00:00 2001
From: Meng Zhuo
Date: Mon, 20 Sep 2021 20:44:50 +0800
Subject: [PATCH 062/406] runtime: ensure at least 1 tick between events
ticks might be same after tick division, although the real cputicks
is linear growth
Fixes #46737
Change-Id: I1d98866fbf21b426c6c1c96cc9cf802d7f440f18
Reviewed-on: https://go-review.googlesource.com/c/go/+/330849
Trust: Meng Zhuo
Trust: Bryan C. Mills
Run-TryBot: Meng Zhuo
Reviewed-by: Michael Knyszek
TryBot-Result: Go Bot
---
src/runtime/trace.go | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/runtime/trace.go b/src/runtime/trace.go
index 00544e4283..5b14a5f553 100644
--- a/src/runtime/trace.go
+++ b/src/runtime/trace.go
@@ -551,8 +551,15 @@ func traceEventLocked(extraBytes int, mp *m, pid int32, bufp *traceBufPtr, ev by
bufp.set(buf)
}
+ // NOTE: ticks might be same after tick division, although the real cputicks is
+ // linear growth.
ticks := uint64(cputicks()) / traceTickDiv
tickDiff := ticks - buf.lastTicks
+ if tickDiff == 0 {
+ ticks = buf.lastTicks + 1
+ tickDiff = 1
+ }
+
buf.lastTicks = ticks
narg := byte(len(args))
if skip >= 0 {
@@ -653,6 +660,9 @@ func traceFlush(buf traceBufPtr, pid int32) traceBufPtr {
// initialize the buffer for a new batch
ticks := uint64(cputicks()) / traceTickDiv
+ if ticks == bufp.lastTicks {
+ ticks = bufp.lastTicks + 1
+ }
bufp.lastTicks = ticks
bufp.byte(traceEvBatch | 1<
Date: Thu, 9 Sep 2021 00:32:03 +0800
Subject: [PATCH 063/406] encoding/base64: add examples for Encode/Decode
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Fixes #37595
Change-Id: I83e5f6105748a0a9238322a4f7ec4b0bbf61a263
Reviewed-on: https://go-review.googlesource.com/c/go/+/348394
Reviewed-by: Daniel Martí
Reviewed-by: Ian Lance Taylor
Trust: Daniel Martí
Trust: Cherry Mui
---
src/encoding/base64/example_test.go | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/encoding/base64/example_test.go b/src/encoding/base64/example_test.go
index 73f119ac5e..61a3adc5ad 100644
--- a/src/encoding/base64/example_test.go
+++ b/src/encoding/base64/example_test.go
@@ -35,6 +35,15 @@ func ExampleEncoding_EncodeToString() {
// YW55ICsgb2xkICYgZGF0YQ==
}
+func ExampleEncoding_Encode() {
+ data := []byte("Hello, world!")
+ dst := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
+ base64.StdEncoding.Encode(dst, data)
+ fmt.Println(string(dst))
+ // Output:
+ // SGVsbG8sIHdvcmxkIQ==
+}
+
func ExampleEncoding_DecodeString() {
str := "c29tZSBkYXRhIHdpdGggACBhbmQg77u/"
data, err := base64.StdEncoding.DecodeString(str)
@@ -47,6 +56,20 @@ func ExampleEncoding_DecodeString() {
// "some data with \x00 and \ufeff"
}
+func ExampleEncoding_Decode() {
+ str := "SGVsbG8sIHdvcmxkIQ=="
+ dst := make([]byte, base64.StdEncoding.DecodedLen(len(str)))
+ n, err := base64.StdEncoding.Decode(dst, []byte(str))
+ if err != nil {
+ fmt.Println("decode error:", err)
+ return
+ }
+ dst = dst[:n]
+ fmt.Printf("%q\n", dst)
+ // Output:
+ // "Hello, world!"
+}
+
func ExampleNewEncoder() {
input := []byte("foo\x00bar")
encoder := base64.NewEncoder(base64.StdEncoding, os.Stdout)
From b0351bfd7d5f0d367c27aa07789b2e6317442ece Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Mart=C3=AD?=
Date: Tue, 19 Oct 2021 09:09:55 +0000
Subject: [PATCH 064/406] Revert "cmd/compile: use MOVBE instruction for
GOAMD64>=v3"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This reverts CL 354670.
Reason for revert: broke make.bash with GOAMD64=v3.
Fixes #49061.
Change-Id: I7f2ed99b7c10100c4e0c1462ea91c4c9d8c609b2
Reviewed-on: https://go-review.googlesource.com/c/go/+/356790
Trust: Daniel Martí
Run-TryBot: Daniel Martí
TryBot-Result: Go Bot
Reviewed-by: Koichi Shiraishi
Reviewed-by: Cuong Manh Le
---
src/cmd/compile/internal/amd64/ssa.go | 7 +-
.../compile/internal/amd64/versions_test.go | 27 +-
src/cmd/compile/internal/ssa/gen/AMD64.rules | 26 --
src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 6 -
src/cmd/compile/internal/ssa/opGen.go | 64 -----
src/cmd/compile/internal/ssa/rewriteAMD64.go | 243 ------------------
test/codegen/memcombine.go | 36 +--
7 files changed, 20 insertions(+), 389 deletions(-)
diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go
index b0e5c34030..0e74574422 100644
--- a/src/cmd/compile/internal/amd64/ssa.go
+++ b/src/cmd/compile/internal/amd64/ssa.go
@@ -772,9 +772,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.From.Val = math.Float64frombits(uint64(v.AuxInt))
p.To.Type = obj.TYPE_REG
p.To.Reg = x
- case ssa.OpAMD64MOVQload, ssa.OpAMD64MOVLload, ssa.OpAMD64MOVWload, ssa.OpAMD64MOVBload, ssa.OpAMD64MOVOload,
- ssa.OpAMD64MOVSSload, ssa.OpAMD64MOVSDload, ssa.OpAMD64MOVBQSXload, ssa.OpAMD64MOVWQSXload, ssa.OpAMD64MOVLQSXload,
- ssa.OpAMD64MOVBEQload, ssa.OpAMD64MOVBELload:
+ case ssa.OpAMD64MOVQload, ssa.OpAMD64MOVSSload, ssa.OpAMD64MOVSDload, ssa.OpAMD64MOVLload, ssa.OpAMD64MOVWload, ssa.OpAMD64MOVBload, ssa.OpAMD64MOVBQSXload, ssa.OpAMD64MOVWQSXload, ssa.OpAMD64MOVLQSXload, ssa.OpAMD64MOVOload:
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_MEM
p.From.Reg = v.Args[0].Reg()
@@ -790,8 +788,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Reg = v.Reg()
case ssa.OpAMD64MOVQstore, ssa.OpAMD64MOVSSstore, ssa.OpAMD64MOVSDstore, ssa.OpAMD64MOVLstore, ssa.OpAMD64MOVWstore, ssa.OpAMD64MOVBstore, ssa.OpAMD64MOVOstore,
ssa.OpAMD64ADDQmodify, ssa.OpAMD64SUBQmodify, ssa.OpAMD64ANDQmodify, ssa.OpAMD64ORQmodify, ssa.OpAMD64XORQmodify,
- ssa.OpAMD64ADDLmodify, ssa.OpAMD64SUBLmodify, ssa.OpAMD64ANDLmodify, ssa.OpAMD64ORLmodify, ssa.OpAMD64XORLmodify,
- ssa.OpAMD64MOVBEQstore, ssa.OpAMD64MOVBELstore:
+ ssa.OpAMD64ADDLmodify, ssa.OpAMD64SUBLmodify, ssa.OpAMD64ANDLmodify, ssa.OpAMD64ORLmodify, ssa.OpAMD64XORLmodify:
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_REG
p.From.Reg = v.Args[1].Reg()
diff --git a/src/cmd/compile/internal/amd64/versions_test.go b/src/cmd/compile/internal/amd64/versions_test.go
index ee1a8ca3aa..de677f3a69 100644
--- a/src/cmd/compile/internal/amd64/versions_test.go
+++ b/src/cmd/compile/internal/amd64/versions_test.go
@@ -53,9 +53,7 @@ func TestGoAMD64v1(t *testing.T) {
opcodes := map[string]bool{}
var features []string
for feature, opcodeList := range featureToOpcodes {
- if runtimeFeatures[feature] {
- features = append(features, fmt.Sprintf("cpu.%s=off", feature))
- }
+ features = append(features, fmt.Sprintf("cpu.%s=off", feature))
for _, op := range opcodeList {
opcodes[op] = true
}
@@ -206,28 +204,14 @@ func clobber(t *testing.T, src string, dst *os.File, opcodes map[string]bool) {
f.Close()
}
-func setOf(keys ...string) map[string]bool {
- m := make(map[string]bool, len(keys))
- for _, key := range keys {
- m[key] = true
- }
- return m
-}
-
-var runtimeFeatures = setOf(
- "adx", "aes", "avx", "avx2", "bmi1", "bmi2", "erms", "fma",
- "pclmulqdq", "popcnt", "rdtscp", "sse3", "sse41", "sse42", "ssse3",
-)
-
var featureToOpcodes = map[string][]string{
// Note: we include *q, *l, and plain opcodes here.
// go tool objdump doesn't include a [QL] on popcnt instructions, until CL 351889
// native objdump doesn't include [QL] on linux.
- "popcnt": {"popcntq", "popcntl", "popcnt"},
- "bmi1": {"andnq", "andnl", "andn", "blsiq", "blsil", "blsi", "blsmskq", "blsmskl", "blsmsk", "blsrq", "blsrl", "blsr", "tzcntq", "tzcntl", "tzcnt"},
- "sse41": {"roundsd"},
- "fma": {"vfmadd231sd"},
- "movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"},
+ "popcnt": []string{"popcntq", "popcntl", "popcnt"},
+ "bmi1": []string{"andnq", "andnl", "andn", "blsiq", "blsil", "blsi", "blsmskq", "blsmskl", "blsmsk", "blsrq", "blsrl", "blsr", "tzcntq", "tzcntl", "tzcnt"},
+ "sse41": []string{"roundsd"},
+ "fma": []string{"vfmadd231sd"},
}
// Test to use POPCNT instruction, if available
@@ -380,4 +364,5 @@ func TestFMA(t *testing.T) {
t.Errorf("FMA(%f,%f,%f) = %f, want %f", tt.x, tt.y, tt.z, got, tt.want)
}
}
+
}
diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules
index 507d701999..8b73ee14ea 100644
--- a/src/cmd/compile/internal/ssa/gen/AMD64.rules
+++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules
@@ -2219,29 +2219,3 @@
(AND(Q|L) x (ADD(Q|L)const [-1] x)) && buildcfg.GOAMD64 >= 3 => (BLSR(Q|L) x)
(BSWAP(Q|L) (BSWAP(Q|L) p)) => p
-
-// CPUID feature: MOVBE.
-(MOV(Q|L)store [i] {s} p x:(BSWAP(Q|L) w) mem) && x.Uses == 1 && buildcfg.GOAMD64 >= 3 => (MOVBE(Q|L)store [i] {s} p w mem)
-(BSWAP(Q|L) x:(MOV(Q|L)load [i] {s} p mem)) && x.Uses == 1 && buildcfg.GOAMD64 >= 3 => (MOVBE(Q|L)load [i] {s} p mem)
-(BSWAP(Q|L) (MOVBE(Q|L)load [i] {s} p m)) => (MOV(Q|L)load [i] {s} p m)
-(MOVBE(Q|L)store [i] {s} p (BSWAP(Q|L) x) m) => (MOV(Q|L)store [i] {s} p x m)
-
-(ORQ x0:(MOVBELload [i0] {s} p mem)
- sh:(SHLQconst [32] x1:(MOVBELload [i1] {s} p mem)))
- && i0 == i1+4
- && x0.Uses == 1
- && x1.Uses == 1
- && sh.Uses == 1
- && mergePoint(b,x0,x1) != nil
- && clobber(x0, x1, sh)
- => @mergePoint(b,x0,x1) (MOVBEQload [i1] {s} p mem)
-
-(ORQ x0:(MOVBELload [i] {s} p0 mem)
- sh:(SHLQconst [32] x1:(MOVBELload [i] {s} p1 mem)))
- && x0.Uses == 1
- && x1.Uses == 1
- && sh.Uses == 1
- && sequentialAddresses(p1, p0, 4)
- && mergePoint(b,x0,x1) != nil
- && clobber(x0, x1, sh)
- => @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p0 mem)
diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
index e3c94e4b2e..731454c761 100644
--- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
+++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
@@ -922,12 +922,6 @@ func init() {
// and BSFQ(0) is undefined. Same for TZCNTL(0)==32
{name: "TZCNTQ", argLength: 1, reg: gp11, asm: "TZCNTQ", clobberFlags: true},
{name: "TZCNTL", argLength: 1, reg: gp11, asm: "TZCNTL", clobberFlags: true},
-
- // CPUID feature: MOVBE
- {name: "MOVBELload", argLength: 2, reg: gpload, asm: "MOVBEL", aux: "SymOff", typ: "UInt32", faultOnNilArg0: true, symEffect: "Read"}, // load and swap 4 bytes from arg0+auxint+aux. arg1=mem. Zero extend.
- {name: "MOVBELstore", argLength: 3, reg: gpstore, asm: "MOVBEL", aux: "SymOff", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // swap and store 4 bytes in arg1 to arg0+auxint+aux. arg2=mem
- {name: "MOVBEQload", argLength: 2, reg: gpload, asm: "MOVBEQ", aux: "SymOff", typ: "UInt64", faultOnNilArg0: true, symEffect: "Read"}, // load and swap 8 bytes from arg0+auxint+aux. arg1=mem
- {name: "MOVBEQstore", argLength: 3, reg: gpstore, asm: "MOVBEQ", aux: "SymOff", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // swap and store 8 bytes in arg1 to arg0+auxint+aux. arg2=mem
}
var AMD64blocks = []blockData{
diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go
index 091f43f40a..640e517fe7 100644
--- a/src/cmd/compile/internal/ssa/opGen.go
+++ b/src/cmd/compile/internal/ssa/opGen.go
@@ -1043,10 +1043,6 @@ const (
OpAMD64BLSRL
OpAMD64TZCNTQ
OpAMD64TZCNTL
- OpAMD64MOVBELload
- OpAMD64MOVBELstore
- OpAMD64MOVBEQload
- OpAMD64MOVBEQstore
OpARMADD
OpARMADDconst
@@ -13784,66 +13780,6 @@ var opcodeTable = [...]opInfo{
},
},
},
- {
- name: "MOVBELload",
- auxType: auxSymOff,
- argLen: 2,
- faultOnNilArg0: true,
- symEffect: SymRead,
- asm: x86.AMOVBEL,
- reg: regInfo{
- inputs: []inputInfo{
- {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
- },
- outputs: []outputInfo{
- {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
- },
- },
- },
- {
- name: "MOVBELstore",
- auxType: auxSymOff,
- argLen: 3,
- faultOnNilArg0: true,
- symEffect: SymWrite,
- asm: x86.AMOVBEL,
- reg: regInfo{
- inputs: []inputInfo{
- {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15
- {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
- },
- },
- },
- {
- name: "MOVBEQload",
- auxType: auxSymOff,
- argLen: 2,
- faultOnNilArg0: true,
- symEffect: SymRead,
- asm: x86.AMOVBEQ,
- reg: regInfo{
- inputs: []inputInfo{
- {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
- },
- outputs: []outputInfo{
- {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
- },
- },
- },
- {
- name: "MOVBEQstore",
- auxType: auxSymOff,
- argLen: 3,
- faultOnNilArg0: true,
- symEffect: SymWrite,
- asm: x86.AMOVBEQ,
- reg: regInfo{
- inputs: []inputInfo{
- {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15
- {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
- },
- },
- },
{
name: "ADD",
diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go
index 88b545a465..201fbf2954 100644
--- a/src/cmd/compile/internal/ssa/rewriteAMD64.go
+++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go
@@ -222,10 +222,6 @@ func rewriteValueAMD64(v *Value) bool {
return rewriteValueAMD64_OpAMD64LEAQ4(v)
case OpAMD64LEAQ8:
return rewriteValueAMD64_OpAMD64LEAQ8(v)
- case OpAMD64MOVBELstore:
- return rewriteValueAMD64_OpAMD64MOVBELstore(v)
- case OpAMD64MOVBEQstore:
- return rewriteValueAMD64_OpAMD64MOVBEQstore(v)
case OpAMD64MOVBQSX:
return rewriteValueAMD64_OpAMD64MOVBQSX(v)
case OpAMD64MOVBQSXload:
@@ -3627,43 +3623,6 @@ func rewriteValueAMD64_OpAMD64BSWAPL(v *Value) bool {
v.copyOf(p)
return true
}
- // match: (BSWAPL x:(MOVLload [i] {s} p mem))
- // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
- // result: (MOVBELload [i] {s} p mem)
- for {
- x := v_0
- if x.Op != OpAMD64MOVLload {
- break
- }
- i := auxIntToInt32(x.AuxInt)
- s := auxToSym(x.Aux)
- mem := x.Args[1]
- p := x.Args[0]
- if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
- break
- }
- v.reset(OpAMD64MOVBELload)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg2(p, mem)
- return true
- }
- // match: (BSWAPL (MOVBELload [i] {s} p m))
- // result: (MOVLload [i] {s} p m)
- for {
- if v_0.Op != OpAMD64MOVBELload {
- break
- }
- i := auxIntToInt32(v_0.AuxInt)
- s := auxToSym(v_0.Aux)
- m := v_0.Args[1]
- p := v_0.Args[0]
- v.reset(OpAMD64MOVLload)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg2(p, m)
- return true
- }
return false
}
func rewriteValueAMD64_OpAMD64BSWAPQ(v *Value) bool {
@@ -3678,43 +3637,6 @@ func rewriteValueAMD64_OpAMD64BSWAPQ(v *Value) bool {
v.copyOf(p)
return true
}
- // match: (BSWAPQ x:(MOVQload [i] {s} p mem))
- // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
- // result: (MOVBEQload [i] {s} p mem)
- for {
- x := v_0
- if x.Op != OpAMD64MOVQload {
- break
- }
- i := auxIntToInt32(x.AuxInt)
- s := auxToSym(x.Aux)
- mem := x.Args[1]
- p := x.Args[0]
- if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
- break
- }
- v.reset(OpAMD64MOVBEQload)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg2(p, mem)
- return true
- }
- // match: (BSWAPQ (MOVBEQload [i] {s} p m))
- // result: (MOVQload [i] {s} p m)
- for {
- if v_0.Op != OpAMD64MOVBEQload {
- break
- }
- i := auxIntToInt32(v_0.AuxInt)
- s := auxToSym(v_0.Aux)
- m := v_0.Args[1]
- p := v_0.Args[0]
- v.reset(OpAMD64MOVQload)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg2(p, m)
- return true
- }
return false
}
func rewriteValueAMD64_OpAMD64BTCLconst(v *Value) bool {
@@ -9473,52 +9395,6 @@ func rewriteValueAMD64_OpAMD64LEAQ8(v *Value) bool {
}
return false
}
-func rewriteValueAMD64_OpAMD64MOVBELstore(v *Value) bool {
- v_2 := v.Args[2]
- v_1 := v.Args[1]
- v_0 := v.Args[0]
- // match: (MOVBELstore [i] {s} p (BSWAPL x) m)
- // result: (MOVLstore [i] {s} p x m)
- for {
- i := auxIntToInt32(v.AuxInt)
- s := auxToSym(v.Aux)
- p := v_0
- if v_1.Op != OpAMD64BSWAPL {
- break
- }
- x := v_1.Args[0]
- m := v_2
- v.reset(OpAMD64MOVLstore)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg3(p, x, m)
- return true
- }
- return false
-}
-func rewriteValueAMD64_OpAMD64MOVBEQstore(v *Value) bool {
- v_2 := v.Args[2]
- v_1 := v.Args[1]
- v_0 := v.Args[0]
- // match: (MOVBEQstore [i] {s} p (BSWAPQ x) m)
- // result: (MOVQstore [i] {s} p x m)
- for {
- i := auxIntToInt32(v.AuxInt)
- s := auxToSym(v.Aux)
- p := v_0
- if v_1.Op != OpAMD64BSWAPQ {
- break
- }
- x := v_1.Args[0]
- m := v_2
- v.reset(OpAMD64MOVQstore)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg3(p, x, m)
- return true
- }
- return false
-}
func rewriteValueAMD64_OpAMD64MOVBQSX(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
@@ -12349,28 +12225,6 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool {
v.AddArg3(ptr, val, mem)
return true
}
- // match: (MOVLstore [i] {s} p x:(BSWAPL w) mem)
- // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
- // result: (MOVBELstore [i] {s} p w mem)
- for {
- i := auxIntToInt32(v.AuxInt)
- s := auxToSym(v.Aux)
- p := v_0
- x := v_1
- if x.Op != OpAMD64BSWAPL {
- break
- }
- w := x.Args[0]
- mem := v_2
- if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
- break
- }
- v.reset(OpAMD64MOVBELstore)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg3(p, w, mem)
- return true
- }
return false
}
func rewriteValueAMD64_OpAMD64MOVLstoreconst(v *Value) bool {
@@ -13310,28 +13164,6 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool {
v.AddArg3(ptr, val, mem)
return true
}
- // match: (MOVQstore [i] {s} p x:(BSWAPQ w) mem)
- // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
- // result: (MOVBEQstore [i] {s} p w mem)
- for {
- i := auxIntToInt32(v.AuxInt)
- s := auxToSym(v.Aux)
- p := v_0
- x := v_1
- if x.Op != OpAMD64BSWAPQ {
- break
- }
- w := x.Args[0]
- mem := v_2
- if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
- break
- }
- v.reset(OpAMD64MOVBEQstore)
- v.AuxInt = int32ToAuxInt(i)
- v.Aux = symToAux(s)
- v.AddArg3(p, w, mem)
- return true
- }
return false
}
func rewriteValueAMD64_OpAMD64MOVQstoreconst(v *Value) bool {
@@ -18825,81 +18657,6 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value) bool {
}
break
}
- // match: (ORQ x0:(MOVBELload [i0] {s} p mem) sh:(SHLQconst [32] x1:(MOVBELload [i1] {s} p mem)))
- // cond: i0 == i1+4 && x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && mergePoint(b,x0,x1) != nil && clobber(x0, x1, sh)
- // result: @mergePoint(b,x0,x1) (MOVBEQload [i1] {s} p mem)
- for {
- for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
- x0 := v_0
- if x0.Op != OpAMD64MOVBELload {
- continue
- }
- i0 := auxIntToInt32(x0.AuxInt)
- s := auxToSym(x0.Aux)
- mem := x0.Args[1]
- p := x0.Args[0]
- sh := v_1
- if sh.Op != OpAMD64SHLQconst || auxIntToInt8(sh.AuxInt) != 32 {
- continue
- }
- x1 := sh.Args[0]
- if x1.Op != OpAMD64MOVBELload {
- continue
- }
- i1 := auxIntToInt32(x1.AuxInt)
- if auxToSym(x1.Aux) != s {
- continue
- }
- _ = x1.Args[1]
- if p != x1.Args[0] || mem != x1.Args[1] || !(i0 == i1+4 && x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && mergePoint(b, x0, x1) != nil && clobber(x0, x1, sh)) {
- continue
- }
- b = mergePoint(b, x0, x1)
- v0 := b.NewValue0(x1.Pos, OpAMD64MOVBEQload, typ.UInt64)
- v.copyOf(v0)
- v0.AuxInt = int32ToAuxInt(i1)
- v0.Aux = symToAux(s)
- v0.AddArg2(p, mem)
- return true
- }
- break
- }
- // match: (ORQ x0:(MOVBELload [i] {s} p0 mem) sh:(SHLQconst [32] x1:(MOVBELload [i] {s} p1 mem)))
- // cond: x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && sequentialAddresses(p1, p0, 4) && mergePoint(b,x0,x1) != nil && clobber(x0, x1, sh)
- // result: @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p0 mem)
- for {
- for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
- x0 := v_0
- if x0.Op != OpAMD64MOVBELload {
- continue
- }
- i := auxIntToInt32(x0.AuxInt)
- s := auxToSym(x0.Aux)
- mem := x0.Args[1]
- p0 := x0.Args[0]
- sh := v_1
- if sh.Op != OpAMD64SHLQconst || auxIntToInt8(sh.AuxInt) != 32 {
- continue
- }
- x1 := sh.Args[0]
- if x1.Op != OpAMD64MOVBELload || auxIntToInt32(x1.AuxInt) != i || auxToSym(x1.Aux) != s {
- continue
- }
- _ = x1.Args[1]
- p1 := x1.Args[0]
- if mem != x1.Args[1] || !(x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && sequentialAddresses(p1, p0, 4) && mergePoint(b, x0, x1) != nil && clobber(x0, x1, sh)) {
- continue
- }
- b = mergePoint(b, x0, x1)
- v0 := b.NewValue0(x1.Pos, OpAMD64MOVBEQload, typ.UInt64)
- v.copyOf(v0)
- v0.AuxInt = int32ToAuxInt(i)
- v0.Aux = symToAux(s)
- v0.AddArg2(p0, mem)
- return true
- }
- break
- }
return false
}
func rewriteValueAMD64_OpAMD64ORQconst(v *Value) bool {
diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go
index 97e1d4bdfb..2a0c534df0 100644
--- a/test/codegen/memcombine.go
+++ b/test/codegen/memcombine.go
@@ -70,8 +70,7 @@ func load_le16_idx(b []byte, idx int) {
}
func load_be64(b []byte) {
- // amd64/v1,amd64/v2:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
- // amd64/v3:`MOVBEQ`
+ // amd64:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
// s390x:`MOVD\s\(.*\),`
// arm64:`REV`,`MOVD\s\(R[0-9]+\),`,-`MOV[BHW]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`,-`MOV[BHW]Z`
@@ -79,8 +78,7 @@ func load_be64(b []byte) {
}
func load_be64_idx(b []byte, idx int) {
- // amd64/v1,amd64/v2:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
- // amd64/v3: `MOVBEQ`
+ // amd64:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
// s390x:`MOVD\s\(.*\)\(.*\*1\),`
// arm64:`REV`,`MOVD\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[WHB]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`,-`MOV[BHW]Z`
@@ -88,8 +86,7 @@ func load_be64_idx(b []byte, idx int) {
}
func load_be32(b []byte) {
- // amd64/v1,amd64/v2:`BSWAPL`,-`MOV[BW]`,-`OR`
- // amd64/v3: `MOVBEL`
+ // amd64:`BSWAPL`,-`MOV[BW]`,-`OR`
// s390x:`MOVWZ\s\(.*\),`
// arm64:`REVW`,`MOVWU\s\(R[0-9]+\),`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`,-`MOV[BH]Z`
@@ -97,8 +94,7 @@ func load_be32(b []byte) {
}
func load_be32_idx(b []byte, idx int) {
- // amd64/v1,amd64/v2:`BSWAPL`,-`MOV[BW]`,-`OR`
- // amd64/v3: `MOVBEL`
+ // amd64:`BSWAPL`,-`MOV[BW]`,-`OR`
// s390x:`MOVWZ\s\(.*\)\(.*\*1\),`
// arm64:`REVW`,`MOVWU\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[HB]`,-`REV16W`
// ppc64le:`MOVWBR`,-`MOV[BH]Z`
@@ -183,8 +179,7 @@ func load_be_byte4_uint32(s []byte) uint32 {
func load_be_byte4_uint32_inv(s []byte) uint32 {
// arm64:`MOVWU\t\(R[0-9]+\)`,`REVW`,-`ORR`,-`REV16W`,-`MOV[BH]`
- // amd64/v1,amd64/v2:`MOVL\s\([A-Z]+\)`,`BSWAPL`,-`MOV[BW]`,-`OR`
- // amd64/v3: `MOVBEL`
+ // amd64:`MOVL\s\([A-Z]+\)`,`BSWAPL`,-`MOV[BW]`,-`OR`
return uint32(s[3]) | uint32(s[2])<<8 | uint32(s[1])<<16 | uint32(s[0])<<24
}
@@ -196,8 +191,7 @@ func load_be_byte8_uint64(s []byte) uint64 {
func load_be_byte8_uint64_inv(s []byte) uint64 {
// arm64:`MOVD\t\(R[0-9]+\)`,`REV`,-`ORR`,-`REVW`,-`REV16W`,-`MOV[BHW]`
- // amd64/v1,amd64/v2:`MOVQ\s\([A-Z]+\),\s[A-Z]+`,`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
- // amd64/v3: `MOVBEQ`
+ // amd64:`MOVQ\s\([A-Z]+\),\s[A-Z]+`,`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
// ppc64le:`MOVDBR\t\(R[0-9]+\)`,-`MOV[BHW]Z`
return uint64(s[7]) | uint64(s[6])<<8 | uint64(s[5])<<16 | uint64(s[4])<<24 | uint64(s[3])<<32 | uint64(s[2])<<40 | uint64(s[1])<<48 | uint64(s[0])<<56
}
@@ -415,8 +409,7 @@ func store_le16_idx(b []byte, idx int) {
}
func store_be64(b []byte) {
- // amd64/v1,amd64/v2:`BSWAPQ`,-`SHR.`
- // amd64/v3: `MOVBEQ`
+ // amd64:`BSWAPQ`,-`SHR.`
// arm64:`MOVD`,`REV`,-`MOV[WBH]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`
// s390x:`MOVD\s.*\(.*\)$`,-`SRW\s`,-`SRD\s`
@@ -424,8 +417,7 @@ func store_be64(b []byte) {
}
func store_be64_idx(b []byte, idx int) {
- // amd64/v1,amd64/v2:`BSWAPQ`,-`SHR.`
- // amd64/v3:`MOVBEQ`
+ // amd64:`BSWAPQ`,-`SHR.`
// arm64:`REV`,`MOVD\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BHW]`,-`REV16W`,-`REVW`
// ppc64le:`MOVDBR`
// s390x:`MOVD\s.*\(.*\)\(.*\*1\)$`,-`SRW\s`,-`SRD\s`
@@ -433,8 +425,7 @@ func store_be64_idx(b []byte, idx int) {
}
func store_be32(b []byte) {
- // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.`
- // amd64/v3:`MOVBEL`
+ // amd64:`BSWAPL`,-`SHR.`
// arm64:`MOVW`,`REVW`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`
// s390x:`MOVW\s.*\(.*\)$`,-`SRW\s`,-`SRD\s`
@@ -454,8 +445,7 @@ func store_be32_load(b, x *[8]byte) {
}
func store_be32_idx(b []byte, idx int) {
- // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.`
- // amd64/v3:`MOVBEL`
+ // amd64:`BSWAPL`,-`SHR.`
// arm64:`REVW`,`MOVW\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`
// s390x:`MOVW\s.*\(.*\)\(.*\*1\)$`,-`SRW\s`,-`SRD\s`
@@ -518,16 +508,14 @@ func store_be_byte_2(b []byte, val uint16) {
func store_be_byte_4(b []byte, val uint32) {
_ = b[4]
// arm64:`REVW`,`MOVW\sR[0-9]+,\s1\(R[0-9]+\)`,-`MOVB`,-`MOVH`,-`REV16W`
- // amd64/v1,amd64/v2:`MOVL\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`
- // amd64/v3:`MOVBEL\s[A-Z]+,\s1\([A-Z]+\)`
+ // amd64:`MOVL\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`
b[1], b[2], b[3], b[4] = byte(val>>24), byte(val>>16), byte(val>>8), byte(val)
}
func store_be_byte_8(b []byte, val uint64) {
_ = b[8]
// arm64:`REV`,`MOVD\sR[0-9]+,\s1\(R[0-9]+\)`,-`MOVB`,-`MOVH`,-`MOVW`,-`REV16W`,-`REVW`
- // amd64/v1,amd64/v2:`MOVQ\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`,-`MOVL`
- // amd64/v3:`MOVBEQ\s[A-Z]+,\s1\([A-Z]+\)`, -`MOVBEL`
+ // amd64:`MOVQ\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`,-`MOVL`
b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8] = byte(val>>56), byte(val>>48), byte(val>>40), byte(val>>32), byte(val>>24), byte(val>>16), byte(val>>8), byte(val)
}
From bde0463da3f31934791b0bb2ccacdf6206314073 Mon Sep 17 00:00:00 2001
From: Joel Sing
Date: Sat, 16 Oct 2021 03:59:41 +1100
Subject: [PATCH 065/406] cmd/internal/obj/riscv: fix trampoline calls from
large functions
On riscv64, the JAL instruction is only capable of reaching +/-1MB. In the case where
a single function and its trampolines exceeds this size, it is possible that the JAL
is unable to reach the trampoline, which is laid down after the function text. In the
case of large functions, switch back to using a AUIPC+JALR pairs rather than using
trampolines.
Fixes #48791
Change-Id: I119cf3bc20ce4933a9b7ab41a8e514437c6addb9
Reviewed-on: https://go-review.googlesource.com/c/go/+/356250
Trust: Joel Sing
Reviewed-by: Cherry Mui
---
src/cmd/internal/obj/riscv/asm_test.go | 95 ++++++++++++++++++++++----
src/cmd/internal/obj/riscv/obj.go | 36 ++++++++--
2 files changed, 113 insertions(+), 18 deletions(-)
diff --git a/src/cmd/internal/obj/riscv/asm_test.go b/src/cmd/internal/obj/riscv/asm_test.go
index 684c6b61f2..b23142dbe8 100644
--- a/src/cmd/internal/obj/riscv/asm_test.go
+++ b/src/cmd/internal/obj/riscv/asm_test.go
@@ -16,32 +16,30 @@ import (
"testing"
)
-// TestLarge generates a very large file to verify that large
-// program builds successfully, in particular, too-far
-// conditional branches are fixed.
-func TestLarge(t *testing.T) {
+// TestLargeBranch generates a large function with a very far conditional
+// branch, in order to ensure that it assembles successfully.
+func TestLargeBranch(t *testing.T) {
if testing.Short() {
- t.Skip("Skip in short mode")
+ t.Skip("Skipping test in short mode")
}
testenv.MustHaveGoBuild(t)
- dir, err := ioutil.TempDir("", "testlarge")
+ dir, err := ioutil.TempDir("", "testlargebranch")
if err != nil {
- t.Fatalf("could not create directory: %v", err)
+ t.Fatalf("Could not create directory: %v", err)
}
defer os.RemoveAll(dir)
// Generate a very large function.
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
- gen(buf)
+ genLargeBranch(buf)
tmpfile := filepath.Join(dir, "x.s")
- err = ioutil.WriteFile(tmpfile, buf.Bytes(), 0644)
- if err != nil {
- t.Fatalf("can't write output: %v\n", err)
+ if err := ioutil.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
+ t.Fatalf("Failed to write file: %v", err)
}
- // Build generated file.
+ // Assemble generated file.
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
out, err := cmd.CombinedOutput()
@@ -50,8 +48,7 @@ func TestLarge(t *testing.T) {
}
}
-// gen generates a very large program, with a very far conditional branch.
-func gen(buf *bytes.Buffer) {
+func genLargeBranch(buf *bytes.Buffer) {
fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
fmt.Fprintln(buf, "BEQ X0, X0, label")
for i := 0; i < 1<<19; i++ {
@@ -61,6 +58,76 @@ func gen(buf *bytes.Buffer) {
fmt.Fprintln(buf, "ADD $0, X0, X0")
}
+// TestLargeCall generates a large function (>1MB of text) with a call to
+// a following function, in order to ensure that it assembles and links
+// correctly.
+func TestLargeCall(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping test in short mode")
+ }
+ testenv.MustHaveGoBuild(t)
+
+ dir, err := ioutil.TempDir("", "testlargecall")
+ if err != nil {
+ t.Fatalf("could not create directory: %v", err)
+ }
+ defer os.RemoveAll(dir)
+
+ if err := ioutil.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largecall"), 0644); err != nil {
+ t.Fatalf("Failed to write file: %v\n", err)
+ }
+ main := `package main
+func main() {
+ x()
+}
+
+func x()
+func y()
+`
+ if err := ioutil.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
+ t.Fatalf("failed to write main: %v\n", err)
+ }
+
+ // Generate a very large function with call.
+ buf := bytes.NewBuffer(make([]byte, 0, 7000000))
+ genLargeCall(buf)
+
+ if err := ioutil.WriteFile(filepath.Join(dir, "x.s"), buf.Bytes(), 0644); err != nil {
+ t.Fatalf("Failed to write file: %v\n", err)
+ }
+
+ // Build generated files.
+ cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Errorf("Build failed: %v, output: %s", err, out)
+ }
+
+ if runtime.GOARCH == "riscv64" && testenv.HasCGO() {
+ cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-linkmode=external")
+ cmd.Dir = dir
+ cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Errorf("Build failed: %v, output: %s", err, out)
+ }
+ }
+}
+
+func genLargeCall(buf *bytes.Buffer) {
+ fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-0")
+ fmt.Fprintln(buf, "CALL ·y(SB)")
+ for i := 0; i < 1<<19; i++ {
+ fmt.Fprintln(buf, "ADD $0, X0, X0")
+ }
+ fmt.Fprintln(buf, "RET")
+ fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
+ fmt.Fprintln(buf, "ADD $0, X0, X0")
+ fmt.Fprintln(buf, "RET")
+}
+
// Issue 20348.
func TestNoRet(t *testing.T) {
dir, err := ioutil.TempDir("", "testnoret")
diff --git a/src/cmd/internal/obj/riscv/obj.go b/src/cmd/internal/obj/riscv/obj.go
index b346b13577..d98806edb5 100644
--- a/src/cmd/internal/obj/riscv/obj.go
+++ b/src/cmd/internal/obj/riscv/obj.go
@@ -280,14 +280,15 @@ func containsCall(sym *obj.LSym) bool {
}
// setPCs sets the Pc field in all instructions reachable from p.
-// It uses pc as the initial value.
-func setPCs(p *obj.Prog, pc int64) {
+// It uses pc as the initial value and returns the next available pc.
+func setPCs(p *obj.Prog, pc int64) int64 {
for ; p != nil; p = p.Link {
p.Pc = pc
for _, ins := range instructionsForProg(p) {
pc += int64(ins.length())
}
}
+ return pc
}
// stackOffset updates Addr offsets based on the current stack size.
@@ -582,17 +583,26 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
}
}
+ var callCount int
for p := cursym.Func().Text; p != nil; p = p.Link {
markRelocs(p)
+ if p.Mark&NEED_CALL_RELOC == NEED_CALL_RELOC {
+ callCount++
+ }
}
+ const callTrampSize = 8 // 2 machine instructions.
+ maxTrampSize := int64(callCount * callTrampSize)
// Compute instruction addresses. Once we do that, we need to check for
// overextended jumps and branches. Within each iteration, Pc differences
// are always lower bounds (since the program gets monotonically longer,
// a fixed point will be reached). No attempt to handle functions > 2GiB.
for {
- rescan := false
- setPCs(cursym.Func().Text, 0)
+ big, rescan := false, false
+ maxPC := setPCs(cursym.Func().Text, 0)
+ if maxPC+maxTrampSize > (1 << 20) {
+ big = true
+ }
for p := cursym.Func().Text; p != nil; p = p.Link {
switch p.As {
@@ -619,6 +629,24 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
case AJAL:
// Linker will handle the intersymbol case and trampolines.
if p.To.Target() == nil {
+ if !big {
+ break
+ }
+ // This function is going to be too large for JALs
+ // to reach trampolines. Replace with AUIPC+JALR.
+ jmp := obj.Appendp(p, newprog)
+ jmp.As = AJALR
+ jmp.From = p.From
+ jmp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
+
+ p.As = AAUIPC
+ p.Mark = (p.Mark &^ NEED_CALL_RELOC) | NEED_PCREL_ITYPE_RELOC
+ p.SetFrom3(obj.Addr{Type: obj.TYPE_CONST, Offset: p.To.Offset, Sym: p.To.Sym})
+ p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: 0}
+ p.Reg = obj.REG_NONE
+ p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
+
+ rescan = true
break
}
offset := p.To.Target().Pc - p.Pc
From 6294207a1c79e318124850155c7b6c23997c8c13 Mon Sep 17 00:00:00 2001
From: "Bryan C. Mills"
Date: Mon, 18 Oct 2021 16:15:43 -0400
Subject: [PATCH 066/406] cmd/go: skip flaky fuzz tests
(Temporarily, until they can be fixed.)
For #49046
For #49047
Change-Id: Ib580a5e45a0955aabdfc1899ed38a262a37f66ab
Reviewed-on: https://go-review.googlesource.com/c/go/+/356649
Trust: Bryan C. Mills
Run-TryBot: Bryan C. Mills
TryBot-Result: Go Bot
Reviewed-by: Roland Shoemaker
Reviewed-by: Katie Hockman
---
src/cmd/go/testdata/script/test_fuzz_fuzztime.txt | 2 ++
src/cmd/go/testdata/script/test_fuzz_mutator_repeat.txt | 2 ++
2 files changed, 4 insertions(+)
diff --git a/src/cmd/go/testdata/script/test_fuzz_fuzztime.txt b/src/cmd/go/testdata/script/test_fuzz_fuzztime.txt
index 9c9972f9e9..6264aca17d 100644
--- a/src/cmd/go/testdata/script/test_fuzz_fuzztime.txt
+++ b/src/cmd/go/testdata/script/test_fuzz_fuzztime.txt
@@ -1,3 +1,5 @@
+skip # Flaky: https://golang.org/issue/49046
+
# TODO(jayconrod): support shared memory on more platforms.
[!darwin] [!linux] [!windows] skip
diff --git a/src/cmd/go/testdata/script/test_fuzz_mutator_repeat.txt b/src/cmd/go/testdata/script/test_fuzz_mutator_repeat.txt
index 0924ed37e6..f2952c349b 100644
--- a/src/cmd/go/testdata/script/test_fuzz_mutator_repeat.txt
+++ b/src/cmd/go/testdata/script/test_fuzz_mutator_repeat.txt
@@ -1,3 +1,5 @@
+skip # Flaky: https://golang.org/issue/49047
+
# TODO(jayconrod): support shared memory on more platforms.
[!darwin] [!linux] [!windows] skip
From 067d796549242bec2d33226c9da1e67f092a7be2 Mon Sep 17 00:00:00 2001
From: Katie Hockman
Date: Thu, 14 Oct 2021 12:32:58 -0400
Subject: [PATCH 067/406] testing: write output to buffer when fuzzing
Fixes #48709
Change-Id: Ia6376a2f792946498d6565a53605b3e6c985ea7c
Reviewed-on: https://go-review.googlesource.com/c/go/+/355909
Trust: Katie Hockman
Run-TryBot: Katie Hockman
TryBot-Result: Go Bot
Reviewed-by: Jay Conrod
Reviewed-by: Roland Shoemaker
Reviewed-by: Bryan C. Mills
---
.../go/testdata/script/test_fuzz_minimize.txt | 20 +++++++++++--
.../script/test_fuzz_minimize_interesting.txt | 2 +-
src/testing/fuzz.go | 28 +++++++++++++------
3 files changed, 39 insertions(+), 11 deletions(-)
diff --git a/src/cmd/go/testdata/script/test_fuzz_minimize.txt b/src/cmd/go/testdata/script/test_fuzz_minimize.txt
index 8b11621bbd..3293e878bb 100644
--- a/src/cmd/go/testdata/script/test_fuzz_minimize.txt
+++ b/src/cmd/go/testdata/script/test_fuzz_minimize.txt
@@ -31,7 +31,7 @@ stdout FAIL
! go test -fuzz=FuzzMinimizeZeroLimitSet -run=FuzzMinimizeZeroLimitSet -fuzztime=10000x -fuzzminimizetime=0x .
! stdout '^ok'
! stdout 'minimizing'
-stdout 'there was an Error'
+stdout -count=1 'there was an Error'
stdout FAIL
# Test that minimization is working for recoverable errors.
@@ -49,11 +49,27 @@ go run ./check_testdata FuzzMinimizerRecoverable 50
! go test -run=FuzzMinimizerRecoverable .
rm testdata
+# Test that minimization is working for recoverable errors. Run it with -v this
+# time to ensure the command line output still looks right.
+! go test -v -fuzz=FuzzMinimizerRecoverable -run=FuzzMinimizerRecoverable -fuzztime=10000x .
+! stdout '^ok'
+stdout 'got the minimum size!'
+# The error message that was printed should be for the one written to testdata.
+stdout 'contains a non-zero byte of length 50'
+stdout FAIL
+
+# Check that the bytes written to testdata are of length 50 (the minimum size)
+go run ./check_testdata FuzzMinimizerRecoverable 50
+
+# Test that re-running the minimized value causes a crash.
+! go test -run=FuzzMinimizerRecoverable .
+rm testdata
+
# Test that minimization doesn't run for non-recoverable errors.
! go test -fuzz=FuzzMinimizerNonrecoverable -run=FuzzMinimizerNonrecoverable -fuzztime=10000x .
! stdout '^ok'
! stdout 'minimizing'
-stdout 'fuzzing process terminated unexpectedly: exit status 99'
+stdout -count=1 'fuzzing process terminated unexpectedly: exit status 99'
stdout FAIL
# Check that re-running the value causes a crash.
diff --git a/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt b/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
index fc66201eb3..8ea4cdb8a5 100644
--- a/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
+++ b/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt
@@ -29,7 +29,7 @@ env GOCACHE=$WORK/gocache
! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.fuzztime=10000x -test.parallel=1
! stdout '^ok'
stdout 'got the minimum size!'
-stdout 'flaky failure'
+stdout -count=1 'flaky failure'
stdout FAIL
# Make sure the crash that was written will fail when run with go test
diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go
index 0429f8243d..d5cb5e853f 100644
--- a/src/testing/fuzz.go
+++ b/src/testing/fuzz.go
@@ -5,6 +5,7 @@
package testing
import (
+ "bytes"
"errors"
"flag"
"fmt"
@@ -367,14 +368,14 @@ func (f *F) Fuzz(ff interface{}) {
// run calls fn on a given input, as a subtest with its own T.
// run is analogous to T.Run. The test filtering and cleanup works similarly.
// fn is called in its own goroutine.
- run := func(e corpusEntry) error {
+ run := func(captureOut io.Writer, e corpusEntry) (ok bool) {
if e.Values == nil {
// The corpusEntry must have non-nil Values in order to run the
// test. If Values is nil, it is a bug in our code.
panic(fmt.Sprintf("corpus file %q was not unmarshaled", e.Path))
}
if shouldFailFast() {
- return nil
+ return true
}
testName := f.name
if e.Path != "" {
@@ -405,6 +406,10 @@ func (f *F) Fuzz(ff interface{}) {
},
context: f.testContext,
}
+ if captureOut != nil {
+ // t.parent aliases f.common.
+ t.parent.w = captureOut
+ }
t.w = indenter{&t.common}
if t.chatty != nil {
// TODO(#48132): adjust this to work with test2json.
@@ -426,10 +431,7 @@ func (f *F) Fuzz(ff interface{}) {
})
<-t.signal
f.inFuzzFn = false
- if t.Failed() {
- return errors.New(string(f.output))
- }
- return nil
+ return !t.Failed()
}
switch f.fuzzContext.mode {
@@ -466,7 +468,17 @@ func (f *F) Fuzz(ff interface{}) {
case fuzzWorker:
// Fuzzing is enabled, and this is a worker process. Follow instructions
// from the coordinator.
- if err := f.fuzzContext.deps.RunFuzzWorker(run); err != nil {
+ if err := f.fuzzContext.deps.RunFuzzWorker(func(e corpusEntry) error {
+ // Don't write to f.w (which points to Stdout) if running from a
+ // fuzz worker. This would become very verbose, particularly during
+ // minimization. Return the error instead, and let the caller deal
+ // with the output.
+ var buf bytes.Buffer
+ if ok := run(&buf, e); !ok {
+ return errors.New(buf.String())
+ }
+ return nil
+ }); err != nil {
// Internal errors are marked with f.Fail; user code may call this too, before F.Fuzz.
// The worker will exit with fuzzWorkerExitCode, indicating this is a failure
// (and 'go test' should exit non-zero) but a crasher should not be recorded.
@@ -479,7 +491,7 @@ func (f *F) Fuzz(ff interface{}) {
for _, e := range f.corpus {
name := fmt.Sprintf("%s/%s", f.name, filepath.Base(e.Path))
if _, ok, _ := f.testContext.match.fullName(nil, name); ok {
- run(e)
+ run(f.w, e)
}
}
}
From fe7df4c4d043fc65800bbec7f575c1ba50327aa9 Mon Sep 17 00:00:00 2001
From: wdvxdr
Date: Tue, 19 Oct 2021 19:39:21 +0800
Subject: [PATCH 068/406] cmd/compile: use MOVBE instruction for GOAMD64>=v3
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
In CL 354670, I copied some existing rules for convenience but forgot
to update the last rule which broke `GOAMD64=v3 ./make.bat`
Revive CL 354670
Change-Id: Ic1e2047c603f0122482a4b293ce1ef74d806c019
Reviewed-on: https://go-review.googlesource.com/c/go/+/356810
Reviewed-by: Daniel Martí
Reviewed-by: Keith Randall
Trust: Daniel Martí
Run-TryBot: Daniel Martí
TryBot-Result: Go Bot
---
src/cmd/compile/internal/amd64/ssa.go | 7 +-
.../compile/internal/amd64/versions_test.go | 27 +-
src/cmd/compile/internal/ssa/gen/AMD64.rules | 26 ++
src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 6 +
src/cmd/compile/internal/ssa/opGen.go | 64 +++++
src/cmd/compile/internal/ssa/rewriteAMD64.go | 243 ++++++++++++++++++
test/codegen/memcombine.go | 36 ++-
7 files changed, 389 insertions(+), 20 deletions(-)
diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go
index 0e74574422..b0e5c34030 100644
--- a/src/cmd/compile/internal/amd64/ssa.go
+++ b/src/cmd/compile/internal/amd64/ssa.go
@@ -772,7 +772,9 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.From.Val = math.Float64frombits(uint64(v.AuxInt))
p.To.Type = obj.TYPE_REG
p.To.Reg = x
- case ssa.OpAMD64MOVQload, ssa.OpAMD64MOVSSload, ssa.OpAMD64MOVSDload, ssa.OpAMD64MOVLload, ssa.OpAMD64MOVWload, ssa.OpAMD64MOVBload, ssa.OpAMD64MOVBQSXload, ssa.OpAMD64MOVWQSXload, ssa.OpAMD64MOVLQSXload, ssa.OpAMD64MOVOload:
+ case ssa.OpAMD64MOVQload, ssa.OpAMD64MOVLload, ssa.OpAMD64MOVWload, ssa.OpAMD64MOVBload, ssa.OpAMD64MOVOload,
+ ssa.OpAMD64MOVSSload, ssa.OpAMD64MOVSDload, ssa.OpAMD64MOVBQSXload, ssa.OpAMD64MOVWQSXload, ssa.OpAMD64MOVLQSXload,
+ ssa.OpAMD64MOVBEQload, ssa.OpAMD64MOVBELload:
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_MEM
p.From.Reg = v.Args[0].Reg()
@@ -788,7 +790,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Reg = v.Reg()
case ssa.OpAMD64MOVQstore, ssa.OpAMD64MOVSSstore, ssa.OpAMD64MOVSDstore, ssa.OpAMD64MOVLstore, ssa.OpAMD64MOVWstore, ssa.OpAMD64MOVBstore, ssa.OpAMD64MOVOstore,
ssa.OpAMD64ADDQmodify, ssa.OpAMD64SUBQmodify, ssa.OpAMD64ANDQmodify, ssa.OpAMD64ORQmodify, ssa.OpAMD64XORQmodify,
- ssa.OpAMD64ADDLmodify, ssa.OpAMD64SUBLmodify, ssa.OpAMD64ANDLmodify, ssa.OpAMD64ORLmodify, ssa.OpAMD64XORLmodify:
+ ssa.OpAMD64ADDLmodify, ssa.OpAMD64SUBLmodify, ssa.OpAMD64ANDLmodify, ssa.OpAMD64ORLmodify, ssa.OpAMD64XORLmodify,
+ ssa.OpAMD64MOVBEQstore, ssa.OpAMD64MOVBELstore:
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_REG
p.From.Reg = v.Args[1].Reg()
diff --git a/src/cmd/compile/internal/amd64/versions_test.go b/src/cmd/compile/internal/amd64/versions_test.go
index de677f3a69..ee1a8ca3aa 100644
--- a/src/cmd/compile/internal/amd64/versions_test.go
+++ b/src/cmd/compile/internal/amd64/versions_test.go
@@ -53,7 +53,9 @@ func TestGoAMD64v1(t *testing.T) {
opcodes := map[string]bool{}
var features []string
for feature, opcodeList := range featureToOpcodes {
- features = append(features, fmt.Sprintf("cpu.%s=off", feature))
+ if runtimeFeatures[feature] {
+ features = append(features, fmt.Sprintf("cpu.%s=off", feature))
+ }
for _, op := range opcodeList {
opcodes[op] = true
}
@@ -204,14 +206,28 @@ func clobber(t *testing.T, src string, dst *os.File, opcodes map[string]bool) {
f.Close()
}
+func setOf(keys ...string) map[string]bool {
+ m := make(map[string]bool, len(keys))
+ for _, key := range keys {
+ m[key] = true
+ }
+ return m
+}
+
+var runtimeFeatures = setOf(
+ "adx", "aes", "avx", "avx2", "bmi1", "bmi2", "erms", "fma",
+ "pclmulqdq", "popcnt", "rdtscp", "sse3", "sse41", "sse42", "ssse3",
+)
+
var featureToOpcodes = map[string][]string{
// Note: we include *q, *l, and plain opcodes here.
// go tool objdump doesn't include a [QL] on popcnt instructions, until CL 351889
// native objdump doesn't include [QL] on linux.
- "popcnt": []string{"popcntq", "popcntl", "popcnt"},
- "bmi1": []string{"andnq", "andnl", "andn", "blsiq", "blsil", "blsi", "blsmskq", "blsmskl", "blsmsk", "blsrq", "blsrl", "blsr", "tzcntq", "tzcntl", "tzcnt"},
- "sse41": []string{"roundsd"},
- "fma": []string{"vfmadd231sd"},
+ "popcnt": {"popcntq", "popcntl", "popcnt"},
+ "bmi1": {"andnq", "andnl", "andn", "blsiq", "blsil", "blsi", "blsmskq", "blsmskl", "blsmsk", "blsrq", "blsrl", "blsr", "tzcntq", "tzcntl", "tzcnt"},
+ "sse41": {"roundsd"},
+ "fma": {"vfmadd231sd"},
+ "movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"},
}
// Test to use POPCNT instruction, if available
@@ -364,5 +380,4 @@ func TestFMA(t *testing.T) {
t.Errorf("FMA(%f,%f,%f) = %f, want %f", tt.x, tt.y, tt.z, got, tt.want)
}
}
-
}
diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules
index 8b73ee14ea..47a6af003c 100644
--- a/src/cmd/compile/internal/ssa/gen/AMD64.rules
+++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules
@@ -2219,3 +2219,29 @@
(AND(Q|L) x (ADD(Q|L)const [-1] x)) && buildcfg.GOAMD64 >= 3 => (BLSR(Q|L) x)
(BSWAP(Q|L) (BSWAP(Q|L) p)) => p
+
+// CPUID feature: MOVBE.
+(MOV(Q|L)store [i] {s} p x:(BSWAP(Q|L) w) mem) && x.Uses == 1 && buildcfg.GOAMD64 >= 3 => (MOVBE(Q|L)store [i] {s} p w mem)
+(BSWAP(Q|L) x:(MOV(Q|L)load [i] {s} p mem)) && x.Uses == 1 && buildcfg.GOAMD64 >= 3 => (MOVBE(Q|L)load [i] {s} p mem)
+(BSWAP(Q|L) (MOVBE(Q|L)load [i] {s} p m)) => (MOV(Q|L)load [i] {s} p m)
+(MOVBE(Q|L)store [i] {s} p (BSWAP(Q|L) x) m) => (MOV(Q|L)store [i] {s} p x m)
+
+(ORQ x0:(MOVBELload [i0] {s} p mem)
+ sh:(SHLQconst [32] x1:(MOVBELload [i1] {s} p mem)))
+ && i0 == i1+4
+ && x0.Uses == 1
+ && x1.Uses == 1
+ && sh.Uses == 1
+ && mergePoint(b,x0,x1) != nil
+ && clobber(x0, x1, sh)
+ => @mergePoint(b,x0,x1) (MOVBEQload [i1] {s} p mem)
+
+(ORQ x0:(MOVBELload [i] {s} p0 mem)
+ sh:(SHLQconst [32] x1:(MOVBELload [i] {s} p1 mem)))
+ && x0.Uses == 1
+ && x1.Uses == 1
+ && sh.Uses == 1
+ && sequentialAddresses(p1, p0, 4)
+ && mergePoint(b,x0,x1) != nil
+ && clobber(x0, x1, sh)
+ => @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p1 mem)
diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
index 731454c761..e3c94e4b2e 100644
--- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
+++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go
@@ -922,6 +922,12 @@ func init() {
// and BSFQ(0) is undefined. Same for TZCNTL(0)==32
{name: "TZCNTQ", argLength: 1, reg: gp11, asm: "TZCNTQ", clobberFlags: true},
{name: "TZCNTL", argLength: 1, reg: gp11, asm: "TZCNTL", clobberFlags: true},
+
+ // CPUID feature: MOVBE
+ {name: "MOVBELload", argLength: 2, reg: gpload, asm: "MOVBEL", aux: "SymOff", typ: "UInt32", faultOnNilArg0: true, symEffect: "Read"}, // load and swap 4 bytes from arg0+auxint+aux. arg1=mem. Zero extend.
+ {name: "MOVBELstore", argLength: 3, reg: gpstore, asm: "MOVBEL", aux: "SymOff", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // swap and store 4 bytes in arg1 to arg0+auxint+aux. arg2=mem
+ {name: "MOVBEQload", argLength: 2, reg: gpload, asm: "MOVBEQ", aux: "SymOff", typ: "UInt64", faultOnNilArg0: true, symEffect: "Read"}, // load and swap 8 bytes from arg0+auxint+aux. arg1=mem
+ {name: "MOVBEQstore", argLength: 3, reg: gpstore, asm: "MOVBEQ", aux: "SymOff", typ: "Mem", faultOnNilArg0: true, symEffect: "Write"}, // swap and store 8 bytes in arg1 to arg0+auxint+aux. arg2=mem
}
var AMD64blocks = []blockData{
diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go
index 640e517fe7..091f43f40a 100644
--- a/src/cmd/compile/internal/ssa/opGen.go
+++ b/src/cmd/compile/internal/ssa/opGen.go
@@ -1043,6 +1043,10 @@ const (
OpAMD64BLSRL
OpAMD64TZCNTQ
OpAMD64TZCNTL
+ OpAMD64MOVBELload
+ OpAMD64MOVBELstore
+ OpAMD64MOVBEQload
+ OpAMD64MOVBEQstore
OpARMADD
OpARMADDconst
@@ -13780,6 +13784,66 @@ var opcodeTable = [...]opInfo{
},
},
},
+ {
+ name: "MOVBELload",
+ auxType: auxSymOff,
+ argLen: 2,
+ faultOnNilArg0: true,
+ symEffect: SymRead,
+ asm: x86.AMOVBEL,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ outputs: []outputInfo{
+ {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
+ },
+ },
+ },
+ {
+ name: "MOVBELstore",
+ auxType: auxSymOff,
+ argLen: 3,
+ faultOnNilArg0: true,
+ symEffect: SymWrite,
+ asm: x86.AMOVBEL,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ },
+ },
+ {
+ name: "MOVBEQload",
+ auxType: auxSymOff,
+ argLen: 2,
+ faultOnNilArg0: true,
+ symEffect: SymRead,
+ asm: x86.AMOVBEQ,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ outputs: []outputInfo{
+ {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15
+ },
+ },
+ },
+ {
+ name: "MOVBEQstore",
+ auxType: auxSymOff,
+ argLen: 3,
+ faultOnNilArg0: true,
+ symEffect: SymWrite,
+ asm: x86.AMOVBEQ,
+ reg: regInfo{
+ inputs: []inputInfo{
+ {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15
+ {0, 4295032831}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 g R15 SB
+ },
+ },
+ },
{
name: "ADD",
diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go
index 201fbf2954..0c789d6b49 100644
--- a/src/cmd/compile/internal/ssa/rewriteAMD64.go
+++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go
@@ -222,6 +222,10 @@ func rewriteValueAMD64(v *Value) bool {
return rewriteValueAMD64_OpAMD64LEAQ4(v)
case OpAMD64LEAQ8:
return rewriteValueAMD64_OpAMD64LEAQ8(v)
+ case OpAMD64MOVBELstore:
+ return rewriteValueAMD64_OpAMD64MOVBELstore(v)
+ case OpAMD64MOVBEQstore:
+ return rewriteValueAMD64_OpAMD64MOVBEQstore(v)
case OpAMD64MOVBQSX:
return rewriteValueAMD64_OpAMD64MOVBQSX(v)
case OpAMD64MOVBQSXload:
@@ -3623,6 +3627,43 @@ func rewriteValueAMD64_OpAMD64BSWAPL(v *Value) bool {
v.copyOf(p)
return true
}
+ // match: (BSWAPL x:(MOVLload [i] {s} p mem))
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBELload [i] {s} p mem)
+ for {
+ x := v_0
+ if x.Op != OpAMD64MOVLload {
+ break
+ }
+ i := auxIntToInt32(x.AuxInt)
+ s := auxToSym(x.Aux)
+ mem := x.Args[1]
+ p := x.Args[0]
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBELload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, mem)
+ return true
+ }
+ // match: (BSWAPL (MOVBELload [i] {s} p m))
+ // result: (MOVLload [i] {s} p m)
+ for {
+ if v_0.Op != OpAMD64MOVBELload {
+ break
+ }
+ i := auxIntToInt32(v_0.AuxInt)
+ s := auxToSym(v_0.Aux)
+ m := v_0.Args[1]
+ p := v_0.Args[0]
+ v.reset(OpAMD64MOVLload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, m)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64BSWAPQ(v *Value) bool {
@@ -3637,6 +3678,43 @@ func rewriteValueAMD64_OpAMD64BSWAPQ(v *Value) bool {
v.copyOf(p)
return true
}
+ // match: (BSWAPQ x:(MOVQload [i] {s} p mem))
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBEQload [i] {s} p mem)
+ for {
+ x := v_0
+ if x.Op != OpAMD64MOVQload {
+ break
+ }
+ i := auxIntToInt32(x.AuxInt)
+ s := auxToSym(x.Aux)
+ mem := x.Args[1]
+ p := x.Args[0]
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBEQload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, mem)
+ return true
+ }
+ // match: (BSWAPQ (MOVBEQload [i] {s} p m))
+ // result: (MOVQload [i] {s} p m)
+ for {
+ if v_0.Op != OpAMD64MOVBEQload {
+ break
+ }
+ i := auxIntToInt32(v_0.AuxInt)
+ s := auxToSym(v_0.Aux)
+ m := v_0.Args[1]
+ p := v_0.Args[0]
+ v.reset(OpAMD64MOVQload)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg2(p, m)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64BTCLconst(v *Value) bool {
@@ -9395,6 +9473,52 @@ func rewriteValueAMD64_OpAMD64LEAQ8(v *Value) bool {
}
return false
}
+func rewriteValueAMD64_OpAMD64MOVBELstore(v *Value) bool {
+ v_2 := v.Args[2]
+ v_1 := v.Args[1]
+ v_0 := v.Args[0]
+ // match: (MOVBELstore [i] {s} p (BSWAPL x) m)
+ // result: (MOVLstore [i] {s} p x m)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ if v_1.Op != OpAMD64BSWAPL {
+ break
+ }
+ x := v_1.Args[0]
+ m := v_2
+ v.reset(OpAMD64MOVLstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, x, m)
+ return true
+ }
+ return false
+}
+func rewriteValueAMD64_OpAMD64MOVBEQstore(v *Value) bool {
+ v_2 := v.Args[2]
+ v_1 := v.Args[1]
+ v_0 := v.Args[0]
+ // match: (MOVBEQstore [i] {s} p (BSWAPQ x) m)
+ // result: (MOVQstore [i] {s} p x m)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ if v_1.Op != OpAMD64BSWAPQ {
+ break
+ }
+ x := v_1.Args[0]
+ m := v_2
+ v.reset(OpAMD64MOVQstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, x, m)
+ return true
+ }
+ return false
+}
func rewriteValueAMD64_OpAMD64MOVBQSX(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
@@ -12225,6 +12349,28 @@ func rewriteValueAMD64_OpAMD64MOVLstore(v *Value) bool {
v.AddArg3(ptr, val, mem)
return true
}
+ // match: (MOVLstore [i] {s} p x:(BSWAPL w) mem)
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBELstore [i] {s} p w mem)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ x := v_1
+ if x.Op != OpAMD64BSWAPL {
+ break
+ }
+ w := x.Args[0]
+ mem := v_2
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBELstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, w, mem)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64MOVLstoreconst(v *Value) bool {
@@ -13164,6 +13310,28 @@ func rewriteValueAMD64_OpAMD64MOVQstore(v *Value) bool {
v.AddArg3(ptr, val, mem)
return true
}
+ // match: (MOVQstore [i] {s} p x:(BSWAPQ w) mem)
+ // cond: x.Uses == 1 && buildcfg.GOAMD64 >= 3
+ // result: (MOVBEQstore [i] {s} p w mem)
+ for {
+ i := auxIntToInt32(v.AuxInt)
+ s := auxToSym(v.Aux)
+ p := v_0
+ x := v_1
+ if x.Op != OpAMD64BSWAPQ {
+ break
+ }
+ w := x.Args[0]
+ mem := v_2
+ if !(x.Uses == 1 && buildcfg.GOAMD64 >= 3) {
+ break
+ }
+ v.reset(OpAMD64MOVBEQstore)
+ v.AuxInt = int32ToAuxInt(i)
+ v.Aux = symToAux(s)
+ v.AddArg3(p, w, mem)
+ return true
+ }
return false
}
func rewriteValueAMD64_OpAMD64MOVQstoreconst(v *Value) bool {
@@ -18657,6 +18825,81 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value) bool {
}
break
}
+ // match: (ORQ x0:(MOVBELload [i0] {s} p mem) sh:(SHLQconst [32] x1:(MOVBELload [i1] {s} p mem)))
+ // cond: i0 == i1+4 && x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && mergePoint(b,x0,x1) != nil && clobber(x0, x1, sh)
+ // result: @mergePoint(b,x0,x1) (MOVBEQload [i1] {s} p mem)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ x0 := v_0
+ if x0.Op != OpAMD64MOVBELload {
+ continue
+ }
+ i0 := auxIntToInt32(x0.AuxInt)
+ s := auxToSym(x0.Aux)
+ mem := x0.Args[1]
+ p := x0.Args[0]
+ sh := v_1
+ if sh.Op != OpAMD64SHLQconst || auxIntToInt8(sh.AuxInt) != 32 {
+ continue
+ }
+ x1 := sh.Args[0]
+ if x1.Op != OpAMD64MOVBELload {
+ continue
+ }
+ i1 := auxIntToInt32(x1.AuxInt)
+ if auxToSym(x1.Aux) != s {
+ continue
+ }
+ _ = x1.Args[1]
+ if p != x1.Args[0] || mem != x1.Args[1] || !(i0 == i1+4 && x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && mergePoint(b, x0, x1) != nil && clobber(x0, x1, sh)) {
+ continue
+ }
+ b = mergePoint(b, x0, x1)
+ v0 := b.NewValue0(x1.Pos, OpAMD64MOVBEQload, typ.UInt64)
+ v.copyOf(v0)
+ v0.AuxInt = int32ToAuxInt(i1)
+ v0.Aux = symToAux(s)
+ v0.AddArg2(p, mem)
+ return true
+ }
+ break
+ }
+ // match: (ORQ x0:(MOVBELload [i] {s} p0 mem) sh:(SHLQconst [32] x1:(MOVBELload [i] {s} p1 mem)))
+ // cond: x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && sequentialAddresses(p1, p0, 4) && mergePoint(b,x0,x1) != nil && clobber(x0, x1, sh)
+ // result: @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p1 mem)
+ for {
+ for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 {
+ x0 := v_0
+ if x0.Op != OpAMD64MOVBELload {
+ continue
+ }
+ i := auxIntToInt32(x0.AuxInt)
+ s := auxToSym(x0.Aux)
+ mem := x0.Args[1]
+ p0 := x0.Args[0]
+ sh := v_1
+ if sh.Op != OpAMD64SHLQconst || auxIntToInt8(sh.AuxInt) != 32 {
+ continue
+ }
+ x1 := sh.Args[0]
+ if x1.Op != OpAMD64MOVBELload || auxIntToInt32(x1.AuxInt) != i || auxToSym(x1.Aux) != s {
+ continue
+ }
+ _ = x1.Args[1]
+ p1 := x1.Args[0]
+ if mem != x1.Args[1] || !(x0.Uses == 1 && x1.Uses == 1 && sh.Uses == 1 && sequentialAddresses(p1, p0, 4) && mergePoint(b, x0, x1) != nil && clobber(x0, x1, sh)) {
+ continue
+ }
+ b = mergePoint(b, x0, x1)
+ v0 := b.NewValue0(x1.Pos, OpAMD64MOVBEQload, typ.UInt64)
+ v.copyOf(v0)
+ v0.AuxInt = int32ToAuxInt(i)
+ v0.Aux = symToAux(s)
+ v0.AddArg2(p1, mem)
+ return true
+ }
+ break
+ }
return false
}
func rewriteValueAMD64_OpAMD64ORQconst(v *Value) bool {
diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go
index 2a0c534df0..97e1d4bdfb 100644
--- a/test/codegen/memcombine.go
+++ b/test/codegen/memcombine.go
@@ -70,7 +70,8 @@ func load_le16_idx(b []byte, idx int) {
}
func load_be64(b []byte) {
- // amd64:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v3:`MOVBEQ`
// s390x:`MOVD\s\(.*\),`
// arm64:`REV`,`MOVD\s\(R[0-9]+\),`,-`MOV[BHW]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`,-`MOV[BHW]Z`
@@ -78,7 +79,8 @@ func load_be64(b []byte) {
}
func load_be64_idx(b []byte, idx int) {
- // amd64:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v3: `MOVBEQ`
// s390x:`MOVD\s\(.*\)\(.*\*1\),`
// arm64:`REV`,`MOVD\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[WHB]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`,-`MOV[BHW]Z`
@@ -86,7 +88,8 @@ func load_be64_idx(b []byte, idx int) {
}
func load_be32(b []byte) {
- // amd64:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v3: `MOVBEL`
// s390x:`MOVWZ\s\(.*\),`
// arm64:`REVW`,`MOVWU\s\(R[0-9]+\),`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`,-`MOV[BH]Z`
@@ -94,7 +97,8 @@ func load_be32(b []byte) {
}
func load_be32_idx(b []byte, idx int) {
- // amd64:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v3: `MOVBEL`
// s390x:`MOVWZ\s\(.*\)\(.*\*1\),`
// arm64:`REVW`,`MOVWU\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[HB]`,-`REV16W`
// ppc64le:`MOVWBR`,-`MOV[BH]Z`
@@ -179,7 +183,8 @@ func load_be_byte4_uint32(s []byte) uint32 {
func load_be_byte4_uint32_inv(s []byte) uint32 {
// arm64:`MOVWU\t\(R[0-9]+\)`,`REVW`,-`ORR`,-`REV16W`,-`MOV[BH]`
- // amd64:`MOVL\s\([A-Z]+\)`,`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v1,amd64/v2:`MOVL\s\([A-Z]+\)`,`BSWAPL`,-`MOV[BW]`,-`OR`
+ // amd64/v3: `MOVBEL`
return uint32(s[3]) | uint32(s[2])<<8 | uint32(s[1])<<16 | uint32(s[0])<<24
}
@@ -191,7 +196,8 @@ func load_be_byte8_uint64(s []byte) uint64 {
func load_be_byte8_uint64_inv(s []byte) uint64 {
// arm64:`MOVD\t\(R[0-9]+\)`,`REV`,-`ORR`,-`REVW`,-`REV16W`,-`MOV[BHW]`
- // amd64:`MOVQ\s\([A-Z]+\),\s[A-Z]+`,`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v1,amd64/v2:`MOVQ\s\([A-Z]+\),\s[A-Z]+`,`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR`
+ // amd64/v3: `MOVBEQ`
// ppc64le:`MOVDBR\t\(R[0-9]+\)`,-`MOV[BHW]Z`
return uint64(s[7]) | uint64(s[6])<<8 | uint64(s[5])<<16 | uint64(s[4])<<24 | uint64(s[3])<<32 | uint64(s[2])<<40 | uint64(s[1])<<48 | uint64(s[0])<<56
}
@@ -409,7 +415,8 @@ func store_le16_idx(b []byte, idx int) {
}
func store_be64(b []byte) {
- // amd64:`BSWAPQ`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`SHR.`
+ // amd64/v3: `MOVBEQ`
// arm64:`MOVD`,`REV`,-`MOV[WBH]`,-`REVW`,-`REV16W`
// ppc64le:`MOVDBR`
// s390x:`MOVD\s.*\(.*\)$`,-`SRW\s`,-`SRD\s`
@@ -417,7 +424,8 @@ func store_be64(b []byte) {
}
func store_be64_idx(b []byte, idx int) {
- // amd64:`BSWAPQ`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPQ`,-`SHR.`
+ // amd64/v3:`MOVBEQ`
// arm64:`REV`,`MOVD\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BHW]`,-`REV16W`,-`REVW`
// ppc64le:`MOVDBR`
// s390x:`MOVD\s.*\(.*\)\(.*\*1\)$`,-`SRW\s`,-`SRD\s`
@@ -425,7 +433,8 @@ func store_be64_idx(b []byte, idx int) {
}
func store_be32(b []byte) {
- // amd64:`BSWAPL`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.`
+ // amd64/v3:`MOVBEL`
// arm64:`MOVW`,`REVW`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`
// s390x:`MOVW\s.*\(.*\)$`,-`SRW\s`,-`SRD\s`
@@ -445,7 +454,8 @@ func store_be32_load(b, x *[8]byte) {
}
func store_be32_idx(b []byte, idx int) {
- // amd64:`BSWAPL`,-`SHR.`
+ // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.`
+ // amd64/v3:`MOVBEL`
// arm64:`REVW`,`MOVW\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BH]`,-`REV16W`
// ppc64le:`MOVWBR`
// s390x:`MOVW\s.*\(.*\)\(.*\*1\)$`,-`SRW\s`,-`SRD\s`
@@ -508,14 +518,16 @@ func store_be_byte_2(b []byte, val uint16) {
func store_be_byte_4(b []byte, val uint32) {
_ = b[4]
// arm64:`REVW`,`MOVW\sR[0-9]+,\s1\(R[0-9]+\)`,-`MOVB`,-`MOVH`,-`REV16W`
- // amd64:`MOVL\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`
+ // amd64/v1,amd64/v2:`MOVL\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`
+ // amd64/v3:`MOVBEL\s[A-Z]+,\s1\([A-Z]+\)`
b[1], b[2], b[3], b[4] = byte(val>>24), byte(val>>16), byte(val>>8), byte(val)
}
func store_be_byte_8(b []byte, val uint64) {
_ = b[8]
// arm64:`REV`,`MOVD\sR[0-9]+,\s1\(R[0-9]+\)`,-`MOVB`,-`MOVH`,-`MOVW`,-`REV16W`,-`REVW`
- // amd64:`MOVQ\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`,-`MOVL`
+ // amd64/v1,amd64/v2:`MOVQ\s[A-Z]+,\s1\([A-Z]+\)`,-`MOVB`,-`MOVW`,-`MOVL`
+ // amd64/v3:`MOVBEQ\s[A-Z]+,\s1\([A-Z]+\)`, -`MOVBEL`
b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8] = byte(val>>56), byte(val>>48), byte(val>>40), byte(val>>32), byte(val>>24), byte(val>>16), byte(val>>8), byte(val)
}
From f92a3589fa04285dccab3ca7454eaaf2d0e7cde3 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Tue, 19 Oct 2021 16:34:43 +0700
Subject: [PATCH 069/406] reflect: fix methodValueCall code pointer mismatched
CL 322350 changed how to take address of assembly functions, using
abi.FuncPCABI0 intrinsic. But we forgot to update the code in
Value.UnsafePointer (was Value.Pointer) to reflect that change.
This CL fixes that bug, and also add a test to make sure the code
pointer is in sync.
Change-Id: I05ae7df31c706583a0f374d8af027066528f5ceb
Reviewed-on: https://go-review.googlesource.com/c/go/+/356809
Trust: Cuong Manh Le
Run-TryBot: Cuong Manh Le
TryBot-Result: Go Bot
Reviewed-by: Cherry Mui
---
src/reflect/all_test.go | 8 ++++++++
src/reflect/export_test.go | 2 ++
src/reflect/makefunc.go | 6 +++++-
src/reflect/value.go | 4 ++--
4 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go
index 141cc8f73d..91aac9cccb 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -7722,3 +7722,11 @@ func TestNotInHeapDeref(t *testing.T) {
v = ValueOf((*nih)(unsafe.Pointer(new(int))))
shouldPanic("reflect: reflect.Value.Elem on an invalid notinheap pointer", func() { v.Elem() })
}
+
+func TestMethodCallValueCodePtr(t *testing.T) {
+ p := ValueOf(Point{}).Method(1).UnsafePointer()
+ want := MethodValueCallCodePtr()
+ if got := uintptr(p); got != want {
+ t.Errorf("methodValueCall code pointer mismatched, want: %v, got: %v", want, got)
+ }
+}
diff --git a/src/reflect/export_test.go b/src/reflect/export_test.go
index 01749e30d8..ba7fb68067 100644
--- a/src/reflect/export_test.go
+++ b/src/reflect/export_test.go
@@ -161,3 +161,5 @@ func SetArgRegs(ints, floats int, floatSize uintptr) (oldInts, oldFloats int, ol
clearLayoutCache()
return
}
+
+var MethodValueCallCodePtr = methodValueCallCodePtr
diff --git a/src/reflect/makefunc.go b/src/reflect/makefunc.go
index 588be8bcc1..d0b0935cb8 100644
--- a/src/reflect/makefunc.go
+++ b/src/reflect/makefunc.go
@@ -107,7 +107,7 @@ func makeMethodValue(op string, v Value) Value {
// v.Type returns the actual type of the method value.
ftyp := (*funcType)(unsafe.Pointer(v.Type().(*rtype)))
- code := abi.FuncPCABI0(methodValueCall)
+ code := methodValueCallCodePtr()
// methodValue contains a stack map for use by the runtime
_, _, abi := funcLayout(ftyp, nil)
@@ -130,6 +130,10 @@ func makeMethodValue(op string, v Value) Value {
return Value{&ftyp.rtype, unsafe.Pointer(fv), v.flag&flagRO | flag(Func)}
}
+func methodValueCallCodePtr() uintptr {
+ return abi.FuncPCABI0(methodValueCall)
+}
+
// 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)
diff --git a/src/reflect/value.go b/src/reflect/value.go
index a272714ac9..1d385f6bf9 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -2488,8 +2488,8 @@ func (v Value) UnsafePointer() unsafe.Pointer {
// created via reflect have the same underlying code pointer,
// so their Pointers are equal. The function used here must
// match the one used in makeMethodValue.
- f := methodValueCall
- return **(**unsafe.Pointer)(unsafe.Pointer(&f))
+ code := methodValueCallCodePtr()
+ return *(*unsafe.Pointer)(unsafe.Pointer(&code))
}
p := v.pointer()
// Non-nil func value points at data block.
From 254c497e5c5628be115b966808d6e76d335313a3 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Mon, 18 Oct 2021 14:56:08 -0700
Subject: [PATCH 070/406] cmd/compile, types2: better error message for invalid
type assertion
This CL addresses the 2nd part of the issue below.
- For types2, now use the same error messages as the compiler in this case.
- Make the mechanism for reporting clarifying error messages handle the case
where we don't have additional position information.
- Provide context information (type assertion vs type switch).
Fixes #49005.
Change-Id: I4eeaf4f0c3f2f8735b63993778f58d713fef21ee
Reviewed-on: https://go-review.googlesource.com/c/go/+/356512
Trust: Robert Griesemer
Reviewed-by: Cuong Manh Le
Reviewed-by: Robert Findley
---
src/cmd/compile/internal/types2/errors.go | 5 ++-
.../compile/internal/types2/errors_test.go | 2 +-
src/cmd/compile/internal/types2/expr.go | 22 +++++++-----
src/cmd/compile/internal/types2/stmt.go | 4 +--
.../internal/types2/testdata/check/expr3.src | 4 +--
.../internal/types2/testdata/check/issues.src | 4 +--
.../internal/types2/testdata/check/stmt0.src | 2 +-
.../types2/testdata/fixedbugs/issue49005.go | 34 +++++++++++++++++++
test/fixedbugs/issue49005b.go | 15 ++++++++
test/switch6.go | 4 +--
10 files changed, 77 insertions(+), 19 deletions(-)
create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue49005.go
create mode 100644 test/fixedbugs/issue49005b.go
diff --git a/src/cmd/compile/internal/types2/errors.go b/src/cmd/compile/internal/types2/errors.go
index ea43fab178..0c8a4a90ff 100644
--- a/src/cmd/compile/internal/types2/errors.go
+++ b/src/cmd/compile/internal/types2/errors.go
@@ -61,7 +61,10 @@ func (err *error_) msg(qf Qualifier) string {
for i := range err.desc {
p := &err.desc[i]
if i > 0 {
- fmt.Fprintf(&buf, "\n\t%s: ", p.pos)
+ fmt.Fprint(&buf, "\n\t")
+ if p.pos.IsKnown() {
+ fmt.Fprintf(&buf, "%s: ", p.pos)
+ }
}
buf.WriteString(sprintf(qf, p.format, p.args...))
}
diff --git a/src/cmd/compile/internal/types2/errors_test.go b/src/cmd/compile/internal/types2/errors_test.go
index 72a2ce3655..ac73ca4650 100644
--- a/src/cmd/compile/internal/types2/errors_test.go
+++ b/src/cmd/compile/internal/types2/errors_test.go
@@ -19,7 +19,7 @@ func TestError(t *testing.T) {
t.Errorf("simple error: got %q, want %q", got, want)
}
- want = ": foo 42\n\t: bar 43"
+ want = ": foo 42\n\tbar 43"
err.errorf(nopos, "bar %d", 43)
if got := err.String(); got != want {
t.Errorf("simple error: got %q, want %q", got, want)
diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go
index 3a3a139156..2d22c027eb 100644
--- a/src/cmd/compile/internal/types2/expr.go
+++ b/src/cmd/compile/internal/types2/expr.go
@@ -1463,7 +1463,7 @@ func (check *Checker) exprInternal(x *operand, e syntax.Expr, hint Type) exprKin
if T == Typ[Invalid] {
goto Error
}
- check.typeAssertion(posFor(x), x, xtyp, T)
+ check.typeAssertion(e, x, xtyp, T, false)
x.mode = commaok
x.typ = T
@@ -1605,26 +1605,32 @@ func keyVal(x constant.Value) interface{} {
}
// typeAssertion checks that x.(T) is legal; xtyp must be the type of x.
-func (check *Checker) typeAssertion(pos syntax.Pos, x *operand, xtyp *Interface, T Type) {
+func (check *Checker) typeAssertion(e syntax.Expr, x *operand, xtyp *Interface, T Type, typeSwitch bool) {
method, wrongType := check.assertableTo(xtyp, T)
if method == nil {
return
}
+
var msg string
if wrongType != nil {
if Identical(method.typ, wrongType.typ) {
- msg = fmt.Sprintf("missing method %s (%s has pointer receiver)", method.name, method.name)
+ msg = fmt.Sprintf("%s method has pointer receiver", method.name)
} else {
- msg = fmt.Sprintf("wrong type for method %s (have %s, want %s)", method.name, wrongType.typ, method.typ)
+ msg = fmt.Sprintf("wrong type for method %s: have %s, want %s", method.name, wrongType.typ, method.typ)
}
} else {
- msg = "missing method " + method.name
+ msg = fmt.Sprintf("missing %s method", method.name)
}
- if check.conf.CompilerErrorMessages {
- check.errorf(pos, "impossible type assertion: %s (%s)", x, msg)
+
+ var err error_
+ if typeSwitch {
+ err.errorf(e.Pos(), "impossible type switch case: %s", e)
+ err.errorf(nopos, "%s cannot have dynamic type %s (%s)", x, T, msg)
} else {
- check.errorf(pos, "%s cannot have dynamic type %s (%s)", x, T, msg)
+ err.errorf(e.Pos(), "impossible type assertion: %s", e)
+ err.errorf(nopos, "%s does not implement %s (%s)", T, x.typ, msg)
}
+ check.report(&err)
}
// expr typechecks expression e and initializes x with the expression value.
diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go
index f3f345fd2f..e826f35105 100644
--- a/src/cmd/compile/internal/types2/stmt.go
+++ b/src/cmd/compile/internal/types2/stmt.go
@@ -306,7 +306,7 @@ L:
}
seen[T] = e
if T != nil {
- check.typeAssertion(e.Pos(), x, xtyp, T)
+ check.typeAssertion(e, x, xtyp, T, true)
}
}
return
@@ -347,7 +347,7 @@ L:
// }
// seen[hash] = e
// if T != nil {
-// check.typeAssertion(e.Pos(), x, xtyp, T)
+// check.typeAssertion(e, x, xtyp, T, true)
// }
// }
// return
diff --git a/src/cmd/compile/internal/types2/testdata/check/expr3.src b/src/cmd/compile/internal/types2/testdata/check/expr3.src
index fd28421dc8..df4cf6a840 100644
--- a/src/cmd/compile/internal/types2/testdata/check/expr3.src
+++ b/src/cmd/compile/internal/types2/testdata/check/expr3.src
@@ -459,9 +459,9 @@ func type_asserts() {
var t I
_ = t /* ERROR "use of .* outside type switch" */ .(type)
- _ = t /* ERROR "missing method m" */ .(T)
+ _ = t /* ERROR "m method has pointer receiver" */ .(T)
_ = t.(*T)
- _ = t /* ERROR "missing method m" */ .(T1)
+ _ = t /* ERROR "missing m method" */ .(T1)
_ = t /* ERROR "wrong type for method m" */ .(T2)
_ = t /* STRICT "wrong type for method m" */ .(I2) // only an error in strict mode (issue 8561)
diff --git a/src/cmd/compile/internal/types2/testdata/check/issues.src b/src/cmd/compile/internal/types2/testdata/check/issues.src
index d83a95af0e..dfd51006b9 100644
--- a/src/cmd/compile/internal/types2/testdata/check/issues.src
+++ b/src/cmd/compile/internal/types2/testdata/check/issues.src
@@ -132,12 +132,12 @@ func issue10260() {
var x I1
x = T1 /* ERROR cannot use .*: missing method foo \(foo has pointer receiver\) */ {}
- _ = x /* ERROR .* cannot have dynamic type T1 \(missing method foo \(foo has pointer receiver\)\) */ .(T1)
+ _ = x. /* ERROR impossible type assertion: x.\(T1\)\n\tT1 does not implement I1 \(foo method has pointer receiver\) */ (T1)
T1{}.foo /* ERROR cannot call pointer method foo on T1 */ ()
x.Foo /* ERROR "x.Foo undefined \(type I1 has no field or method Foo, but does have foo\)" */ ()
- _ = i2 /* ERROR i2 .* cannot have dynamic type \*T1 \(wrong type for method foo \(have func\(\), want func\(x int\)\)\) */ .(*T1)
+ _ = i2. /* ERROR impossible type assertion: i2.\(\*T1\)\n\t\*T1 does not implement I2 \(wrong type for method foo: have func\(\), want func\(x int\)\) */ (*T1)
i1 = i0 /* ERROR cannot use .* missing method foo */
i1 = t0 /* ERROR cannot use .* missing method foo */
diff --git a/src/cmd/compile/internal/types2/testdata/check/stmt0.src b/src/cmd/compile/internal/types2/testdata/check/stmt0.src
index d744f2ba81..5ec37b4ace 100644
--- a/src/cmd/compile/internal/types2/testdata/check/stmt0.src
+++ b/src/cmd/compile/internal/types2/testdata/check/stmt0.src
@@ -715,7 +715,7 @@ func typeswitches() {
var t I
switch t.(type) {
case T:
- case T1 /* ERROR "missing method m" */ :
+ case T1 /* ERROR "missing m method" */ :
case T2 /* ERROR "wrong type for method m" */ :
case I2 /* STRICT "wrong type for method m" */ : // only an error in strict mode (issue 8561)
}
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49005.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49005.go
new file mode 100644
index 0000000000..6225e68488
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49005.go
@@ -0,0 +1,34 @@
+// Copyright 2021 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.
+
+// This file is tested when running "go test -run Manual"
+// without source arguments. Use for one-off debugging.
+
+package p
+
+type T1 interface{ M() }
+
+func F1() T1
+
+var _ = F1().(*X1 /* ERROR undeclared name: X1 */)
+
+func _() {
+ switch F1().(type) {
+ case *X1 /* ERROR undeclared name: X1 */ :
+ }
+}
+
+type T2 interface{ M() }
+
+func F2() T2
+
+var _ = F2(). /* ERROR impossible type assertion: F2\(\).\(\*X2\)\n\t\*X2 does not implement T2 \(missing M method\) */ (*X2)
+
+type X2 struct{}
+
+func _() {
+ switch F2().(type) {
+ case * /* ERROR impossible type switch case: \*X2\n\tF2\(\) \(value of type T2\) cannot have dynamic type \*X2 \(missing M method\) */ X2:
+ }
+}
diff --git a/test/fixedbugs/issue49005b.go b/test/fixedbugs/issue49005b.go
new file mode 100644
index 0000000000..9bff4e9d18
--- /dev/null
+++ b/test/fixedbugs/issue49005b.go
@@ -0,0 +1,15 @@
+// errorcheck
+
+// Copyright 2021 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.
+
+package p
+
+type T interface{ M() }
+
+func F() T
+
+var _ = F().(*X) // ERROR "impossible type assertion:( F\(\).\(\*X\))?\n\t\*X does not implement T \(missing M method\)"
+
+type X struct{}
diff --git a/test/switch6.go b/test/switch6.go
index 4f95d02615..b9d9800391 100644
--- a/test/switch6.go
+++ b/test/switch6.go
@@ -15,7 +15,7 @@ package main
// Verify that type switch statements with impossible cases are detected by the compiler.
func f0(e error) {
switch e.(type) {
- case int: // ERROR "impossible type switch case: e \(type error\) cannot have dynamic type int \(missing Error method\)|impossible type assertion"
+ case int: // ERROR "impossible type switch case: (int\n\t)?e \(.*type error\) cannot have dynamic type int \(missing Error method\)"
}
}
@@ -41,6 +41,6 @@ func (*X) Foo() {}
func f2() {
var i I
switch i.(type) {
- case X: // ERROR "impossible type switch case: i \(type I\) cannot have dynamic type X \(Foo method has pointer receiver\)|impossible type assertion"
+ case X: // ERROR "impossible type switch case: (X\n\t)?i \(.*type I\) cannot have dynamic type X \(Foo method has pointer receiver\)"
}
}
From 3a07ab70a2d63e3ac1fc126529dde29852a972f5 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Mon, 18 Oct 2021 17:17:08 -0700
Subject: [PATCH 071/406] cmd/compile/internal/types2: add support for
inferring type instances
This is an essentially clean port of CL 356489 from go/types to types2,
with minor adjustments due to the different AST packages and error
reporting.
Fixes #47990.
Change-Id: I52187872474bfc1fb49eb77905f22fc820b7295b
Reviewed-on: https://go-review.googlesource.com/c/go/+/356514
Trust: Robert Griesemer
Reviewed-by: Robert Findley
---
src/cmd/compile/internal/types2/call.go | 32 +++++++-
.../compile/internal/types2/instantiate.go | 49 ------------
src/cmd/compile/internal/types2/named.go | 3 +-
.../{tinference.go2 => funcinference.go2} | 29 ++++---
.../types2/testdata/check/typeinference.go2 | 47 +++++++++++
.../types2/testdata/check/typeinst2.go2 | 4 +
.../types2/testdata/check/typeinstcycles.go2 | 11 +++
src/cmd/compile/internal/types2/typexpr.go | 80 ++++++++++++++++---
8 files changed, 177 insertions(+), 78 deletions(-)
rename src/cmd/compile/internal/types2/testdata/check/{tinference.go2 => funcinference.go2} (82%)
create mode 100644 src/cmd/compile/internal/types2/testdata/check/typeinference.go2
create mode 100644 src/cmd/compile/internal/types2/testdata/check/typeinstcycles.go2
diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go
index 8b45b28017..3859e39550 100644
--- a/src/cmd/compile/internal/types2/call.go
+++ b/src/cmd/compile/internal/types2/call.go
@@ -57,7 +57,7 @@ func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
}
// instantiate function signature
- res := check.instantiate(x.Pos(), sig, targs, poslist).(*Signature)
+ res := check.instantiateSignature(x.Pos(), sig, targs, poslist)
assert(res.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(inst.X, targs, res)
x.typ = res
@@ -65,6 +65,34 @@ func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
x.expr = inst
}
+func (check *Checker) instantiateSignature(pos syntax.Pos, typ *Signature, targs []Type, posList []syntax.Pos) (res *Signature) {
+ assert(check != nil)
+ assert(len(targs) == typ.TypeParams().Len())
+
+ if check.conf.Trace {
+ check.trace(pos, "-- instantiating %s with %s", typ, targs)
+ check.indent++
+ defer func() {
+ check.indent--
+ check.trace(pos, "=> %s (under = %s)", res, res.Underlying())
+ }()
+ }
+
+ inst := check.instance(pos, typ, targs, check.conf.Context).(*Signature)
+ assert(len(posList) <= len(targs))
+ tparams := typ.TypeParams().list()
+ if i, err := check.verify(pos, tparams, targs); err != nil {
+ // best position for error reporting
+ pos := pos
+ if i < len(posList) {
+ pos = posList[i]
+ }
+ check.softErrorf(pos, err.Error())
+ }
+
+ return inst
+}
+
func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind {
var inst *syntax.IndexExpr // function instantiation, if any
if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil {
@@ -345,7 +373,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
}
// compute result signature
- rsig = check.instantiate(call.Pos(), sig, targs, nil).(*Signature)
+ rsig = check.instantiateSignature(call.Pos(), sig, targs, nil)
assert(rsig.TypeParams().Len() == 0) // signature is not generic anymore
check.recordInstance(call.Fun, targs, rsig)
diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go
index 5e45ea33ce..d6cefb4bfe 100644
--- a/src/cmd/compile/internal/types2/instantiate.go
+++ b/src/cmd/compile/internal/types2/instantiate.go
@@ -49,55 +49,6 @@ func Instantiate(ctxt *Context, typ Type, targs []Type, validate bool) (Type, er
return inst, err
}
-// instantiate creates an instance and defers verification of constraints to
-// later in the type checking pass. For Named types the resulting instance will
-// be unexpanded.
-func (check *Checker) instantiate(pos syntax.Pos, typ Type, targs []Type, posList []syntax.Pos) (res Type) {
- assert(check != nil)
- if check.conf.Trace {
- check.trace(pos, "-- instantiating %s with %s", typ, NewTypeList(targs))
- check.indent++
- defer func() {
- check.indent--
- var under Type
- if res != nil {
- // Calling under() here may lead to endless instantiations.
- // Test case: type T[P any] T[P]
- under = safeUnderlying(res)
- }
- check.trace(pos, "=> %s (under = %s)", res, under)
- }()
- }
-
- inst := check.instance(pos, typ, targs, check.conf.Context)
-
- assert(len(posList) <= len(targs))
- check.later(func() {
- // Collect tparams again because lazily loaded *Named types may not have
- // had tparams set up above.
- var tparams []*TypeParam
- switch t := typ.(type) {
- case *Named:
- tparams = t.TypeParams().list()
- case *Signature:
- tparams = t.TypeParams().list()
- }
- // Avoid duplicate errors; instantiate will have complained if tparams
- // and targs do not have the same length.
- if len(tparams) == len(targs) {
- if i, err := check.verify(pos, tparams, targs); err != nil {
- // best position for error reporting
- pos := pos
- if i < len(posList) {
- pos = posList[i]
- }
- check.softErrorf(pos, err.Error())
- }
- }
- })
- return inst
-}
-
// instance creates a type or function instance using the given original type
// typ and arguments targs. For Named types the resulting instance will be
// unexpanded.
diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go
index 3971e03179..8f2a52b4f2 100644
--- a/src/cmd/compile/internal/types2/named.go
+++ b/src/cmd/compile/internal/types2/named.go
@@ -239,7 +239,8 @@ func expandNamed(ctxt *Context, n *Named, instPos syntax.Pos) (tparams *TypePara
check := n.check
- if check.validateTArgLen(instPos, n.orig.tparams.Len(), n.targs.Len()) {
+ // Mismatching arg and tparam length may be checked elsewhere.
+ if n.orig.tparams.Len() == n.targs.Len() {
// We must always have a context, to avoid infinite recursion.
ctxt = check.bestContext(ctxt)
h := ctxt.TypeHash(n.orig, n.targs.list())
diff --git a/src/cmd/compile/internal/types2/testdata/check/tinference.go2 b/src/cmd/compile/internal/types2/testdata/check/funcinference.go2
similarity index 82%
rename from src/cmd/compile/internal/types2/testdata/check/tinference.go2
rename to src/cmd/compile/internal/types2/testdata/check/funcinference.go2
index 2409fef4ae..7160e18b19 100644
--- a/src/cmd/compile/internal/types2/testdata/check/tinference.go2
+++ b/src/cmd/compile/internal/types2/testdata/check/funcinference.go2
@@ -2,26 +2,25 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package tinferenceB
+package funcInference
import "strconv"
type any interface{}
-// Embedding stand-alone type parameters is not permitted for now. Disabled.
-// func f0[A any, B interface{~C}, C interface{~D}, D interface{~A}](A, B, C, D)
-// func _() {
-// f := f0[string]
-// f("a", "b", "c", "d")
-// f0("a", "b", "c", "d")
-// }
-//
-// func f1[A any, B interface{~A}](A, B)
-// func _() {
-// f := f1[int]
-// f(int(0), int(0))
-// f1(int(0), int(0))
-// }
+func f0[A any, B interface{~*C}, C interface{~*D}, D interface{~*A}](A, B, C, D) {}
+func _() {
+ f := f0[string]
+ f("a", nil, nil, nil)
+ f0("a", nil, nil, nil)
+}
+
+func f1[A any, B interface{~*A}](A, B) {}
+func _() {
+ f := f1[int]
+ f(int(0), new(int))
+ f1(int(0), new(int))
+}
func f2[A any, B interface{~[]A}](A, B) {}
func _() {
diff --git a/src/cmd/compile/internal/types2/testdata/check/typeinference.go2 b/src/cmd/compile/internal/types2/testdata/check/typeinference.go2
new file mode 100644
index 0000000000..8876ccaa4e
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/check/typeinference.go2
@@ -0,0 +1,47 @@
+// Copyright 2021 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.
+
+package typeInference
+
+// basic inference
+type Tb[P ~*Q, Q any] int
+func _() {
+ var x Tb[*int]
+ var y Tb[*int, int]
+ x = y
+ _ = x
+}
+
+// recursive inference
+type Tr[A any, B ~*C, C ~*D, D ~*A] int
+func _() {
+ var x Tr[string]
+ var y Tr[string, ***string, **string, *string]
+ var z Tr[int, ***int, **int, *int]
+ x = y
+ x = z // ERROR cannot use z .* as Tr
+ _ = x
+}
+
+// other patterns of inference
+type To0[A any, B ~[]A] int
+type To1[A any, B ~struct{a A}] int
+type To2[A any, B ~[][]A] int
+type To3[A any, B ~[3]*A] int
+type To4[A any, B any, C ~struct{a A; b B}] int
+func _() {
+ var _ To0[int]
+ var _ To1[int]
+ var _ To2[int]
+ var _ To3[int]
+ var _ To4[int, string]
+}
+
+// failed inference
+type Tf0[A, B any] int
+type Tf1[A any, B ~struct{a A; c C}, C any] int
+func _() {
+ var _ Tf0 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 2 type parameters */ [int]
+ var _ Tf1 /* ERROR cannot infer B */ /* ERROR got 1 arguments but 3 type parameters */ [int]
+}
diff --git a/src/cmd/compile/internal/types2/testdata/check/typeinst2.go2 b/src/cmd/compile/internal/types2/testdata/check/typeinst2.go2
index 49f48c7283..ecf4b0f714 100644
--- a/src/cmd/compile/internal/types2/testdata/check/typeinst2.go2
+++ b/src/cmd/compile/internal/types2/testdata/check/typeinst2.go2
@@ -252,3 +252,7 @@ var _ = f0_[int]
var _ = f0_[bool /* ERROR does not satisfy I0_ */ ]
var _ = f0_[string /* ERROR does not satisfy I0_ */ ]
var _ = f0_[float64 /* ERROR does not satisfy I0_ */ ]
+
+// Using a function instance as a type is an error.
+var _ f0 // ERROR not a type
+var _ f0 /* ERROR not a type */ [int]
diff --git a/src/cmd/compile/internal/types2/testdata/check/typeinstcycles.go2 b/src/cmd/compile/internal/types2/testdata/check/typeinstcycles.go2
new file mode 100644
index 0000000000..74fe19195a
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/check/typeinstcycles.go2
@@ -0,0 +1,11 @@
+// Copyright 2021 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.
+
+package p
+
+import "unsafe"
+
+func F1[T any](_ [unsafe.Sizeof(F1[int])]T) (res T) { return }
+func F2[T any](_ T) (res [unsafe.Sizeof(F2[string])]int) { return }
+func F3[T any](_ [unsafe.Sizeof(F1[string])]int) {}
diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go
index eae9330914..20e56caf1e 100644
--- a/src/cmd/compile/internal/types2/typexpr.go
+++ b/src/cmd/compile/internal/types2/typexpr.go
@@ -396,13 +396,24 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) {
return typ
}
-func (check *Checker) instantiatedType(x syntax.Expr, targsx []syntax.Expr, def *Named) Type {
+func (check *Checker) instantiatedType(x syntax.Expr, targsx []syntax.Expr, def *Named) (res Type) {
+ if check.conf.Trace {
+ check.trace(x.Pos(), "-- instantiating %s with %s", x, targsx)
+ check.indent++
+ defer func() {
+ check.indent--
+ // Don't format the underlying here. It will always be nil.
+ check.trace(x.Pos(), "=> %s", res)
+ }()
+ }
+
gtyp := check.genericType(x, true)
if gtyp == Typ[Invalid] {
return gtyp // error already reported
}
- base, _ := gtyp.(*Named)
- if base == nil {
+
+ origin, _ := gtyp.(*Named)
+ if origin == nil {
panic(fmt.Sprintf("%v: cannot instantiate %v", x.Pos(), gtyp))
}
@@ -416,20 +427,67 @@ func (check *Checker) instantiatedType(x syntax.Expr, targsx []syntax.Expr, def
// determine argument positions
posList := make([]syntax.Pos, len(targs))
for i, arg := range targsx {
- posList[i] = syntax.StartPos(arg)
+ posList[i] = arg.Pos()
}
- typ := check.instantiate(x.Pos(), base, targs, posList)
- def.setUnderlying(typ)
- check.recordInstance(x, targs, typ)
+ // create the instance
+ h := check.conf.Context.TypeHash(origin, targs)
+ // targs may be incomplete, and require inference. In any case we should de-duplicate.
+ inst := check.conf.Context.typeForHash(h, nil)
+ // If inst is non-nil, we can't just return here. Inst may have been
+ // constructed via recursive substitution, in which case we wouldn't do the
+ // validation below. Ensure that the validation (and resulting errors) runs
+ // for each instantiated type in the source.
+ if inst == nil {
+ tname := NewTypeName(x.Pos(), origin.obj.pkg, origin.obj.name, nil)
+ inst = check.newNamed(tname, origin, nil, nil, nil) // underlying, methods and tparams are set when named is resolved
+ inst.targs = NewTypeList(targs)
+ inst = check.conf.Context.typeForHash(h, inst)
+ }
+ def.setUnderlying(inst)
- // make sure we check instantiation works at least once
- // and that the resulting type is valid
+ inst.resolver = func(ctxt *Context, n *Named) (*TypeParamList, Type, []*Func) {
+ tparams := origin.TypeParams().list()
+
+ inferred := targs
+ if len(targs) < len(tparams) {
+ // If inference fails, len(inferred) will be 0, and inst.underlying will
+ // be set to Typ[Invalid] in expandNamed.
+ inferred = check.infer(x.Pos(), tparams, targs, nil, nil)
+ if len(inferred) > len(targs) {
+ inst.targs = NewTypeList(inferred)
+ }
+ }
+
+ check.recordInstance(x, inferred, inst)
+ return expandNamed(ctxt, n, x.Pos())
+ }
+
+ // origin.tparams may not be set up, so we need to do expansion later.
check.later(func() {
- check.validType(typ, nil)
+ // This is an instance from the source, not from recursive substitution,
+ // and so it must be resolved during type-checking so that we can report
+ // errors.
+ inst.resolve(check.conf.Context)
+ // Since check is non-nil, we can still mutate inst. Unpinning the resolver
+ // frees some memory.
+ inst.resolver = nil
+
+ if check.validateTArgLen(x.Pos(), inst.tparams.Len(), inst.targs.Len()) {
+ if i, err := check.verify(x.Pos(), inst.tparams.list(), inst.targs.list()); err != nil {
+ // best position for error reporting
+ pos := x.Pos()
+ if i < len(posList) {
+ pos = posList[i]
+ }
+ check.softErrorf(pos, err.Error())
+ }
+ }
+
+ check.validType(inst, nil)
})
- return typ
+ return inst
}
// arrayLength type-checks the array length expression e
From a73c6cf762560b458eb938e4461cd8debc479fd9 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Mon, 18 Oct 2021 17:31:23 -0700
Subject: [PATCH 072/406] cmd/compile/internal/types2: ensure named types are
expanded after type-checking
This is a clean port of CL 356490 from go/types to types2.
Fixes #48703.
Fixes #48974.
Change-Id: I08c0db0b92250cbb043325541b21a577726b40ca
Reviewed-on: https://go-review.googlesource.com/c/go/+/356515
Trust: Robert Griesemer
Reviewed-by: Robert Findley
---
src/cmd/compile/internal/types2/check.go | 28 +++++++++++++++++++
src/cmd/compile/internal/types2/named.go | 23 ++++++---------
.../types2/testdata/fixedbugs/issue48703.go2 | 27 ++++++++++++++++++
.../types2/testdata/fixedbugs/issue48974.go2 | 22 +++++++++++++++
4 files changed, 85 insertions(+), 15 deletions(-)
create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue48703.go2
create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue48974.go2
diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go
index 470376f8e8..6e8883e5de 100644
--- a/src/cmd/compile/internal/types2/check.go
+++ b/src/cmd/compile/internal/types2/check.go
@@ -132,6 +132,7 @@ type Checker struct {
untyped map[syntax.Expr]exprInfo // map of expressions without final type
delayed []action // stack of delayed action segments; segments are processed in FIFO order
objPath []Object // path of object dependencies during type inference (for cycle reporting)
+ defTypes []*Named // defined types created during type checking, for final validation.
// context within which the current object is type-checked
// (valid only for the duration of type-checking a specific object)
@@ -302,6 +303,9 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) {
print("== processDelayed ==")
check.processDelayed(0) // incl. all functions
+ print("== expandDefTypes ==")
+ check.expandDefTypes()
+
print("== initOrder ==")
check.initOrder()
@@ -321,6 +325,7 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) {
check.pkgPathMap = nil
check.seenPkgMap = nil
check.recvTParamMap = nil
+ check.defTypes = nil
// TODO(gri) There's more memory we should release at this point.
@@ -347,6 +352,29 @@ func (check *Checker) processDelayed(top int) {
check.delayed = check.delayed[:top]
}
+func (check *Checker) expandDefTypes() {
+ // Ensure that every defined type created in the course of type-checking has
+ // either non-*Named underlying, or is unresolved.
+ //
+ // This guarantees that we don't leak any types whose underlying is *Named,
+ // because any unresolved instances will lazily compute their underlying by
+ // substituting in the underlying of their origin. The origin must have
+ // either been imported or type-checked and expanded here, and in either case
+ // its underlying will be fully expanded.
+ for i := 0; i < len(check.defTypes); i++ {
+ n := check.defTypes[i]
+ switch n.underlying.(type) {
+ case nil:
+ if n.resolver == nil {
+ panic("nil underlying")
+ }
+ case *Named:
+ n.under() // n.under may add entries to check.defTypes
+ }
+ n.check = nil
+ }
+}
+
func (check *Checker) record(x *operand) {
// convert x into a user-friendly set of values
// TODO(gri) this code can be simplified
diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go
index 8f2a52b4f2..eb8b5d1ba8 100644
--- a/src/cmd/compile/internal/types2/named.go
+++ b/src/cmd/compile/internal/types2/named.go
@@ -65,22 +65,9 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tpar
if obj.typ == nil {
obj.typ = typ
}
- // Ensure that typ is always expanded, at which point the check field can be
- // nilled out.
- //
- // Note that currently we cannot nil out check inside typ.under(), because
- // it's possible that typ is expanded multiple times.
- //
- // TODO(gri): clean this up so that under is the only function mutating
- // named types.
+ // Ensure that typ is always expanded and sanity-checked.
if check != nil {
- check.later(func() {
- switch typ.under().(type) {
- case *Named:
- panic("unexpanded underlying type")
- }
- typ.check = nil
- })
+ check.defTypes = append(check.defTypes, typ)
}
return typ
}
@@ -239,6 +226,12 @@ func expandNamed(ctxt *Context, n *Named, instPos syntax.Pos) (tparams *TypePara
check := n.check
+ if _, unexpanded := n.orig.underlying.(*Named); unexpanded {
+ // We should only get an unexpanded underlying here during type checking
+ // (for example, in recursive type declarations).
+ assert(check != nil)
+ }
+
// Mismatching arg and tparam length may be checked elsewhere.
if n.orig.tparams.Len() == n.targs.Len() {
// We must always have a context, to avoid infinite recursion.
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48703.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48703.go2
new file mode 100644
index 0000000000..8a32c1ecf2
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48703.go2
@@ -0,0 +1,27 @@
+// Copyright 2021 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.
+
+package p
+
+import "unsafe"
+
+// The actual example from the issue.
+type List[P any] struct{}
+
+func (_ List[P]) m() (_ List[List[P]]) { return }
+
+// Other types of recursion through methods.
+type R[P any] int
+
+func (*R[R /* ERROR must be an identifier */ [int]]) m0() {}
+func (R[P]) m1(R[R[P]]) {}
+func (R[P]) m2(R[*P]) {}
+func (R[P]) m3([unsafe.Sizeof(new(R[P]))]int) {}
+func (R[P]) m4([unsafe.Sizeof(new(R[R[P]]))]int) {}
+
+// Mutual recursion
+type M[P any] int
+
+func (R[P]) m5(M[M[P]]) {}
+func (M[P]) m(R[R[P]]) {}
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48974.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48974.go2
new file mode 100644
index 0000000000..ca4b6d9321
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue48974.go2
@@ -0,0 +1,22 @@
+// Copyright 2021 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.
+
+package p
+
+type Fooer interface {
+ Foo()
+}
+
+type Fooable[F Fooer] struct {
+ ptr F
+}
+
+func (f *Fooable[F]) Adapter() *Fooable[*FooerImpl[F]] {
+ return &Fooable[*FooerImpl[F]]{&FooerImpl[F]{}}
+}
+
+type FooerImpl[F Fooer] struct {
+}
+
+func (fi *FooerImpl[F]) Foo() {}
From 99fad12e4788fdf67e49dadd16571238f935b408 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Mon, 18 Oct 2021 17:42:21 -0700
Subject: [PATCH 073/406] cmd/compile/internal/types2: delay expansion of
underlying in typeDecl
This is a clean port of CL 356533 from go/types to types2.
Fixes #49043.
Change-Id: If389b94ece28042b0c8b436959dd21f26147a144
Reviewed-on: https://go-review.googlesource.com/c/go/+/356517
Trust: Robert Griesemer
Reviewed-by: Robert Findley
---
src/cmd/compile/internal/types2/decl.go | 24 ++++++-------------
src/cmd/compile/internal/types2/named.go | 17 ++++++++++++-
.../types2/testdata/check/typeinst.go2 | 2 +-
.../types2/testdata/fixedbugs/issue49043.go2 | 24 +++++++++++++++++++
4 files changed, 48 insertions(+), 19 deletions(-)
create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue49043.go2
diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go
index a605057579..63be4b3223 100644
--- a/src/cmd/compile/internal/types2/decl.go
+++ b/src/cmd/compile/internal/types2/decl.go
@@ -597,22 +597,12 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
rhs = check.definedType(tdecl.Type, named)
assert(rhs != nil)
named.fromRHS = rhs
- // The underlying type of named may be itself a named type that is
- // incomplete:
- //
- // type (
- // A B
- // B *C
- // C A
- // )
- //
- // The type of C is the (named) type of A which is incomplete,
- // and which has as its underlying type the named type B.
- // Determine the (final, unnamed) underlying type by resolving
- // any forward chain.
- // TODO(gri) Investigate if we can just use named.fromRHS here
- // and rely on lazy computation of the underlying type.
- named.underlying = under(named)
+
+ // If the underlying was not set while type-checking the right-hand side, it
+ // is invalid and an error should have been reported elsewhere.
+ if named.underlying == nil {
+ named.underlying = Typ[Invalid]
+ }
// If the RHS is a type parameter, it must be from this type declaration.
if tpar, _ := named.underlying.(*TypeParam); tpar != nil && tparamIndex(named.TypeParams().list(), tpar) < 0 {
@@ -711,7 +701,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
// and field names must be distinct."
base := asNamed(obj.typ) // shouldn't fail but be conservative
if base != nil {
- u := safeUnderlying(base) // base should be expanded, but use safeUnderlying to be conservative
+ u := base.under()
if t, _ := u.(*Struct); t != nil {
for _, fld := range t.fields {
if fld.name != "_" {
diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go
index eb8b5d1ba8..6ebad8fbb5 100644
--- a/src/cmd/compile/internal/types2/named.go
+++ b/src/cmd/compile/internal/types2/named.go
@@ -130,6 +130,18 @@ func (t *Named) String() string { return TypeString(t, nil) }
// chain before returning it. If no underlying type is found or a cycle
// is detected, the result is Typ[Invalid]. If a cycle is detected and
// n0.check != nil, the cycle is reported.
+//
+// This is necessary because the underlying type of named may be itself a
+// named type that is incomplete:
+//
+// type (
+// A B
+// B *C
+// C A
+// )
+//
+// The type of C is the (named) type of A which is incomplete,
+// and which has as its underlying type the named type B.
func (n0 *Named) under() Type {
u := n0.Underlying()
@@ -139,7 +151,9 @@ func (n0 *Named) under() Type {
var n1 *Named
switch u1 := u.(type) {
case nil:
- return Typ[Invalid]
+ // After expansion via Underlying(), we should never encounter a nil
+ // underlying.
+ panic("nil underlying")
default:
// common case
return u
@@ -223,6 +237,7 @@ func (check *Checker) bestContext(ctxt *Context) *Context {
// The underlying type will be Typ[Invalid] if there was an error.
func expandNamed(ctxt *Context, n *Named, instPos syntax.Pos) (tparams *TypeParamList, underlying Type, methods []*Func) {
n.orig.resolve(ctxt)
+ assert(n.orig.underlying != nil)
check := n.check
diff --git a/src/cmd/compile/internal/types2/testdata/check/typeinst.go2 b/src/cmd/compile/internal/types2/testdata/check/typeinst.go2
index 3fab2cb9ad..14f1b07ee2 100644
--- a/src/cmd/compile/internal/types2/testdata/check/typeinst.go2
+++ b/src/cmd/compile/internal/types2/testdata/check/typeinst.go2
@@ -57,5 +57,5 @@ var _ T3[int] = T3[int](List[int]{1, 2, 3})
// Self-recursive generic types are not permitted
-type self1[P any] self1 /* ERROR illegal cycle */ [P]
+type self1 /* ERROR illegal cycle */ [P any] self1[P]
type self2[P any] *self2[P] // this is ok
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49043.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49043.go2
new file mode 100644
index 0000000000..c37b0f1267
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49043.go2
@@ -0,0 +1,24 @@
+// Copyright 2021 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.
+
+package p
+
+// The example from the issue.
+type (
+ N /* ERROR illegal cycle */ [P any] M[P]
+ M[P any] N[P]
+)
+
+// A slightly more complicated case.
+type (
+ A /* ERROR illegal cycle */ [P any] B[P]
+ B[P any] C[P]
+ C[P any] A[P]
+)
+
+// Confusing but valid (note that `type T *T` is valid).
+type (
+ N1[P any] *M1[P]
+ M1[P any] *N1[P]
+)
From 404f84d417ceed0f47e51d2c4f933a6dee96dca5 Mon Sep 17 00:00:00 2001
From: Benjamin Peterson
Date: Fri, 15 Oct 2021 23:24:28 +0000
Subject: [PATCH 074/406] runtime: remove reference to crypto/tls GODEBUG usage
crypto/tls briefly used GODEBUG. That usage was removed in CL 191999.
Change-Id: I759b6f1b02db8160075cba30d73823018e19ad9d
GitHub-Last-Rev: 12d2a4a82b1467e4c2214aa78eb9a0af4938a9de
GitHub-Pull-Request: golang/go#49012
Reviewed-on: https://go-review.googlesource.com/c/go/+/356313
Reviewed-by: Austin Clements
Trust: Michael Pratt
---
src/runtime/extern.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/runtime/extern.go b/src/runtime/extern.go
index eca4062e68..b2003ba543 100644
--- a/src/runtime/extern.go
+++ b/src/runtime/extern.go
@@ -144,7 +144,7 @@ It is a comma-separated list of name=val pairs setting these named variables:
because it also disables the conservative stack scanning used
for asynchronously preempted goroutines.
-The net, net/http, and crypto/tls packages also refer to debugging variables in GODEBUG.
+The net and net/http packages also refer to debugging variables in GODEBUG.
See the documentation for those packages for details.
The GOMAXPROCS variable limits the number of operating system threads that
From 982060203c26b60fd74e4fa2fd967600c65ee7fc Mon Sep 17 00:00:00 2001
From: Katie Hockman
Date: Fri, 15 Oct 2021 11:16:54 -0400
Subject: [PATCH 075/406] testing: don't allow f.Log/Logf or f.Skipped inside
f.Fuzz
This change also does some refactors around how
we prevent many (*F) methods from being called
inside (*F).Fuzz. Previously, there was a lot of
comment/code duplication, which was going to be
difficult to maintain and brittle. The refactor
lessens this duplication.
Previously, the methods Log, Logf, Failed, Name and
Skipped were the only (*common) methods that were
allowed to be called inside (*F).Fuzz. After this
change, Failed and Name are still allowed, but
Log, Logf, and Skipped are not (t.Log, t.Logf, or
t.Skipped should be used instead).
Fixes #48988
Change-Id: I4066247d551ea1908e8a2ca2889509fc68e3bb44
Reviewed-on: https://go-review.googlesource.com/c/go/+/356151
Trust: Katie Hockman
Run-TryBot: Katie Hockman
TryBot-Result: Go Bot
Reviewed-by: Bryan C. Mills
Reviewed-by: Jay Conrod
---
src/cmd/go/testdata/script/test_fuzz.txt | 39 ++++++
src/testing/fuzz.go | 147 ++++-------------------
src/testing/testing.go | 28 ++++-
3 files changed, 87 insertions(+), 127 deletions(-)
diff --git a/src/cmd/go/testdata/script/test_fuzz.txt b/src/cmd/go/testdata/script/test_fuzz.txt
index c9930aa37e..4665202bf0 100644
--- a/src/cmd/go/testdata/script/test_fuzz.txt
+++ b/src/cmd/go/testdata/script/test_fuzz.txt
@@ -70,6 +70,12 @@ stdout 'f.Fuzz function'
stdout FAIL
stdout 'f.Fuzz function'
+# Test that f.Fail within f.Fuzz panics
+! go test fail_fuzz_fn_fuzz_test.go
+! stdout ^ok
+stdout FAIL
+stdout 'f.Fuzz function'
+
# Test that f.Skip within f.Fuzz panics
! go test skip_fuzz_fn_fuzz_test.go
! stdout ^ok
@@ -77,6 +83,14 @@ stdout 'f.Fuzz function'
stdout FAIL
stdout 'f.Fuzz function'
+# Test that f.Skipped within f.Fuzz panics
+! go test skipped_fuzz_fn_fuzz_test.go
+! stdout ^ok
+! stdout 'f.Skipped is'
+stdout FAIL
+stdout 'f.Fuzz function'
+stdout 't.Skipped is false'
+
# Test that runtime.Goexit within the fuzz function is an error.
! go test goexit_fuzz_fn_fuzz_test.go
! stdout ^ok
@@ -260,6 +274,18 @@ func Fuzz(f *testing.F) {
})
}
+-- fail_fuzz_fn_fuzz_test.go --
+package skip_fuzz_fn_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+ f.Add([]byte("aa"))
+ f.Fuzz(func(t *testing.T, b []byte) {
+ f.Fail()
+ })
+}
+
-- skip_fuzz_fn_fuzz_test.go --
package skip_fuzz_fn_fuzz
@@ -272,6 +298,19 @@ func Fuzz(f *testing.F) {
})
}
+-- skipped_fuzz_fn_fuzz_test.go --
+package skipped_fuzz_fn_fuzz
+
+import "testing"
+
+func Fuzz(f *testing.F) {
+ f.Add([]byte("aa"))
+ f.Fuzz(func(t *testing.T, b []byte) {
+ t.Logf("t.Skipped is %t\n", t.Skipped())
+ t.Logf("f.Skipped is %t\n", f.Skipped())
+ })
+}
+
-- goexit_fuzz_fn_fuzz_test.go --
package goexit_fuzz_fn_fuzz
diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go
index d5cb5e853f..10665168f4 100644
--- a/src/testing/fuzz.go
+++ b/src/testing/fuzz.go
@@ -57,6 +57,10 @@ type InternalFuzzTarget struct {
// call F.Fuzz once to provide a fuzz function. See the testing package
// documentation for an example, and see the F.Fuzz and F.Add method
// documentation for details.
+//
+// *F methods can only be called before (*F).Fuzz. Once inside the function
+// passed to (*F).Fuzz, only (*T) methods can be used. The only *F methods that
+// are allowed in the (*F).Fuzz function are (*F).Failed and (*F).Name.
type F struct {
common
fuzzContext *fuzzContext
@@ -88,78 +92,6 @@ type corpusEntry = struct {
IsSeed bool
}
-// Cleanup registers a function to be called after the fuzz function has been
-// called on all seed corpus entries, and after fuzzing completes (if enabled).
-// Cleanup functions will be called in last added, first called order.
-func (f *F) Cleanup(fn func()) {
- if f.inFuzzFn {
- panic("testing: f.Cleanup was called inside the f.Fuzz function, use t.Cleanup instead")
- }
- f.common.Helper()
- f.common.Cleanup(fn)
-}
-
-// Error is equivalent to Log followed by Fail.
-func (f *F) Error(args ...interface{}) {
- if f.inFuzzFn {
- panic("testing: f.Error was called inside the f.Fuzz function, use t.Error instead")
- }
- f.common.Helper()
- f.common.Error(args...)
-}
-
-// Errorf is equivalent to Logf followed by Fail.
-func (f *F) Errorf(format string, args ...interface{}) {
- if f.inFuzzFn {
- panic("testing: f.Errorf was called inside the f.Fuzz function, use t.Errorf instead")
- }
- f.common.Helper()
- f.common.Errorf(format, args...)
-}
-
-// Fail marks the function as having failed but continues execution.
-func (f *F) Fail() {
- if f.inFuzzFn {
- panic("testing: f.Fail was called inside the f.Fuzz function, use t.Fail instead")
- }
- f.common.Helper()
- f.common.Fail()
-}
-
-// FailNow marks the function as having failed and stops its execution
-// by calling runtime.Goexit (which then runs all deferred calls in the
-// current goroutine).
-// Execution will continue at the next test, benchmark, or fuzz function.
-// FailNow must be called from the goroutine running the
-// fuzz target, not from other goroutines
-// created during the test. Calling FailNow does not stop
-// those other goroutines.
-func (f *F) FailNow() {
- if f.inFuzzFn {
- panic("testing: f.FailNow was called inside the f.Fuzz function, use t.FailNow instead")
- }
- f.common.Helper()
- f.common.FailNow()
-}
-
-// Fatal is equivalent to Log followed by FailNow.
-func (f *F) Fatal(args ...interface{}) {
- if f.inFuzzFn {
- panic("testing: f.Fatal was called inside the f.Fuzz function, use t.Fatal instead")
- }
- f.common.Helper()
- f.common.Fatal(args...)
-}
-
-// Fatalf is equivalent to Logf followed by FailNow.
-func (f *F) Fatalf(format string, args ...interface{}) {
- if f.inFuzzFn {
- panic("testing: f.Fatalf was called inside the f.Fuzz function, use t.Fatalf instead")
- }
- f.common.Helper()
- f.common.Fatalf(format, args...)
-}
-
// Helper marks the calling function as a test helper function.
// When printing file and line information, that function will be skipped.
// Helper may be called simultaneously from multiple goroutines.
@@ -188,65 +120,26 @@ func (f *F) Helper() {
}
}
-// Setenv calls os.Setenv(key, value) and uses Cleanup to restore the
-// environment variable to its original value after the test.
-//
-// When fuzzing is enabled, the fuzzing engine spawns worker processes running
-// the test binary. Each worker process inherits the environment of the parent
-// process, including environment variables set with F.Setenv.
-func (f *F) Setenv(key, value string) {
+// Fail marks the function as having failed but continues execution.
+func (f *F) Fail() {
+ // (*F).Fail may be called by (*T).Fail, which we should allow. However, we
+ // shouldn't allow direct (*F).Fail calls from inside the (*F).Fuzz function.
if f.inFuzzFn {
- panic("testing: f.Setenv was called inside the f.Fuzz function, use t.Setenv instead")
+ panic("testing: f.Fail was called inside the f.Fuzz function, use t.Fail instead")
}
f.common.Helper()
- f.common.Setenv(key, value)
+ f.common.Fail()
}
-// Skip is equivalent to Log followed by SkipNow.
-func (f *F) Skip(args ...interface{}) {
+// Skipped reports whether the test was skipped.
+func (f *F) Skipped() bool {
+ // (*F).Skipped may be called by tRunner, which we should allow. However, we
+ // shouldn't allow direct (*F).Skipped calls from inside the (*F).Fuzz function.
if f.inFuzzFn {
- panic("testing: f.Skip was called inside the f.Fuzz function, use t.Skip instead")
+ panic("testing: f.Skipped was called inside the f.Fuzz function, use t.Skipped instead")
}
f.common.Helper()
- f.common.Skip(args...)
-}
-
-// SkipNow marks the test as having been skipped and stops its execution
-// by calling runtime.Goexit.
-// If a test fails (see Error, Errorf, Fail) and is then skipped,
-// it is still considered to have failed.
-// Execution will continue at the next test or benchmark. See also FailNow.
-// SkipNow must be called from the goroutine running the test, not from
-// other goroutines created during the test. Calling SkipNow does not stop
-// those other goroutines.
-func (f *F) SkipNow() {
- if f.inFuzzFn {
- panic("testing: f.SkipNow was called inside the f.Fuzz function, use t.SkipNow instead")
- }
- f.common.Helper()
- f.common.SkipNow()
-}
-
-// Skipf is equivalent to Logf followed by SkipNow.
-func (f *F) Skipf(format string, args ...interface{}) {
- if f.inFuzzFn {
- panic("testing: f.Skipf was called inside the f.Fuzz function, use t.Skipf instead")
- }
- f.common.Helper()
- f.common.Skipf(format, args...)
-}
-
-// TempDir returns a temporary directory for the test to use.
-// The directory is automatically removed by Cleanup when the test and
-// all its subtests complete.
-// Each subsequent call to t.TempDir returns a unique directory;
-// if the directory creation fails, TempDir terminates the test by calling Fatal.
-func (f *F) TempDir() string {
- if f.inFuzzFn {
- panic("testing: f.TempDir was called inside the f.Fuzz function, use t.TempDir instead")
- }
- f.common.Helper()
- return f.common.TempDir()
+ return f.common.Skipped()
}
// Add will add the arguments to the seed corpus for the fuzz target. This will
@@ -297,6 +190,10 @@ var supportedTypes = map[reflect.Type]bool{
// float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64.
// More types may be supported in the future.
//
+// ff must not call any *F methods, e.g. (*F).Log, (*F).Error, (*F).Skip. Use
+// the corresponding *T method instead. The only *F methods that are allowed in
+// the (*F).Fuzz function are (*F).Failed and (*F).Name.
+//
// This function sould be fast and deterministic, and its behavior should not
// depend on shared state. No mutatable input arguments, or pointers to them,
// should be retained between executions of the fuzz function, as the memory
@@ -415,7 +312,7 @@ func (f *F) Fuzz(ff interface{}) {
// TODO(#48132): adjust this to work with test2json.
t.chatty.Updatef(t.name, "=== RUN %s\n", t.name)
}
- f.inFuzzFn = true
+ f.common.inFuzzFn, f.inFuzzFn = true, true
go tRunner(t, func(t *T) {
args := []reflect.Value{reflect.ValueOf(t)}
for _, v := range e.Values {
@@ -430,7 +327,7 @@ func (f *F) Fuzz(ff interface{}) {
fn.Call(args)
})
<-t.signal
- f.inFuzzFn = false
+ f.common.inFuzzFn, f.inFuzzFn = false, false
return !t.Failed()
}
diff --git a/src/testing/testing.go b/src/testing/testing.go
index 57ac580051..d03c0b1cf9 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -492,6 +492,7 @@ type common struct {
cleanupName string // Name of the cleanup function.
cleanupPc []uintptr // The stack trace at the point where Cleanup was called.
finished bool // Test function has completed.
+ inFuzzFn bool // Whether the fuzz function, if this is one, is running.
chatty *chattyPrinter // A copy of chattyPrinter, if the chatty flag is set.
bench bool // Whether the current test is a benchmark.
@@ -547,6 +548,12 @@ func Verbose() bool {
return *chatty
}
+func (c *common) checkFuzzFn(name string) {
+ if c.inFuzzFn {
+ panic(fmt.Sprintf("testing: f.%s was called inside the f.Fuzz function, use t.%s instead", name, name))
+ }
+}
+
// frameSkip searches, starting after skip frames, for the first caller frame
// in a function not marked as a helper and returns that frame.
// The search stops if it finds a tRunner function that
@@ -821,6 +828,7 @@ func (c *common) Failed() bool {
// created during the test. Calling FailNow does not stop
// those other goroutines.
func (c *common) FailNow() {
+ c.checkFuzzFn("FailNow")
c.Fail()
// Calling runtime.Goexit will exit the goroutine, which
@@ -889,47 +897,59 @@ func (c *common) logDepth(s string, depth int) {
// and records the text in the error log. For tests, the text will be printed only if
// the test fails or the -test.v flag is set. For benchmarks, the text is always
// printed to avoid having performance depend on the value of the -test.v flag.
-func (c *common) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) }
+func (c *common) Log(args ...interface{}) {
+ c.checkFuzzFn("Log")
+ c.log(fmt.Sprintln(args...))
+}
// Logf formats its arguments according to the format, analogous to Printf, and
// records the text in the error log. A final newline is added if not provided. For
// tests, the text will be printed only if the test fails or the -test.v flag is
// set. For benchmarks, the text is always printed to avoid having performance
// depend on the value of the -test.v flag.
-func (c *common) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) }
+func (c *common) Logf(format string, args ...interface{}) {
+ c.checkFuzzFn("Logf")
+ c.log(fmt.Sprintf(format, args...))
+}
// Error is equivalent to Log followed by Fail.
func (c *common) Error(args ...interface{}) {
+ c.checkFuzzFn("Error")
c.log(fmt.Sprintln(args...))
c.Fail()
}
// Errorf is equivalent to Logf followed by Fail.
func (c *common) Errorf(format string, args ...interface{}) {
+ c.checkFuzzFn("Errorf")
c.log(fmt.Sprintf(format, args...))
c.Fail()
}
// Fatal is equivalent to Log followed by FailNow.
func (c *common) Fatal(args ...interface{}) {
+ c.checkFuzzFn("Fatal")
c.log(fmt.Sprintln(args...))
c.FailNow()
}
// Fatalf is equivalent to Logf followed by FailNow.
func (c *common) Fatalf(format string, args ...interface{}) {
+ c.checkFuzzFn("Fatalf")
c.log(fmt.Sprintf(format, args...))
c.FailNow()
}
// Skip is equivalent to Log followed by SkipNow.
func (c *common) Skip(args ...interface{}) {
+ c.checkFuzzFn("Skip")
c.log(fmt.Sprintln(args...))
c.SkipNow()
}
// Skipf is equivalent to Logf followed by SkipNow.
func (c *common) Skipf(format string, args ...interface{}) {
+ c.checkFuzzFn("Skipf")
c.log(fmt.Sprintf(format, args...))
c.SkipNow()
}
@@ -943,6 +963,7 @@ func (c *common) Skipf(format string, args ...interface{}) {
// other goroutines created during the test. Calling SkipNow does not stop
// those other goroutines.
func (c *common) SkipNow() {
+ c.checkFuzzFn("SkipNow")
c.mu.Lock()
c.skipped = true
c.finished = true
@@ -982,6 +1003,7 @@ func (c *common) Helper() {
// subtests complete. Cleanup functions will be called in last added,
// first called order.
func (c *common) Cleanup(f func()) {
+ c.checkFuzzFn("Cleanup")
var pc [maxStackLen]uintptr
// Skip two extra frames to account for this function and runtime.Callers itself.
n := runtime.Callers(2, pc[:])
@@ -1015,6 +1037,7 @@ func (c *common) Cleanup(f func()) {
// Each subsequent call to t.TempDir returns a unique directory;
// if the directory creation fails, TempDir terminates the test by calling Fatal.
func (c *common) TempDir() string {
+ c.checkFuzzFn("TempDir")
// Use a single parent directory for all the temporary directories
// created by a test, each numbered sequentially.
c.tempDirMu.Lock()
@@ -1080,6 +1103,7 @@ func (c *common) TempDir() string {
//
// This cannot be used in parallel tests.
func (c *common) Setenv(key, value string) {
+ c.checkFuzzFn("Setenv")
prevValue, ok := os.LookupEnv(key)
if err := os.Setenv(key, value); err != nil {
From ad7db1f90fb66f00f5b020360aabd9f27d1c764f Mon Sep 17 00:00:00 2001
From: Carlo Alberto Ferraris
Date: Thu, 14 Oct 2021 16:50:41 +0900
Subject: [PATCH 076/406] sync: avoid a dynamic check in WaitGroup on 64-bit
architectures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
uint64 is guaranteed by the compiler to be aligned on 64-bit archs.
By using uint64+uint32 instead of [3]uint32 we can make use of the
guaranteed alignment to avoid the run-time alignment check.
On linux/amd64:
name old time/op new time/op delta
WaitGroupUncontended-4 8.84ns ± 3% 7.62ns ± 4% -13.72% (p=0.000 n=17+18)
WaitGroupAddDone-4 66.8ns ± 3% 45.9ns ± 2% -31.31% (p=0.000 n=20+18)
WaitGroupAddDoneWork-4 79.2ns ± 1% 56.6ns ± 1% -28.54% (p=0.000 n=17+20)
WaitGroupWait-4 2.83ns ± 2% 2.58ns ± 2% -9.05% (p=0.000 n=18+20)
WaitGroupWaitWork-4 16.8ns ± 6% 16.5ns ± 6% ~ (p=0.072 n=20+18)
WaitGroupActuallyWait-4 263ns ± 2% 261ns ± 5% ~ (p=0.063 n=18+20)
Change-Id: I314340f2ed8a47d8b9c15f8a3b07e41f252f4831
Reviewed-on: https://go-review.googlesource.com/c/go/+/189837
Reviewed-by: Cherry Mui
Reviewed-by: Ian Lance Taylor
Run-TryBot: Cherry Mui
TryBot-Result: Go Bot
---
src/sync/waitgroup.go | 22 ++++++++++++++--------
1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/src/sync/waitgroup.go b/src/sync/waitgroup.go
index e81a493dea..9c6662d04b 100644
--- a/src/sync/waitgroup.go
+++ b/src/sync/waitgroup.go
@@ -22,18 +22,24 @@ type WaitGroup struct {
// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
// 64-bit atomic operations require 64-bit alignment, but 32-bit
- // compilers do not ensure it. So we allocate 12 bytes and then use
- // the aligned 8 bytes in them as state, and the other 4 as storage
- // for the sema.
- state1 [3]uint32
+ // compilers only guarantee that 64-bit fields are 32-bit aligned.
+ // For this reason on 32 bit architectures we need to check in state()
+ // if state1 is aligned or not, and dynamically "swap" the field order if
+ // needed.
+ state1 uint64
+ state2 uint32
}
-// state returns pointers to the state and sema fields stored within wg.state1.
+// state returns pointers to the state and sema fields stored within wg.state*.
func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {
- if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
- return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]
+ if unsafe.Alignof(wg.state1) == 8 || uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
+ // state1 is 64-bit aligned: nothing to do.
+ return &wg.state1, &wg.state2
} else {
- return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]
+ // state1 is 32-bit aligned but not 64-bit aligned: this means that
+ // (&state1)+4 is 64-bit aligned.
+ state := (*[3]uint32)(unsafe.Pointer(&wg.state1))
+ return (*uint64)(unsafe.Pointer(&state[1])), &state[0]
}
}
From 6c3cd5d2eb7149c9c1ced7d70c3f4157f27c1588 Mon Sep 17 00:00:00 2001
From: Archana R
Date: Wed, 13 Oct 2021 03:50:06 -0500
Subject: [PATCH 077/406] internal/bytealg: port bytes.Index and bytes.Count to
reg ABI on ppc64x
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This change adds support for the reg ABI to the Index and Count
functions for ppc64/ppc64le.
Most Index and Count benchmarks show improvement in performance on
POWER9 with this change. Similar numbers observed on POWER8 and POWER10.
name old time/op new time/op delta
Index/32 71.0ns ± 0% 67.9ns ± 0% -4.42% (p=0.001 n=7+6)
IndexEasy/10 17.5ns ± 0% 17.2ns ± 0% -1.30% (p=0.001 n=7+7)
name old time/op new time/op delta
Count/10 26.6ns ± 0% 25.0ns ± 1% -6.02% (p=0.001 n=7+7)
Count/32 78.6ns ± 0% 74.7ns ± 0% -4.97% (p=0.001 n=7+7)
Count/4K 5.03µs ± 0% 5.03µs ± 0% -0.07% (p=0.000 n=6+7)
CountEasy/10 26.9ns ± 0% 25.2ns ± 1% -6.31% (p=0.001 n=7+7)
CountSingle/32 11.8ns ± 0% 9.9ns ± 0% -15.70% (p=0.002 n=6+6)
Change-Id: Ibd146c04f8107291c55f9e6100b8264dfccc41ae
Reviewed-on: https://go-review.googlesource.com/c/go/+/355509
Reviewed-by: Cherry Mui
Reviewed-by: Lynn Boger
Run-TryBot: Cherry Mui
TryBot-Result: Go Bot
---
src/internal/bytealg/count_ppc64x.s | 24 +++++++++++++++---
src/internal/bytealg/index_ppc64x.s | 39 +++++++++++++++++++++++++++--
2 files changed, 58 insertions(+), 5 deletions(-)
diff --git a/src/internal/bytealg/count_ppc64x.s b/src/internal/bytealg/count_ppc64x.s
index 94163cbd8a..dbafd06edc 100644
--- a/src/internal/bytealg/count_ppc64x.s
+++ b/src/internal/bytealg/count_ppc64x.s
@@ -8,24 +8,37 @@
#include "go_asm.h"
#include "textflag.h"
-TEXT ·Count(SB), NOSPLIT|NOFRAME, $0-40
+TEXT ·Count(SB),NOSPLIT|NOFRAME,$0-40
+#ifdef GOEXPERIMENT_regabiargs
+// R3 = byte array pointer
+// R4 = length
+ MOVBZ R6,R5 // R5 = byte
+#else
+
MOVD b_base+0(FP), R3 // R3 = byte array pointer
MOVD b_len+8(FP), R4 // R4 = length
MOVBZ c+24(FP), R5 // R5 = byte
MOVD $ret+32(FP), R14 // R14 = &ret
+#endif
BR countbytebody<>(SB)
-TEXT ·CountString(SB), NOSPLIT|NOFRAME, $0-32
+TEXT ·CountString(SB), NOSPLIT|NOFRAME, $0-32
+#ifdef GOEXPERIMENT_regabiargs
+// R3 = byte array pointer
+// R4 = length
+ MOVBZ R5,R5 // R5 = byte
+#else
MOVD s_base+0(FP), R3 // R3 = string
MOVD s_len+8(FP), R4 // R4 = length
MOVBZ c+16(FP), R5 // R5 = byte
MOVD $ret+24(FP), R14 // R14 = &ret
+#endif
BR countbytebody<>(SB)
// R3: addr of string
// R4: len of string
// R5: byte to count
-// R14: addr for return value
+// R14: addr for return value when not regabi
// endianness shouldn't matter since we are just counting and order
// is irrelevant
TEXT countbytebody<>(SB), NOSPLIT|NOFRAME, $0-0
@@ -94,5 +107,10 @@ next2:
BR small
done:
+#ifdef GOEXPERIMENT_regabiargs
+ MOVD R18, R3 // return count
+#else
MOVD R18, (R14) // return count
+#endif
+
RET
diff --git a/src/internal/bytealg/index_ppc64x.s b/src/internal/bytealg/index_ppc64x.s
index 3ed9442125..f587a8ae25 100644
--- a/src/internal/bytealg/index_ppc64x.s
+++ b/src/internal/bytealg/index_ppc64x.s
@@ -46,12 +46,20 @@ DATA byteswap<>+8(SB)/8, $0x0f0e0d0c0b0a0908
GLOBL byteswap<>+0(SB), RODATA, $16
-TEXT ·Index(SB), NOSPLIT|NOFRAME, $0-56
+TEXT ·Index(SB),NOSPLIT|NOFRAME,$0-56
+#ifdef GOEXPERIMENT_regabiargs
+// R3 = byte array pointer
+// R4 = length
+ MOVD R6,R5 // R5 = separator pointer
+ MOVD R7,R6 // R6 = separator length
+#else
MOVD a_base+0(FP), R3 // R3 = byte array pointer
MOVD a_len+8(FP), R4 // R4 = length
MOVD b_base+24(FP), R5 // R5 = separator pointer
MOVD b_len+32(FP), R6 // R6 = separator length
MOVD $ret+48(FP), R14 // R14 = &ret
+#endif
+
#ifdef GOARCH_ppc64le
MOVBZ internal∕cpu·PPC64+const_offsetPPC64HasPOWER9(SB), R7
@@ -63,12 +71,15 @@ TEXT ·Index(SB), NOSPLIT|NOFRAME, $0-56
power8:
BR indexbody<>(SB)
-TEXT ·IndexString(SB), NOSPLIT|NOFRAME, $0-40
+TEXT ·IndexString(SB),NOSPLIT|NOFRAME,$0-40
+#ifndef GOEXPERIMENT_regabiargs
MOVD a_base+0(FP), R3 // R3 = string
MOVD a_len+8(FP), R4 // R4 = length
MOVD b_base+16(FP), R5 // R5 = separator pointer
MOVD b_len+24(FP), R6 // R6 = separator length
MOVD $ret+32(FP), R14 // R14 = &ret
+#endif
+
#ifdef GOARCH_ppc64le
MOVBZ internal∕cpu·PPC64+const_offsetPPC64HasPOWER9(SB), R7
@@ -420,8 +431,12 @@ next17:
BR index17to32loop // Continue
notfound:
+#ifdef GOEXPERIMENT_regabiargs
+ MOVD $-1, R3 // Return -1 if not found
+#else
MOVD $-1, R8 // Return -1 if not found
MOVD R8, (R14)
+#endif
RET
index33plus:
@@ -432,12 +447,20 @@ foundR25:
SRD $3, R25 // Convert from bits to bytes
ADD R25, R7 // Add to current string address
SUB R3, R7 // Subtract from start of string
+#ifdef GOEXPERIMENT_regabiargs
+ MOVD R7, R3 // Return byte where found
+#else
MOVD R7, (R14) // Return byte where found
+#endif
RET
found:
SUB R3, R7 // Return byte where found
+#ifdef GOEXPERIMENT_regabiargs
+ MOVD R7, R3
+#else
MOVD R7, (R14)
+#endif
RET
TEXT indexbodyp9<>(SB), NOSPLIT|NOFRAME, $0
@@ -746,8 +769,12 @@ next17:
BR index17to32loop // Continue
notfound:
+#ifdef GOEXPERIMENT_regabiargs
+ MOVD $-1, R3 // Return -1 if not found
+#else
MOVD $-1, R8 // Return -1 if not found
MOVD R8, (R14)
+#endif
RET
index33plus:
@@ -758,11 +785,19 @@ foundR25:
SRD $3, R25 // Convert from bits to bytes
ADD R25, R7 // Add to current string address
SUB R3, R7 // Subtract from start of string
+#ifdef GOEXPERIMENT_regabiargs
+ MOVD R7, R3 // Return byte where found
+#else
MOVD R7, (R14) // Return byte where found
+#endif
RET
found:
SUB R3, R7 // Return byte where found
+#ifdef GOEXPERIMENT_regabiargs
+ MOVD R7, R3
+#else
MOVD R7, (R14)
+#endif
RET
From 7999fd4710e7f987e86d7b32dd9af31ced4810ba Mon Sep 17 00:00:00 2001
From: "Matt T. Proud"
Date: Tue, 19 Oct 2021 10:09:06 +0200
Subject: [PATCH 078/406] errors: mention Is methods should not call Unwrap
errors.Is internally unwraps the error until the error matches the
target. Because of this, a user-authored Is method on an error type
need not call errors.Unwrap on itself or the target, because that would
make the unwrapping operation O(N^2). It is a subtle detail to remind
authors for resource efficiency reasons.
Change-Id: Ic1ba59a5bdbfe2c7cb51a2cba2537ab6de4a13ff
Reviewed-on: https://go-review.googlesource.com/c/go/+/356789
Reviewed-by: Jean de Klerk
Reviewed-by: Damien Neil
Trust: Jean de Klerk
Trust: Damien Neil
Run-TryBot: Jean de Klerk
TryBot-Result: Go Bot
---
src/errors/wrap.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/errors/wrap.go b/src/errors/wrap.go
index 4eb4f9ae37..b73d5a8ce2 100644
--- a/src/errors/wrap.go
+++ b/src/errors/wrap.go
@@ -35,7 +35,8 @@ func Unwrap(err error) error {
// func (m MyError) Is(target error) bool { return target == fs.ErrExist }
//
// then Is(MyError{}, fs.ErrExist) returns true. See syscall.Errno.Is for
-// an example in the standard library.
+// an example in the standard library. An Is method should only shallowly
+// compare err and the target and not call Unwrap on either.
func Is(err, target error) bool {
if target == nil {
return err == target
From 1d63052782a7535a3d4ce4557fd23fd97699b249 Mon Sep 17 00:00:00 2001
From: Michael Matloob
Date: Fri, 17 Sep 2021 19:38:33 -0400
Subject: [PATCH 079/406] cmd/go: support replaces in the go.work file
Add support for replace directives in the go.work file. If there are
conflicting replaces in go.mod files, suggest that users add an
overriding replace in the go.work file.
Add HighestReplaced to MainModules so that it accounts for the
replacements in the go.work file.
(Reviewers: I'm not totally sure that HighestReplace is computed
correctly. Could you take a closer look at that?)
For #45713
Change-Id: I1d789219ca1dd065ba009ce5d38db9a1fc38ba83
Reviewed-on: https://go-review.googlesource.com/c/go/+/352812
Trust: Michael Matloob
Run-TryBot: Michael Matloob
TryBot-Result: Go Bot
Reviewed-by: Bryan C. Mills
---
src/cmd/go/internal/modcmd/vendor.go | 2 +-
src/cmd/go/internal/modget/get.go | 4 +-
src/cmd/go/internal/modload/build.go | 14 +-
src/cmd/go/internal/modload/import.go | 60 ++++---
src/cmd/go/internal/modload/init.go | 157 ++++++++++++------
src/cmd/go/internal/modload/load.go | 6 +-
src/cmd/go/internal/modload/modfile.go | 157 ++++++++++--------
src/cmd/go/internal/modload/query.go | 40 +----
src/cmd/go/internal/modload/vendor.go | 2 +-
src/cmd/go/testdata/script/work_edit.txt | 2 +-
src/cmd/go/testdata/script/work_replace.txt | 55 ++++++
.../testdata/script/work_replace_conflict.txt | 53 ++++++
.../script/work_replace_conflict_override.txt | 57 +++++++
13 files changed, 409 insertions(+), 200 deletions(-)
create mode 100644 src/cmd/go/testdata/script/work_replace.txt
create mode 100644 src/cmd/go/testdata/script/work_replace_conflict.txt
create mode 100644 src/cmd/go/testdata/script/work_replace_conflict_override.txt
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index 92348b8897..57189b4607 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -128,7 +128,7 @@ func runVendor(ctx context.Context, cmd *base.Command, args []string) {
}
for _, m := range vendorMods {
- replacement, _ := modload.Replacement(m)
+ replacement := modload.Replacement(m)
line := moduleLine(m, replacement)
io.WriteString(w, line)
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index c634512072..2c48c3c444 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -1575,7 +1575,7 @@ func (r *resolver) checkPackageProblems(ctx context.Context, pkgPatterns []strin
i := i
m := r.buildList[i]
mActual := m
- if mRepl, _ := modload.Replacement(m); mRepl.Path != "" {
+ if mRepl := modload.Replacement(m); mRepl.Path != "" {
mActual = mRepl
}
old := module.Version{Path: m.Path, Version: r.initialVersion[m.Path]}
@@ -1583,7 +1583,7 @@ func (r *resolver) checkPackageProblems(ctx context.Context, pkgPatterns []strin
continue
}
oldActual := old
- if oldRepl, _ := modload.Replacement(old); oldRepl.Path != "" {
+ if oldRepl := modload.Replacement(old); oldRepl.Path != "" {
oldActual = oldRepl
}
if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) {
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index da50743138..0e0292ec15 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -239,7 +239,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
}
// completeFromModCache fills in the extra fields in m using the module cache.
- completeFromModCache := func(m *modinfo.ModulePublic, replacedFrom string) {
+ completeFromModCache := func(m *modinfo.ModulePublic) {
checksumOk := func(suffix string) bool {
return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
@@ -258,7 +258,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if m.GoVersion == "" && checksumOk("/go.mod") {
// Load the go.mod file to determine the Go version, since it hasn't
// already been populated from rawGoVersion.
- if summary, err := rawGoModSummary(mod, replacedFrom); err == nil && summary.goVersion != "" {
+ if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
m.GoVersion = summary.goVersion
}
}
@@ -288,11 +288,11 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if rs == nil {
// If this was an explicitly-versioned argument to 'go mod download' or
// 'go list -m', report the actual requested version, not its replacement.
- completeFromModCache(info, "") // Will set m.Error in vendor mode.
+ completeFromModCache(info) // Will set m.Error in vendor mode.
return info
}
- r, replacedFrom := Replacement(m)
+ r := Replacement(m)
if r.Path == "" {
if cfg.BuildMod == "vendor" {
// It's tempting to fill in the "Dir" field to point within the vendor
@@ -301,7 +301,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
// interleave packages from different modules if one module path is a
// prefix of the other.
} else {
- completeFromModCache(info, "")
+ completeFromModCache(info)
}
return info
}
@@ -321,12 +321,12 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
if filepath.IsAbs(r.Path) {
info.Replace.Dir = r.Path
} else {
- info.Replace.Dir = filepath.Join(replacedFrom, r.Path)
+ info.Replace.Dir = filepath.Join(replaceRelativeTo(), r.Path)
}
info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
}
if cfg.BuildMod != "vendor" {
- completeFromModCache(info.Replace, replacedFrom)
+ completeFromModCache(info.Replace)
info.Dir = info.Replace.Dir
info.GoMod = info.Replace.GoMod
info.Retracted = info.Replace.Retracted
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index e64677acd0..bc2b0a0230 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -426,35 +426,33 @@ func queryImport(ctx context.Context, path string, rs *Requirements) (module.Ver
// To avoid spurious remote fetches, try the latest replacement for each
// module (golang.org/issue/26241).
var mods []module.Version
- for _, v := range MainModules.Versions() {
- if index := MainModules.Index(v); index != nil {
- for mp, mv := range index.highestReplaced {
- if !maybeInModule(path, mp) {
- continue
- }
- if mv == "" {
- // The only replacement is a wildcard that doesn't specify a version, so
- // synthesize a pseudo-version with an appropriate major version and a
- // timestamp below any real timestamp. That way, if the main module is
- // used from within some other module, the user will be able to upgrade
- // the requirement to any real version they choose.
- if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
- mv = module.ZeroPseudoVersion(pathMajor[1:])
- } else {
- mv = module.ZeroPseudoVersion("v0")
- }
- }
- mg, err := rs.Graph(ctx)
- if err != nil {
- return module.Version{}, err
- }
- if cmpVersion(mg.Selected(mp), mv) >= 0 {
- // We can't resolve the import by adding mp@mv to the module graph,
- // because the selected version of mp is already at least mv.
- continue
- }
- mods = append(mods, module.Version{Path: mp, Version: mv})
+ if MainModules != nil { // TODO(#48912): Ensure MainModules exists at this point, and remove the check.
+ for mp, mv := range MainModules.HighestReplaced() {
+ if !maybeInModule(path, mp) {
+ continue
}
+ if mv == "" {
+ // The only replacement is a wildcard that doesn't specify a version, so
+ // synthesize a pseudo-version with an appropriate major version and a
+ // timestamp below any real timestamp. That way, if the main module is
+ // used from within some other module, the user will be able to upgrade
+ // the requirement to any real version they choose.
+ if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
+ mv = module.ZeroPseudoVersion(pathMajor[1:])
+ } else {
+ mv = module.ZeroPseudoVersion("v0")
+ }
+ }
+ mg, err := rs.Graph(ctx)
+ if err != nil {
+ return module.Version{}, err
+ }
+ if cmpVersion(mg.Selected(mp), mv) >= 0 {
+ // We can't resolve the import by adding mp@mv to the module graph,
+ // because the selected version of mp is already at least mv.
+ continue
+ }
+ mods = append(mods, module.Version{Path: mp, Version: mv})
}
}
@@ -485,7 +483,7 @@ func queryImport(ctx context.Context, path string, rs *Requirements) (module.Ver
// The package path is not valid to fetch remotely,
// so it can only exist in a replaced module,
// and we know from the above loop that it is not.
- replacement, _ := Replacement(mods[0])
+ replacement := Replacement(mods[0])
return module.Version{}, &PackageNotInModuleError{
Mod: mods[0],
Query: "latest",
@@ -659,11 +657,11 @@ func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, i
if modRoot := MainModules.ModRoot(mod); modRoot != "" {
return modRoot, true, nil
}
- if r, replacedFrom := Replacement(mod); r.Path != "" {
+ if r := Replacement(mod); r.Path != "" {
if r.Version == "" {
dir = r.Path
if !filepath.IsAbs(dir) {
- dir = filepath.Join(replacedFrom, dir)
+ dir = filepath.Join(replaceRelativeTo(), dir)
}
// Ensure that the replacement directory actually exists:
// dirInModule does not report errors for missing modules,
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 621099beb3..0602aee0cc 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -69,9 +69,8 @@ var (
// roots are required but MainModules hasn't been initialized yet. Set to
// the modRoots of the main modules.
// modRoots != nil implies len(modRoots) > 0
- modRoots []string
- gopath string
- workFileGoVersion string
+ modRoots []string
+ gopath string
)
// Variable set in InitWorkfile
@@ -104,6 +103,10 @@ type MainModuleSet struct {
workFileGoVersion string
+ workFileReplaceMap map[module.Version]module.Version
+ // highest replaced version of each module path; empty string for wildcard-only replacements
+ highestReplaced map[string]string
+
indexMu sync.Mutex
indices map[module.Version]*modFileIndex
}
@@ -203,6 +206,10 @@ func (mms *MainModuleSet) ModContainingCWD() module.Version {
return mms.modContainingCWD
}
+func (mms *MainModuleSet) HighestReplaced() map[string]string {
+ return mms.highestReplaced
+}
+
// GoVersion returns the go version set on the single module, in module mode,
// or the go.work file in workspace mode.
func (mms *MainModuleSet) GoVersion() string {
@@ -217,6 +224,10 @@ func (mms *MainModuleSet) GoVersion() string {
return v
}
+func (mms *MainModuleSet) WorkFileReplaceMap() map[module.Version]module.Version {
+ return mms.workFileReplaceMap
+}
+
var MainModules *MainModuleSet
type Root int
@@ -275,6 +286,9 @@ func InitWorkfile() {
case "", "auto":
workFilePath = findWorkspaceFile(base.Cwd())
default:
+ if !filepath.IsAbs(cfg.WorkFile) {
+ base.Errorf("the path provided to -workfile must be an absolute path")
+ }
workFilePath = cfg.WorkFile
}
}
@@ -403,37 +417,6 @@ func Init() {
base.Fatalf("$GOPATH/go.mod exists but should not")
}
}
-
- if inWorkspaceMode() {
- var err error
- workFileGoVersion, modRoots, err = loadWorkFile(workFilePath)
- if err != nil {
- base.Fatalf("reading go.work: %v", err)
- }
- _ = TODOWorkspaces("Support falling back to individual module go.sum " +
- "files for sums not in the workspace sum file.")
- modfetch.GoSumFile = workFilePath + ".sum"
- } else if modRoots == nil {
- // We're in module mode, but not inside a module.
- //
- // Commands like 'go build', 'go run', 'go list' have no go.mod file to
- // read or write. They would need to find and download the latest versions
- // of a potentially large number of modules with no way to save version
- // information. We can succeed slowly (but not reproducibly), but that's
- // not usually a good experience.
- //
- // Instead, we forbid resolving import paths to modules other than std and
- // cmd. Users may still build packages specified with .go files on the
- // command line, but they'll see an error if those files import anything
- // outside std.
- //
- // This can be overridden by calling AllowMissingModuleImports.
- // For example, 'go get' does this, since it is expected to resolve paths.
- //
- // See golang.org/issue/32027.
- } else {
- modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum"
- }
}
// WillBeEnabled checks whether modules should be enabled but does not
@@ -568,16 +551,16 @@ func (goModDirtyError) Error() string {
var errGoModDirty error = goModDirtyError{}
-func loadWorkFile(path string) (goVersion string, modRoots []string, err error) {
+func loadWorkFile(path string) (goVersion string, modRoots []string, replaces []*modfile.Replace, err error) {
_ = TODOWorkspaces("Clean up and write back the go.work file: add module paths for workspace modules.")
workDir := filepath.Dir(path)
workData, err := lockedfile.Read(path)
if err != nil {
- return "", nil, err
+ return "", nil, nil, err
}
wf, err := modfile.ParseWork(path, workData, nil)
if err != nil {
- return "", nil, err
+ return "", nil, nil, err
}
if wf.Go != nil {
goVersion = wf.Go.Version
@@ -589,12 +572,12 @@ func loadWorkFile(path string) (goVersion string, modRoots []string, err error)
modRoot = filepath.Join(workDir, modRoot)
}
if seen[modRoot] {
- return "", nil, fmt.Errorf("path %s appears multiple times in workspace", modRoot)
+ return "", nil, nil, fmt.Errorf("path %s appears multiple times in workspace", modRoot)
}
seen[modRoot] = true
modRoots = append(modRoots, modRoot)
}
- return goVersion, modRoots, nil
+ return goVersion, modRoots, wf.Replace, nil
}
// LoadModFile sets Target and, if there is a main module, parses the initial
@@ -621,10 +604,44 @@ func LoadModFile(ctx context.Context) *Requirements {
}
Init()
+ var (
+ workFileGoVersion string
+ workFileReplaces []*modfile.Replace
+ )
+ if inWorkspaceMode() {
+ var err error
+ workFileGoVersion, modRoots, workFileReplaces, err = loadWorkFile(workFilePath)
+ if err != nil {
+ base.Fatalf("reading go.work: %v", err)
+ }
+ _ = TODOWorkspaces("Support falling back to individual module go.sum " +
+ "files for sums not in the workspace sum file.")
+ modfetch.GoSumFile = workFilePath + ".sum"
+ } else if modRoots == nil {
+ // We're in module mode, but not inside a module.
+ //
+ // Commands like 'go build', 'go run', 'go list' have no go.mod file to
+ // read or write. They would need to find and download the latest versions
+ // of a potentially large number of modules with no way to save version
+ // information. We can succeed slowly (but not reproducibly), but that's
+ // not usually a good experience.
+ //
+ // Instead, we forbid resolving import paths to modules other than std and
+ // cmd. Users may still build packages specified with .go files on the
+ // command line, but they'll see an error if those files import anything
+ // outside std.
+ //
+ // This can be overridden by calling AllowMissingModuleImports.
+ // For example, 'go get' does this, since it is expected to resolve paths.
+ //
+ // See golang.org/issue/32027.
+ } else {
+ modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum"
+ }
if len(modRoots) == 0 {
_ = TODOWorkspaces("Instead of creating a fake module with an empty modroot, make MainModules.Len() == 0 mean that we're in module mode but not inside any module.")
mainModule := module.Version{Path: "command-line-arguments"}
- MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, "")
+ MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, "", nil)
goVersion := LatestGoVersion()
rawGoVersion.Store(mainModule, goVersion)
requirements = newRequirements(pruningForGoVersion(goVersion), nil, nil)
@@ -655,7 +672,7 @@ func LoadModFile(ctx context.Context) *Requirements {
}
}
- MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFileGoVersion)
+ MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFileGoVersion, workFileReplaces)
setDefaultBuildMod() // possibly enable automatic vendoring
rs := requirementsFromModFiles(ctx, modFiles)
@@ -758,7 +775,7 @@ func CreateModFile(ctx context.Context, modPath string) {
fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
modFile := new(modfile.File)
modFile.AddModuleStmt(modPath)
- MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, "")
+ MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, "", nil)
addGoStmt(modFile, modFile.Module.Mod, LatestGoVersion()) // Add the go directive before converted module requirements.
convertedFrom, err := convertLegacyConfig(modFile, modRoot)
@@ -893,7 +910,7 @@ func AllowMissingModuleImports() {
// makeMainModules creates a MainModuleSet and associated variables according to
// the given main modules.
-func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile.File, indices []*modFileIndex, workFileGoVersion string) *MainModuleSet {
+func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile.File, indices []*modFileIndex, workFileGoVersion string, workFileReplaces []*modfile.Replace) *MainModuleSet {
for _, m := range ms {
if m.Version != "" {
panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
@@ -901,13 +918,25 @@ func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile
}
modRootContainingCWD := findModuleRoot(base.Cwd())
mainModules := &MainModuleSet{
- versions: ms[:len(ms):len(ms)],
- inGorootSrc: map[module.Version]bool{},
- pathPrefix: map[module.Version]string{},
- modRoot: map[module.Version]string{},
- modFiles: map[module.Version]*modfile.File{},
- indices: map[module.Version]*modFileIndex{},
- workFileGoVersion: workFileGoVersion,
+ versions: ms[:len(ms):len(ms)],
+ inGorootSrc: map[module.Version]bool{},
+ pathPrefix: map[module.Version]string{},
+ modRoot: map[module.Version]string{},
+ modFiles: map[module.Version]*modfile.File{},
+ indices: map[module.Version]*modFileIndex{},
+ workFileGoVersion: workFileGoVersion,
+ workFileReplaceMap: toReplaceMap(workFileReplaces),
+ highestReplaced: map[string]string{},
+ }
+ replacedByWorkFile := make(map[string]bool)
+ replacements := make(map[module.Version]module.Version)
+ for _, r := range workFileReplaces {
+ replacedByWorkFile[r.Old.Path] = true
+ v, ok := mainModules.highestReplaced[r.Old.Path]
+ if !ok || semver.Compare(r.Old.Version, v) > 0 {
+ mainModules.highestReplaced[r.Old.Path] = r.Old.Version
+ }
+ replacements[r.Old] = r.New
}
for i, m := range ms {
mainModules.pathPrefix[m] = m.Path
@@ -933,6 +962,24 @@ func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile
mainModules.pathPrefix[m] = ""
}
}
+
+ if modFiles[i] != nil {
+ curModuleReplaces := make(map[module.Version]bool)
+ for _, r := range modFiles[i].Replace {
+ if replacedByWorkFile[r.Old.Path] {
+ continue
+ } else if prev, ok := replacements[r.Old]; ok && !curModuleReplaces[r.Old] {
+ base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v\nuse \"go mod editwork -replace %v=[override]\" to resolve", r.Old, prev, r.New, r.Old)
+ }
+ curModuleReplaces[r.Old] = true
+ replacements[r.Old] = r.New
+
+ v, ok := mainModules.highestReplaced[r.Old.Path]
+ if !ok || semver.Compare(r.Old.Version, v) > 0 {
+ mainModules.highestReplaced[r.Old.Path] = r.Old.Version
+ }
+ }
+ }
}
return mainModules
}
@@ -1471,7 +1518,7 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v, ok := rs.rootSelected(prefix); ok && v != "none" {
m := module.Version{Path: prefix, Version: v}
- r, _ := resolveReplacement(m)
+ r := resolveReplacement(m)
keep[r] = true
}
}
@@ -1483,7 +1530,7 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
if v := mg.Selected(prefix); v != "none" {
m := module.Version{Path: prefix, Version: v}
- r, _ := resolveReplacement(m)
+ r := resolveReplacement(m)
keep[r] = true
}
}
@@ -1495,7 +1542,7 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
// Save sums for the root modules (or their replacements), but don't
// incur the cost of loading the graph just to find and retain the sums.
for _, m := range rs.rootModules {
- r, _ := resolveReplacement(m)
+ r := resolveReplacement(m)
keep[modkey(r)] = true
if which == addBuildListZipSums {
keep[r] = true
@@ -1508,14 +1555,14 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
// The requirements from m's go.mod file are present in the module graph,
// so they are relevant to the MVS result regardless of whether m was
// actually selected.
- r, _ := resolveReplacement(m)
+ r := resolveReplacement(m)
keep[modkey(r)] = true
}
})
if which == addBuildListZipSums {
for _, m := range mg.BuildList() {
- r, _ := resolveReplacement(m)
+ r := resolveReplacement(m)
keep[r] = true
}
}
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index 3498c662f3..0f5b015000 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -607,10 +607,10 @@ func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string
tryMod := func(m module.Version) (string, bool) {
var root string
var err error
- if repl, replModRoot := Replacement(m); repl.Path != "" && repl.Version == "" {
+ if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
root = repl.Path
if !filepath.IsAbs(root) {
- root = filepath.Join(replModRoot, root)
+ root = filepath.Join(replaceRelativeTo(), root)
}
} else if repl.Path != "" {
root, err = modfetch.DownloadDir(repl)
@@ -1834,7 +1834,7 @@ func (ld *loader) checkMultiplePaths() {
firstPath := map[module.Version]string{}
for _, mod := range mods {
- src, _ := resolveReplacement(mod)
+ src := resolveReplacement(mod)
if prev, ok := firstPath[src]; !ok {
firstPath[src] = mod.Path
} else if prev != mod.Path {
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index bf05e92ba2..87e8a5e83d 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -99,14 +99,13 @@ func modFileGoVersion(modFile *modfile.File) string {
// A modFileIndex is an index of data corresponding to a modFile
// at a specific point in time.
type modFileIndex struct {
- data []byte
- dataNeedsFix bool // true if fixVersion applied a change while parsing data
- module module.Version
- goVersionV string // GoVersion with "v" prefix
- require map[module.Version]requireMeta
- replace map[module.Version]module.Version
- highestReplaced map[string]string // highest replaced version of each module path; empty string for wildcard-only replacements
- exclude map[module.Version]bool
+ data []byte
+ dataNeedsFix bool // true if fixVersion applied a change while parsing data
+ module module.Version
+ goVersionV string // GoVersion with "v" prefix
+ require map[module.Version]requireMeta
+ replace map[module.Version]module.Version
+ exclude map[module.Version]bool
}
type requireMeta struct {
@@ -187,7 +186,7 @@ func CheckRetractions(ctx context.Context, m module.Version) (err error) {
// Cannot be retracted.
return nil
}
- if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+ if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
// All versions of the module were replaced.
// Don't load retractions, since we'd just load the replacement.
return nil
@@ -204,11 +203,11 @@ func CheckRetractions(ctx context.Context, m module.Version) (err error) {
// We load the raw file here: the go.mod file may have a different module
// path that we expect if the module or its repository was renamed.
// We still want to apply retractions to other aliases of the module.
- rm, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+ rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return err
}
- summary, err := rawGoModSummary(rm, replacedFrom)
+ summary, err := rawGoModSummary(rm)
if err != nil {
return err
}
@@ -306,66 +305,107 @@ func CheckDeprecation(ctx context.Context, m module.Version) (deprecation string
// Don't look up deprecation.
return "", nil
}
- if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+ if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
// All versions of the module were replaced.
// We'll look up deprecation separately for the replacement.
return "", nil
}
- latest, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+ latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
if err != nil {
return "", err
}
- summary, err := rawGoModSummary(latest, replacedFrom)
+ summary, err := rawGoModSummary(latest)
if err != nil {
return "", err
}
return summary.deprecated, nil
}
-func replacement(mod module.Version, index *modFileIndex) (fromVersion string, to module.Version, ok bool) {
- if r, ok := index.replace[mod]; ok {
+func replacement(mod module.Version, replace map[module.Version]module.Version) (fromVersion string, to module.Version, ok bool) {
+ if r, ok := replace[mod]; ok {
return mod.Version, r, true
}
- if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
+ if r, ok := replace[module.Version{Path: mod.Path}]; ok {
return "", r, true
}
return "", module.Version{}, false
}
-// Replacement returns the replacement for mod, if any, and and the module root
-// directory of the main module containing the replace directive.
-// If there is no replacement for mod, Replacement returns
-// a module.Version with Path == "".
-func Replacement(mod module.Version) (module.Version, string) {
- _ = TODOWorkspaces("Support replaces in the go.work file.")
+// Replacement returns the replacement for mod, if any. If the path in the
+// module.Version is relative it's relative to the single main module outside
+// workspace mode, or the workspace's directory in workspace mode.
+func Replacement(mod module.Version) module.Version {
foundFrom, found, foundModRoot := "", module.Version{}, ""
+ if MainModules == nil {
+ return module.Version{}
+ }
+ if _, r, ok := replacement(mod, MainModules.WorkFileReplaceMap()); ok {
+ return r
+ }
for _, v := range MainModules.Versions() {
if index := MainModules.Index(v); index != nil {
- if from, r, ok := replacement(mod, index); ok {
+ if from, r, ok := replacement(mod, index.replace); ok {
modRoot := MainModules.ModRoot(v)
if foundModRoot != "" && foundFrom != from && found != r {
- _ = TODOWorkspaces("once the go.work file supports replaces, recommend them as a way to override conflicts")
base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
mod, modFilePath(foundModRoot), modFilePath(modRoot))
- return found, foundModRoot
+ return canonicalizeReplacePath(found, foundModRoot)
}
found, foundModRoot = r, modRoot
}
}
}
- return found, foundModRoot
+ return canonicalizeReplacePath(found, foundModRoot)
+}
+
+func replaceRelativeTo() string {
+ if workFilePath := WorkFilePath(); workFilePath != "" {
+ return filepath.Dir(workFilePath)
+ }
+ return MainModules.ModRoot(MainModules.mustGetSingleMainModule())
+}
+
+// canonicalizeReplacePath ensures that relative, on-disk, replaced module paths
+// are relative to the workspace directory (in workspace mode) or to the module's
+// directory (in module mode, as they already are).
+func canonicalizeReplacePath(r module.Version, modRoot string) module.Version {
+ if filepath.IsAbs(r.Path) || r.Version != "" {
+ return r
+ }
+ workFilePath := WorkFilePath()
+ if workFilePath == "" {
+ return r
+ }
+ abs := filepath.Join(modRoot, r.Path)
+ if rel, err := filepath.Rel(workFilePath, abs); err == nil {
+ return module.Version{Path: rel, Version: r.Version}
+ }
+ // We couldn't make the version's path relative to the workspace's path,
+ // so just return the absolute path. It's the best we can do.
+ return module.Version{Path: abs, Version: r.Version}
}
// resolveReplacement returns the module actually used to load the source code
// for m: either m itself, or the replacement for m (iff m is replaced).
// It also returns the modroot of the module providing the replacement if
// one was found.
-func resolveReplacement(m module.Version) (module.Version, string) {
- if r, replacedFrom := Replacement(m); r.Path != "" {
- return r, replacedFrom
+func resolveReplacement(m module.Version) module.Version {
+ if r := Replacement(m); r.Path != "" {
+ return r
}
- return m, ""
+ return m
+}
+
+func toReplaceMap(replacements []*modfile.Replace) map[module.Version]module.Version {
+ replaceMap := make(map[module.Version]module.Version, len(replacements))
+ for _, r := range replacements {
+ if prev, dup := replaceMap[r.Old]; dup && prev != r.New {
+ base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
+ }
+ replaceMap[r.Old] = r.New
+ }
+ return replaceMap
}
// indexModFile rebuilds the index of modFile.
@@ -396,21 +436,7 @@ func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsF
i.require[r.Mod] = requireMeta{indirect: r.Indirect}
}
- i.replace = make(map[module.Version]module.Version, len(modFile.Replace))
- for _, r := range modFile.Replace {
- if prev, dup := i.replace[r.Old]; dup && prev != r.New {
- base.Fatalf("go: conflicting replacements for %v:\n\t%v\n\t%v", r.Old, prev, r.New)
- }
- i.replace[r.Old] = r.New
- }
-
- i.highestReplaced = make(map[string]string)
- for _, r := range modFile.Replace {
- v, ok := i.highestReplaced[r.Old.Path]
- if !ok || semver.Compare(r.Old.Version, v) > 0 {
- i.highestReplaced[r.Old.Path] = r.Old.Version
- }
- }
+ i.replace = toReplaceMap(modFile.Replace)
i.exclude = make(map[module.Version]bool, len(modFile.Exclude))
for _, x := range modFile.Exclude {
@@ -552,7 +578,7 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
return summary, nil
}
- actual, replacedFrom := resolveReplacement(m)
+ actual := resolveReplacement(m)
if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && actual.Version != "" {
key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
if !modfetch.HaveSum(key) {
@@ -560,7 +586,7 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
}
}
- summary, err := rawGoModSummary(actual, replacedFrom)
+ summary, err := rawGoModSummary(actual)
if err != nil {
return nil, err
}
@@ -625,22 +651,21 @@ func goModSummary(m module.Version) (*modFileSummary, error) {
//
// rawGoModSummary cannot be used on the Target module.
-func rawGoModSummary(m module.Version, replacedFrom string) (*modFileSummary, error) {
+func rawGoModSummary(m module.Version) (*modFileSummary, error) {
if m.Path == "" && MainModules.Contains(m.Path) {
panic("internal error: rawGoModSummary called on the Target module")
}
type key struct {
- m module.Version
- replacedFrom string
+ m module.Version
}
type cached struct {
summary *modFileSummary
err error
}
- c := rawGoModSummaryCache.Do(key{m, replacedFrom}, func() interface{} {
+ c := rawGoModSummaryCache.Do(key{m}, func() interface{} {
summary := new(modFileSummary)
- name, data, err := rawGoModData(m, replacedFrom)
+ name, data, err := rawGoModData(m)
if err != nil {
return cached{nil, err}
}
@@ -690,15 +715,12 @@ var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result
//
// Unlike rawGoModSummary, rawGoModData does not cache its results in memory.
// Use rawGoModSummary instead unless you specifically need these bytes.
-func rawGoModData(m module.Version, replacedFrom string) (name string, data []byte, err error) {
+func rawGoModData(m module.Version) (name string, data []byte, err error) {
if m.Version == "" {
// m is a replacement module with only a file path.
dir := m.Path
if !filepath.IsAbs(dir) {
- if replacedFrom == "" {
- panic(fmt.Errorf("missing module root of main module providing replacement with relative path: %v", dir))
- }
- dir = filepath.Join(replacedFrom, dir)
+ dir = filepath.Join(replaceRelativeTo(), dir)
}
name = filepath.Join(dir, "go.mod")
if gomodActual, ok := fsys.OverlayPath(name); ok {
@@ -733,20 +755,19 @@ func rawGoModData(m module.Version, replacedFrom string) (name string, data []by
//
// If the queried latest version is replaced,
// queryLatestVersionIgnoringRetractions returns the replacement.
-func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, replacedFrom string, err error) {
+func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
type entry struct {
- latest module.Version
- replacedFrom string // if latest is a replacement
- err error
+ latest module.Version
+ err error
}
e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} {
ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
defer span.Done()
- if repl, replFrom := Replacement(module.Version{Path: path}); repl.Path != "" {
+ if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
// All versions of the module were replaced.
// No need to query.
- return &entry{latest: repl, replacedFrom: replFrom}
+ return &entry{latest: repl}
}
// Find the latest version of the module.
@@ -758,12 +779,12 @@ func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (la
return &entry{err: err}
}
latest := module.Version{Path: path, Version: rev.Version}
- if repl, replFrom := resolveReplacement(latest); repl.Path != "" {
- latest, replacedFrom = repl, replFrom
+ if repl := resolveReplacement(latest); repl.Path != "" {
+ latest = repl
}
- return &entry{latest: latest, replacedFrom: replacedFrom}
+ return &entry{latest: latest}
}).(*entry)
- return e.latest, e.replacedFrom, e.err
+ return e.latest, e.err
}
var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index 82979fbda1..c9ed129dbf 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -513,7 +513,7 @@ func QueryPackages(ctx context.Context, pattern, query string, current func(stri
pkgMods, modOnly, err := QueryPattern(ctx, pattern, query, current, allowed)
if len(pkgMods) == 0 && err == nil {
- replacement, _ := Replacement(modOnly.Mod)
+ replacement := Replacement(modOnly.Mod)
return nil, &PackageNotInModuleError{
Mod: modOnly.Mod,
Replacement: replacement,
@@ -669,7 +669,7 @@ func QueryPattern(ctx context.Context, pattern, query string, current func(strin
if err := firstError(m); err != nil {
return r, err
}
- replacement, _ := Replacement(r.Mod)
+ replacement := Replacement(r.Mod)
return r, &PackageNotInModuleError{
Mod: r.Mod,
Replacement: replacement,
@@ -969,7 +969,7 @@ func moduleHasRootPackage(ctx context.Context, m module.Version) (bool, error) {
// we don't need to verify it in go.sum. This makes 'go list -m -u' faster
// and simpler.
func versionHasGoMod(_ context.Context, m module.Version) (bool, error) {
- _, data, err := rawGoModData(m, "")
+ _, data, err := rawGoModData(m)
if err != nil {
return false, err
}
@@ -996,15 +996,10 @@ func lookupRepo(proxy, path string) (repo versionRepo, err error) {
repo = emptyRepo{path: path, err: err}
}
- // TODO(#45713): Join all the highestReplaced fields into a single value.
- for _, mm := range MainModules.Versions() {
- index := MainModules.Index(mm)
- if index == nil {
- continue
- }
- if _, ok := index.highestReplaced[path]; ok {
- return &replacementRepo{repo: repo}, nil
- }
+ if MainModules == nil {
+ return repo, err
+ } else if _, ok := MainModules.HighestReplaced()[path]; ok {
+ return &replacementRepo{repo: repo}, nil
}
return repo, err
@@ -1098,7 +1093,7 @@ func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
}
}
- if r, _ := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
+ if r := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
return info, err
}
return rr.replacementStat(v)
@@ -1108,24 +1103,7 @@ func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
info, err := rr.repo.Latest()
path := rr.ModulePath()
- highestReplaced, found := "", false
- for _, mm := range MainModules.Versions() {
- if index := MainModules.Index(mm); index != nil {
- if v, ok := index.highestReplaced[path]; ok {
- if !found {
- highestReplaced, found = v, true
- continue
- }
- if semver.Compare(v, highestReplaced) > 0 {
- highestReplaced = v
- }
- }
- }
- }
-
- if found {
- v := highestReplaced
-
+ if v, ok := MainModules.HighestReplaced()[path]; ok {
if v == "" {
// The only replacement is a wildcard that doesn't specify a version, so
// synthesize a pseudo-version with an appropriate major version and a
diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go
index daa5888075..a735cad905 100644
--- a/src/cmd/go/internal/modload/vendor.go
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -209,7 +209,7 @@ func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
}
for _, mod := range vendorReplaced {
- r, _ := Replacement(mod)
+ r := Replacement(mod)
if r == (module.Version{}) {
vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
continue
diff --git a/src/cmd/go/testdata/script/work_edit.txt b/src/cmd/go/testdata/script/work_edit.txt
index 001ac7f65c..979c1f98e0 100644
--- a/src/cmd/go/testdata/script/work_edit.txt
+++ b/src/cmd/go/testdata/script/work_edit.txt
@@ -30,7 +30,7 @@ cmp stdout go.work.want_print
go mod editwork -json -go 1.19 -directory b -dropdirectory c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
cmp stdout go.work.want_json
-go mod editwork -print -fmt -workfile unformatted
+go mod editwork -print -fmt -workfile $GOPATH/src/unformatted
cmp stdout formatted
-- m/go.mod --
diff --git a/src/cmd/go/testdata/script/work_replace.txt b/src/cmd/go/testdata/script/work_replace.txt
new file mode 100644
index 0000000000..5a4cb0eebb
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_replace.txt
@@ -0,0 +1,55 @@
+# Support replace statement in go.work file
+
+# Replacement in go.work file, and none in go.mod file.
+go list -m example.com/dep
+stdout 'example.com/dep v1.0.0 => ./dep'
+
+# Wildcard replacement in go.work file overrides version replacement in go.mod
+# file.
+go list -m example.com/other
+stdout 'example.com/other v1.0.0 => ./other2'
+
+-- go.work --
+directory m
+
+replace example.com/dep => ./dep
+replace example.com/other => ./other2
+
+-- m/go.mod --
+module example.com/m
+
+require example.com/dep v1.0.0
+require example.com/other v1.0.0
+
+replace example.com/other v1.0.0 => ./other
+-- m/m.go --
+package m
+
+import "example.com/dep"
+import "example.com/other"
+
+func F() {
+ dep.G()
+ other.H()
+}
+-- dep/go.mod --
+module example.com/dep
+-- dep/dep.go --
+package dep
+
+func G() {
+}
+-- other/go.mod --
+module example.com/other
+-- other/dep.go --
+package other
+
+func G() {
+}
+-- other2/go.mod --
+module example.com/other
+-- other2/dep.go --
+package other
+
+func G() {
+}
\ No newline at end of file
diff --git a/src/cmd/go/testdata/script/work_replace_conflict.txt b/src/cmd/go/testdata/script/work_replace_conflict.txt
new file mode 100644
index 0000000000..a2f76d13a0
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_replace_conflict.txt
@@ -0,0 +1,53 @@
+# Conflicting replaces in workspace modules returns error that suggests
+# overriding it in the go.work file.
+
+! go list -m example.com/dep
+stderr 'go: conflicting replacements for example.com/dep@v1.0.0:\n\t./dep1\n\t./dep2\nuse "go mod editwork -replace example.com/dep@v1.0.0=\[override\]" to resolve'
+go mod editwork -replace example.com/dep@v1.0.0=./dep1
+go list -m example.com/dep
+stdout 'example.com/dep v1.0.0 => ./dep1'
+
+-- foo --
+-- go.work --
+directory m
+directory n
+-- m/go.mod --
+module example.com/m
+
+require example.com/dep v1.0.0
+replace example.com/dep v1.0.0 => ./dep1
+-- m/m.go --
+package m
+
+import "example.com/dep"
+
+func F() {
+ dep.G()
+}
+-- n/go.mod --
+module example.com/n
+
+require example.com/dep v1.0.0
+replace example.com/dep v1.0.0 => ./dep2
+-- n/n.go --
+package n
+
+import "example.com/dep"
+
+func F() {
+ dep.G()
+}
+-- dep1/go.mod --
+module example.com/dep
+-- dep1/dep.go --
+package dep
+
+func G() {
+}
+-- dep2/go.mod --
+module example.com/dep
+-- dep2/dep.go --
+package dep
+
+func G() {
+}
diff --git a/src/cmd/go/testdata/script/work_replace_conflict_override.txt b/src/cmd/go/testdata/script/work_replace_conflict_override.txt
new file mode 100644
index 0000000000..ebb517dd7c
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_replace_conflict_override.txt
@@ -0,0 +1,57 @@
+# Conflicting workspace module replaces can be overridden by a replace in the
+# go.work file.
+
+go list -m example.com/dep
+stdout 'example.com/dep v1.0.0 => ./dep3'
+
+-- go.work --
+directory m
+directory n
+replace example.com/dep => ./dep3
+-- m/go.mod --
+module example.com/m
+
+require example.com/dep v1.0.0
+replace example.com/dep => ./dep1
+-- m/m.go --
+package m
+
+import "example.com/dep"
+
+func F() {
+ dep.G()
+}
+-- n/go.mod --
+module example.com/n
+
+require example.com/dep v1.0.0
+replace example.com/dep => ./dep2
+-- n/n.go --
+package n
+
+import "example.com/dep"
+
+func F() {
+ dep.G()
+}
+-- dep1/go.mod --
+module example.com/dep
+-- dep1/dep.go --
+package dep
+
+func G() {
+}
+-- dep2/go.mod --
+module example.com/dep
+-- dep2/dep.go --
+package dep
+
+func G() {
+}
+-- dep3/go.mod --
+module example.com/dep
+-- dep3/dep.go --
+package dep
+
+func G() {
+}
From d94498470bb09bb0606b0eff3248cb5b35e1a145 Mon Sep 17 00:00:00 2001
From: Michael Matloob
Date: Fri, 17 Sep 2021 19:38:33 -0400
Subject: [PATCH 080/406] cmd/go: add GOWORK to go env command
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
GOWORK will be set to the go.work file's path, if in workspace mode
or will be empty otherwise.
For #45713
Fixes #48589
Change-Id: I163ffaf274e0a41469c1f3b8514d6f90e20423b0
Reviewed-on: https://go-review.googlesource.com/c/go/+/355689
Trust: Michael Matloob
Trust: Daniel Martí
Run-TryBot: Michael Matloob
TryBot-Result: Go Bot
Reviewed-by: Bryan C. Mills
---
src/cmd/go/internal/envcmd/env.go | 5 ++++-
src/cmd/go/testdata/script/work.txt | 4 ++++
src/cmd/go/testdata/script/work_env.txt | 24 ++++++++++++++++++++++++
3 files changed, 32 insertions(+), 1 deletion(-)
create mode 100644 src/cmd/go/testdata/script/work_env.txt
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
index 1eb773407e..181d2a2ca1 100644
--- a/src/cmd/go/internal/envcmd/env.go
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -152,8 +152,11 @@ func ExtraEnvVars() []cfg.EnvVar {
} else if modload.Enabled() {
gomod = os.DevNull
}
+ modload.InitWorkfile()
+ gowork := modload.WorkFilePath()
return []cfg.EnvVar{
{Name: "GOMOD", Value: gomod},
+ {Name: "GOWORK", Value: gowork},
}
}
@@ -431,7 +434,7 @@ func getOrigEnv(key string) string {
func checkEnvWrite(key, val string) error {
switch key {
- case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOTOOLDIR", "GOVERSION":
+ case "GOEXE", "GOGCCFLAGS", "GOHOSTARCH", "GOHOSTOS", "GOMOD", "GOWORK", "GOTOOLDIR", "GOVERSION":
return fmt.Errorf("%s cannot be modified", key)
case "GOENV":
return fmt.Errorf("%s can only be set using the OS environment", key)
diff --git a/src/cmd/go/testdata/script/work.txt b/src/cmd/go/testdata/script/work.txt
index 657cd060cc..613f037615 100644
--- a/src/cmd/go/testdata/script/work.txt
+++ b/src/cmd/go/testdata/script/work.txt
@@ -1,8 +1,12 @@
! go mod initwork doesnotexist
stderr 'go: creating workspace file: no go.mod file exists in directory doesnotexist'
+go env GOWORK
+! stdout .
go mod initwork ./a ./b
cmp go.work go.work.want
+go env GOWORK
+stdout '^'$WORK'(\\|/)gopath(\\|/)src(\\|/)go.work$'
! go run example.com/b
stderr 'a(\\|/)a.go:4:8: no required module provides package rsc.io/quote; to add it:\n\tcd '$WORK(\\|/)gopath(\\|/)src(\\|/)a'\n\tgo get rsc.io/quote'
diff --git a/src/cmd/go/testdata/script/work_env.txt b/src/cmd/go/testdata/script/work_env.txt
new file mode 100644
index 0000000000..de67255696
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_env.txt
@@ -0,0 +1,24 @@
+go env GOWORK
+stdout '^'$GOPATH'[\\/]src[\\/]go.work$'
+go env
+stdout '^(set )?GOWORK="?'$GOPATH'[\\/]src[\\/]go.work"?$'
+
+cd ..
+go env GOWORK
+! stdout .
+go env
+stdout 'GOWORK=("")?'
+
+cd src
+go env GOWORK
+stdout 'go.work'
+
+! go env -w GOWORK=off
+stderr '^go: GOWORK cannot be modified$'
+
+-- go.work --
+go 1.18
+
+directory a
+-- a/go.mod --
+module example.com/a
From d7149e502dfb13fdc94439eebaf1823a6c6dd84f Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 20 Oct 2021 00:09:09 +0700
Subject: [PATCH 081/406] reflect: restore Value.Pointer implementation
CL 350691 added Value.UnsafePointer and make Value.Pointer call it
internally. It has a downside that Value.Pointer can now eligible to be
inlined, thus making un-intentional side effect, like the test in
fixedbugs/issue15329.go becomes flaky.
This CL restore Value.Pointer original implementation, pre CL 350691,
with the deprecation TODO removed.
Fixes #49067
Change-Id: I735af182f8e729294333ca906ffc062f477cfc99
Reviewed-on: https://go-review.googlesource.com/c/go/+/356949
Trust: Cuong Manh Le
Run-TryBot: Cuong Manh Le
TryBot-Result: Go Bot
Reviewed-by: Keith Randall
---
src/reflect/value.go | 37 ++++++++++++++++++++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 1d385f6bf9..63faa04964 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -1936,7 +1936,42 @@ func (v Value) OverflowUint(x uint64) bool {
//
// Deprecated: use uintptr(Value.UnsafePointer()) to get the equivalent result.
func (v Value) Pointer() uintptr {
- return uintptr(v.UnsafePointer())
+ k := v.kind()
+ switch k {
+ case Ptr:
+ if v.typ.ptrdata == 0 {
+ // Handle pointers to go:notinheap types directly,
+ // so we never materialize such pointers as an
+ // unsafe.Pointer. (Such pointers are always indirect.)
+ // See issue 42076.
+ return *(*uintptr)(v.ptr)
+ }
+ fallthrough
+ case Chan, Map, UnsafePointer:
+ return uintptr(v.pointer())
+ case Func:
+ if v.flag&flagMethod != 0 {
+ // As the doc comment says, the returned pointer is an
+ // underlying code pointer but not necessarily enough to
+ // identify a single function uniquely. All method expressions
+ // created via reflect have the same underlying code pointer,
+ // so their Pointers are equal. The function used here must
+ // match the one used in makeMethodValue.
+ f := methodValueCall
+ return **(**uintptr)(unsafe.Pointer(&f))
+ }
+ p := v.pointer()
+ // Non-nil func value points at data block.
+ // First word of data block is actual code.
+ if p != nil {
+ p = *(*unsafe.Pointer)(p)
+ }
+ return uintptr(p)
+
+ case Slice:
+ return (*SliceHeader)(v.ptr).Data
+ }
+ panic(&ValueError{"reflect.Value.Pointer", v.kind()})
}
// Recv receives and returns a value from the channel v.
From 07e5527249cb0b152a3807d67ea83bafd71d2496 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 20 Oct 2021 00:16:23 +0700
Subject: [PATCH 082/406] reflect: fix methodValueCall code pointer mismatch in
Value.Pointer
This is the port of CL 356809 for Value.Pointer to fix the mismatch of
methodValueCall code pointer.
Change-Id: I080ac41b94b44d878cd5896207a76a28c57fd48b
Reviewed-on: https://go-review.googlesource.com/c/go/+/356950
Trust: Cuong Manh Le
Run-TryBot: Cuong Manh Le
TryBot-Result: Go Bot
Reviewed-by: Cherry Mui
---
src/reflect/all_test.go | 7 +++++--
src/reflect/value.go | 3 +--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go
index 91aac9cccb..fcd0e15f0a 100644
--- a/src/reflect/all_test.go
+++ b/src/reflect/all_test.go
@@ -7724,9 +7724,12 @@ func TestNotInHeapDeref(t *testing.T) {
}
func TestMethodCallValueCodePtr(t *testing.T) {
- p := ValueOf(Point{}).Method(1).UnsafePointer()
+ m := ValueOf(Point{}).Method(1)
want := MethodValueCallCodePtr()
- if got := uintptr(p); got != want {
+ if got := uintptr(m.UnsafePointer()); got != want {
+ t.Errorf("methodValueCall code pointer mismatched, want: %v, got: %v", want, got)
+ }
+ if got := m.Pointer(); got != want {
t.Errorf("methodValueCall code pointer mismatched, want: %v, got: %v", want, got)
}
}
diff --git a/src/reflect/value.go b/src/reflect/value.go
index 63faa04964..7bb8ae5b97 100644
--- a/src/reflect/value.go
+++ b/src/reflect/value.go
@@ -1957,8 +1957,7 @@ func (v Value) Pointer() uintptr {
// created via reflect have the same underlying code pointer,
// so their Pointers are equal. The function used here must
// match the one used in makeMethodValue.
- f := methodValueCall
- return **(**uintptr)(unsafe.Pointer(&f))
+ return methodValueCallCodePtr()
}
p := v.pointer()
// Non-nil func value points at data block.
From 1b24c9e42e527b93a39b6a6b16c20672dd2d9a03 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Tue, 19 Oct 2021 13:44:01 -0700
Subject: [PATCH 083/406] go/types, types2: always accept type parameters when
running Manual test
This makes it easier to run tests on existing generic code that is
not using the `.go2` ending currently used by type checker tests.
For #49074.
Change-Id: I1501463c55dbe1d709918786e1a43b6d1bf1629a
Reviewed-on: https://go-review.googlesource.com/c/go/+/357050
Trust: Robert Griesemer
Run-TryBot: Robert Griesemer
Reviewed-by: Robert Findley
TryBot-Result: Go Bot
---
src/cmd/compile/internal/types2/check_test.go | 2 +-
src/go/types/check_test.go | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/cmd/compile/internal/types2/check_test.go b/src/cmd/compile/internal/types2/check_test.go
index bc68e76407..e71df87f2c 100644
--- a/src/cmd/compile/internal/types2/check_test.go
+++ b/src/cmd/compile/internal/types2/check_test.go
@@ -99,7 +99,7 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
}
var mode syntax.Mode
- if strings.HasSuffix(filenames[0], ".go2") {
+ if strings.HasSuffix(filenames[0], ".go2") || manual {
mode |= syntax.AllowGenerics
}
// parse files and collect parser errors
diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go
index e9df90c4ea..e6176738d1 100644
--- a/src/go/types/check_test.go
+++ b/src/go/types/check_test.go
@@ -210,7 +210,7 @@ func testFiles(t *testing.T, sizes Sizes, filenames []string, srcs [][]byte, man
}
mode := parser.AllErrors
- if !strings.HasSuffix(filenames[0], ".go2") {
+ if !strings.HasSuffix(filenames[0], ".go2") && !manual {
mode |= typeparams.DisallowParsing
}
From 9fa85518ff84420af644b1dd82ff311a39cd99c1 Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Mon, 18 Oct 2021 12:19:07 -0700
Subject: [PATCH 084/406] cmd/compile: substitute "". prefix with package in
more import locations
The "" stand-in for the current package needs to be substituted
in more places when importing, because of generics.
""..dict.conv4["".MyString]
when imported in main and then exported, this becomes
a..dict.conv4["".MyString]
and then the linker makes that into
a..dict.conv4[main.MyString]
Which isn't correct. We need to replace on import not just
function names, but also globals, which this CL does.
Change-Id: Ia04a23b5ffd60aeeaba72c807f69261105670f8e
Reviewed-on: https://go-review.googlesource.com/c/go/+/356570
Trust: Keith Randall
Trust: Dan Scales
Run-TryBot: Keith Randall
TryBot-Result: Go Bot
Reviewed-by: Dan Scales
---
src/cmd/compile/internal/typecheck/iimport.go | 32 ++++++++++++-------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/src/cmd/compile/internal/typecheck/iimport.go b/src/cmd/compile/internal/typecheck/iimport.go
index 52236ce837..6351fc37de 100644
--- a/src/cmd/compile/internal/typecheck/iimport.go
+++ b/src/cmd/compile/internal/typecheck/iimport.go
@@ -176,7 +176,7 @@ func ReadImports(pkg *types.Pkg, data string) {
}
for nSyms := ird.uint64(); nSyms > 0; nSyms-- {
- s := pkg.Lookup(p.stringAt(ird.uint64()))
+ s := pkg.Lookup(p.nameAt(ird.uint64()))
off := ird.uint64()
if _, ok := DeclImporter[s]; !ok {
@@ -188,18 +188,9 @@ func ReadImports(pkg *types.Pkg, data string) {
// Inline body index.
for nPkgs := ird.uint64(); nPkgs > 0; nPkgs-- {
pkg := p.pkgAt(ird.uint64())
- pkgPrefix := pkg.Prefix + "."
for nSyms := ird.uint64(); nSyms > 0; nSyms-- {
- s2 := p.stringAt(ird.uint64())
- // Function/method instantiation names may include "" to
- // represent the path name of the imported package (in type
- // names), so replace "" with pkg.Prefix. The "" in the names
- // will get replaced by the linker as well, so will not
- // appear in the executable. Include the dot to avoid
- // matching with struct tags ending in '"'.
- s2 = strings.Replace(s2, "\"\".", pkgPrefix, -1)
- s := pkg.Lookup(s2)
+ s := pkg.Lookup(p.nameAt(ird.uint64()))
off := ird.uint64()
if _, ok := inlineImporter[s]; !ok {
@@ -233,6 +224,22 @@ func (p *iimporter) stringAt(off uint64) string {
return p.stringData[spos : spos+slen]
}
+// nameAt is the same as stringAt, except it replaces instances
+// of "" with the path of the package being imported.
+func (p *iimporter) nameAt(off uint64) string {
+ s := p.stringAt(off)
+ // Names of objects (functions, methods, globals) may include ""
+ // to represent the path name of the imported package.
+ // Replace "" with the imported package prefix. This occurs
+ // specifically for generics where the names of instantiations
+ // and dictionaries contain package-qualified type names.
+ // Include the dot to avoid matching with struct tags ending in '"'.
+ if strings.Contains(s, "\"\".") {
+ s = strings.Replace(s, "\"\".", p.ipkg.Prefix+".", -1)
+ }
+ return s
+}
+
func (p *iimporter) posBaseAt(off uint64) *src.PosBase {
if posBase, ok := p.posBaseCache[off]; ok {
return posBase
@@ -288,6 +295,7 @@ func (p *iimporter) newReader(off uint64, pkg *types.Pkg) *importReader {
}
func (r *importReader) string() string { return r.p.stringAt(r.uint64()) }
+func (r *importReader) name() string { return r.p.nameAt(r.uint64()) }
func (r *importReader) posBase() *src.PosBase { return r.p.posBaseAt(r.uint64()) }
func (r *importReader) pkg() *types.Pkg { return r.p.pkgAt(r.uint64()) }
@@ -539,7 +547,7 @@ func (r *importReader) localIdent() *types.Sym { return r.ident(false) }
func (r *importReader) selector() *types.Sym { return r.ident(true) }
func (r *importReader) qualifiedIdent() *ir.Ident {
- name := r.string()
+ name := r.name()
pkg := r.pkg()
sym := pkg.Lookup(name)
return ir.NewIdent(src.NoXPos, sym)
From 2be5b846650aa4674a4eca7c11f303673b69a35a Mon Sep 17 00:00:00 2001
From: Keith Randall
Date: Mon, 18 Oct 2021 10:59:29 -0700
Subject: [PATCH 085/406] cmd/compile: allow importing and exporting of
ODYNAMICTYPE
Change-Id: I2fca7a801c85ed93c002c23bfcb0cf9593f1bdf4
Reviewed-on: https://go-review.googlesource.com/c/go/+/356571
Trust: Keith Randall
Trust: Dan Scales
Run-TryBot: Keith Randall
TryBot-Result: Go Bot
Reviewed-by: Dan Scales
---
src/cmd/compile/internal/typecheck/iexport.go | 13 +++++++
src/cmd/compile/internal/typecheck/iimport.go | 8 +++++
test/typeparam/issue49027.dir/a.go | 34 +++++++++++++++++++
test/typeparam/issue49027.dir/main.go | 8 +++++
4 files changed, 63 insertions(+)
diff --git a/src/cmd/compile/internal/typecheck/iexport.go b/src/cmd/compile/internal/typecheck/iexport.go
index e3dd10a56b..46865ba3fa 100644
--- a/src/cmd/compile/internal/typecheck/iexport.go
+++ b/src/cmd/compile/internal/typecheck/iexport.go
@@ -1763,6 +1763,19 @@ func (w *exportWriter) expr(n ir.Node) {
w.op(ir.OTYPE)
w.typ(n.Type())
+ case ir.ODYNAMICTYPE:
+ n := n.(*ir.DynamicType)
+ w.op(ir.ODYNAMICTYPE)
+ w.pos(n.Pos())
+ w.expr(n.X)
+ if n.ITab != nil {
+ w.bool(true)
+ w.expr(n.ITab)
+ } else {
+ w.bool(false)
+ }
+ w.typ(n.Type())
+
case ir.OTYPESW:
n := n.(*ir.TypeSwitchGuard)
w.op(ir.OTYPESW)
diff --git a/src/cmd/compile/internal/typecheck/iimport.go b/src/cmd/compile/internal/typecheck/iimport.go
index 6351fc37de..cb1e56bf51 100644
--- a/src/cmd/compile/internal/typecheck/iimport.go
+++ b/src/cmd/compile/internal/typecheck/iimport.go
@@ -1312,6 +1312,14 @@ func (r *importReader) node() ir.Node {
case ir.OTYPE:
return ir.TypeNode(r.typ())
+ case ir.ODYNAMICTYPE:
+ n := ir.NewDynamicType(r.pos(), r.expr())
+ if r.bool() {
+ n.ITab = r.expr()
+ }
+ n.SetType(r.typ())
+ return n
+
case ir.OTYPESW:
pos := r.pos()
var tag *ir.Ident
diff --git a/test/typeparam/issue49027.dir/a.go b/test/typeparam/issue49027.dir/a.go
index d3ec27deab..da88297965 100644
--- a/test/typeparam/issue49027.dir/a.go
+++ b/test/typeparam/issue49027.dir/a.go
@@ -15,7 +15,41 @@ func conv[T any](v interface{}) T {
func Conv2(v interface{}) (string, bool) {
return conv2[string](v)
}
+
func conv2[T any](v interface{}) (T, bool) {
x, ok := v.(T)
return x, ok
}
+
+func Conv3(v interface{}) string {
+ return conv3[string](v)
+}
+
+func conv3[T any](v interface{}) T {
+ switch v := v.(type) {
+ case T:
+ return v
+ default:
+ var z T
+ return z
+ }
+}
+
+type Mystring string
+
+func (Mystring) Foo() {
+}
+
+func Conv4(v interface{Foo()}) Mystring {
+ return conv4[Mystring](v)
+}
+
+func conv4[T interface{Foo()}](v interface{Foo()}) T {
+ switch v := v.(type) {
+ case T:
+ return v
+ default:
+ var z T
+ return z
+ }
+}
diff --git a/test/typeparam/issue49027.dir/main.go b/test/typeparam/issue49027.dir/main.go
index d0dc33d734..aa20a2fdfb 100644
--- a/test/typeparam/issue49027.dir/main.go
+++ b/test/typeparam/issue49027.dir/main.go
@@ -22,4 +22,12 @@ func main() {
if y != s {
panic(fmt.Sprintf("got %s wanted %s", y, s))
}
+ z := a.Conv3(s)
+ if z != s {
+ panic(fmt.Sprintf("got %s wanted %s", z, s))
+ }
+ w := a.Conv4(a.Mystring(s))
+ if w != a.Mystring(s) {
+ panic(fmt.Sprintf("got %s wanted %s", w, s))
+ }
}
From 4cc6a919077f27d9255536d6539118e65c1650a2 Mon Sep 17 00:00:00 2001
From: Robert Griesemer
Date: Thu, 14 Oct 2021 10:04:10 -0700
Subject: [PATCH 086/406] cmd/compile/internal/types2: implement generic
conversions
Fixes #47150.
Change-Id: I7531ca5917d4e52ca0b9211d6f2114495b19ba09
Reviewed-on: https://go-review.googlesource.com/c/go/+/356010
Trust: Robert Griesemer
Reviewed-by: Robert Findley
---
.../compile/internal/types2/conversions.go | 33 +++++
.../types2/testdata/examples/conversions.go2 | 130 ++++++++++++++++++
2 files changed, 163 insertions(+)
create mode 100644 src/cmd/compile/internal/types2/testdata/examples/conversions.go2
diff --git a/src/cmd/compile/internal/types2/conversions.go b/src/cmd/compile/internal/types2/conversions.go
index 6c26a4c446..8cd74b8f9a 100644
--- a/src/cmd/compile/internal/types2/conversions.go
+++ b/src/cmd/compile/internal/types2/conversions.go
@@ -89,6 +89,39 @@ func (x *operand) convertibleTo(check *Checker, T Type) bool {
return true
}
+ // TODO(gri) consider passing under(x.typ), under(T) into convertibleToImpl (optimization)
+ Vp, _ := under(x.typ).(*TypeParam)
+ Tp, _ := under(T).(*TypeParam)
+
+ // generic cases
+ // (generic operands cannot be constants, so we can ignore x.val)
+ switch {
+ case Vp != nil && Tp != nil:
+ x := *x // don't modify outer x
+ return Vp.underIs(func(V Type) bool {
+ x.typ = V
+ return Tp.underIs(func(T Type) bool {
+ return x.convertibleToImpl(check, T)
+ })
+ })
+ case Vp != nil:
+ x := *x // don't modify outer x
+ return Vp.underIs(func(V Type) bool {
+ x.typ = V
+ return x.convertibleToImpl(check, T)
+ })
+ case Tp != nil:
+ return Tp.underIs(func(T Type) bool {
+ return x.convertibleToImpl(check, T)
+ })
+ }
+
+ // non-generic case
+ return x.convertibleToImpl(check, T)
+}
+
+// convertibleToImpl should only be called by convertibleTo
+func (x *operand) convertibleToImpl(check *Checker, T Type) bool {
// "x's type and T have identical underlying types if tags are ignored"
V := x.typ
Vu := under(V)
diff --git a/src/cmd/compile/internal/types2/testdata/examples/conversions.go2 b/src/cmd/compile/internal/types2/testdata/examples/conversions.go2
new file mode 100644
index 0000000000..7e9e9745bb
--- /dev/null
+++ b/src/cmd/compile/internal/types2/testdata/examples/conversions.go2
@@ -0,0 +1,130 @@
+// Copyright 2021 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.
+
+package conversions
+
+import "unsafe"
+
+// "x is assignable to T"
+// - tested via assignability tests
+
+// "x's type and T have identical underlying types if tags are ignored"
+
+func _[X ~int, T ~int](x X) T { return T(x) }
+func _[X struct{f int "foo"}, T struct{f int "bar"}](x X) T { return T(x) }
+
+type Foo struct{f int "foo"}
+type Bar struct{f int "bar"}
+type Far struct{f float64 }
+
+func _[X Foo, T Bar](x X) T { return T(x) }
+func _[X Foo|Bar, T Bar](x X) T { return T(x) }
+func _[X Foo, T Foo|Bar](x X) T { return T(x) }
+func _[X Foo, T Far](x X) T { return T(x /* ERROR cannot convert */ ) }
+
+// "x's type and T are unnamed pointer types and their pointer base types
+// have identical underlying types if tags are ignored"
+
+func _[X ~*Foo, T ~*Bar](x X) T { return T(x) }
+func _[X ~*Foo|~*Bar, T ~*Bar](x X) T { return T(x) }
+func _[X ~*Foo, T ~*Foo|~*Bar](x X) T { return T(x) }
+func _[X ~*Foo, T ~*Far](x X) T { return T(x /* ERROR cannot convert */ ) }
+
+// "x's type and T are both integer or floating point types"
+
+func _[X Integer, T Integer](x X) T { return T(x) }
+func _[X Unsigned, T Integer](x X) T { return T(x) }
+func _[X Float, T Integer](x X) T { return T(x) }
+
+func _[X Integer, T Unsigned](x X) T { return T(x) }
+func _[X Unsigned, T Unsigned](x X) T { return T(x) }
+func _[X Float, T Unsigned](x X) T { return T(x) }
+
+func _[X Integer, T Float](x X) T { return T(x) }
+func _[X Unsigned, T Float](x X) T { return T(x) }
+func _[X Float, T Float](x X) T { return T(x) }
+
+func _[X, T Integer|Unsigned|Float](x X) T { return T(x) }
+func _[X, T Integer|~string](x X) T { return T(x /* ERROR cannot convert */ ) }
+
+// "x's type and T are both complex types"
+
+func _[X, T Complex](x X) T { return T(x) }
+func _[X, T Float|Complex](x X) T { return T(x /* ERROR cannot convert */ ) }
+
+// "x is an integer or a slice of bytes or runes and T is a string type"
+
+type myInt int
+type myString string
+
+func _[T ~string](x int) T { return T(x) }
+func _[T ~string](x myInt) T { return T(x) }
+func _[X Integer](x X) string { return string(x) }
+func _[X Integer](x X) myString { return myString(x) }
+func _[X Integer](x X) *string { return (*string)(x /* ERROR cannot convert */ ) }
+
+func _[T ~string](x []byte) T { return T(x) }
+func _[T ~string](x []rune) T { return T(x) }
+func _[X ~[]byte, T ~string](x X) T { return T(x) }
+func _[X ~[]rune, T ~string](x X) T { return T(x) }
+func _[X Integer|~[]byte|~[]rune, T ~string](x X) T { return T(x) }
+func _[X Integer|~[]byte|~[]rune, T ~*string](x X) T { return T(x /* ERROR cannot convert */ ) }
+
+// "x is a string and T is a slice of bytes or runes"
+
+func _[T ~[]byte](x string) T { return T(x) }
+func _[T ~[]rune](x string) T { return T(x) }
+func _[T ~[]rune](x *string) T { return T(x /* ERROR cannot convert */ ) }
+
+func _[X ~string, T ~[]byte](x X) T { return T(x) }
+func _[X ~string, T ~[]rune](x X) T { return T(x) }
+func _[X ~string, T ~[]byte|~[]rune](x X) T { return T(x) }
+func _[X ~*string, T ~[]byte|~[]rune](x X) T { return T(x /* ERROR cannot convert */ ) }
+
+// package unsafe:
+// "any pointer or value of underlying type uintptr can be converted into a unsafe.Pointer"
+
+type myUintptr uintptr
+
+func _[X ~uintptr](x X) unsafe.Pointer { return unsafe.Pointer(x) }
+func _[T unsafe.Pointer](x myUintptr) T { return T(x) }
+func _[T unsafe.Pointer](x int64) T { return T(x /* ERROR cannot convert */ ) }
+
+// "and vice versa"
+
+func _[T ~uintptr](x unsafe.Pointer) T { return T(x) }
+func _[X unsafe.Pointer](x X) uintptr { return uintptr(x) }
+func _[X unsafe.Pointer](x X) myUintptr { return myUintptr(x) }
+func _[X unsafe.Pointer](x X) int64 { return int64(x /* ERROR cannot convert */ ) }
+
+// "x is a slice, T is a pointer-to-array type,
+// and the slice and array types have identical element types."
+
+func _[X ~[]E, T ~*[10]E, E any](x X) T { return T(x) }
+func _[X ~[]E, T ~[10]E, E any](x X) T { return T(x /* ERROR cannot convert */ ) }
+
+// ----------------------------------------------------------------------------
+// The following declarations can be replaced by the exported types of the
+// constraints package once all builders support importing interfaces with
+// type constraints.
+
+type Signed interface {
+ ~int | ~int8 | ~int16 | ~int32 | ~int64
+}
+
+type Unsigned interface {
+ ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
+}
+
+type Integer interface {
+ Signed | Unsigned
+}
+
+type Float interface {
+ ~float32 | ~float64
+}
+
+type Complex interface {
+ ~complex64 | ~complex128
+}
From b091189762980836527c4aa50e3693632aea5144 Mon Sep 17 00:00:00 2001
From: Cuong Manh Le
Date: Wed, 20 Oct 2021 09:55:26 +0700
Subject: [PATCH 087/406] cmd/compile/internal/types2: print assignment
operation for invalid operation errors
When invoking check.binary for assignment operation, the expression will
be nil, thus for printing the assignment operation error message, we
need to reconstruct the statement from lhs, op, rhs.
Fixes #48472
Change-Id: Ie38c3dd8069b47e508968d6e43cedcf7536559ef
Reviewed-on: https://go-review.googlesource.com/c/go/+/357229
Trust: Cuong Manh Le
Run-TryBot: Cuong Manh Le