diff --git a/AUTHORS b/AUTHORS index 96704bd564..e2c8150ab0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1435,6 +1435,7 @@ Wei Guangjing Weichao Tang Weixie Cui <523516579@qq.com> Wembley G. Leach, Jr +Wen Yang Will Faught Will Storey Willem van der Schyff diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f79c4132b8..ea8b1b964e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -2746,6 +2746,7 @@ Weichao Tang Weilu Jia Weixie Cui <523516579@qq.com> Wembley G. Leach, Jr +Wen Yang Wenlei (Frank) He Wenzel Lowe Wil Selwood diff --git a/api/next/35044.txt b/api/next/35044.txt new file mode 100644 index 0000000000..0ed6f2e4d0 --- /dev/null +++ b/api/next/35044.txt @@ -0,0 +1 @@ +pkg crypto/x509, method (*CertPool) Clone() *CertPool #35044 \ No newline at end of file diff --git a/api/next/42710.txt b/api/next/42710.txt new file mode 100644 index 0000000000..7879758d16 --- /dev/null +++ b/api/next/42710.txt @@ -0,0 +1,2 @@ +pkg hash/maphash, func Bytes(Seed, []uint8) uint64 #42710 +pkg hash/maphash, func String(Seed, string) uint64 #42710 diff --git a/api/next/50340.txt b/api/next/50340.txt new file mode 100644 index 0000000000..211392cd25 --- /dev/null +++ b/api/next/50340.txt @@ -0,0 +1 @@ +pkg sort, func Find(int, func(int) int) (int, bool) #50340 diff --git a/api/next/50674.txt b/api/next/50674.txt new file mode 100644 index 0000000000..6b5bca3a9d --- /dev/null +++ b/api/next/50674.txt @@ -0,0 +1,9 @@ +pkg crypto/x509, func ParseRevocationList([]uint8) (*RevocationList, error) #50674 +pkg crypto/x509, method (*RevocationList) CheckSignatureFrom(*Certificate) error #50674 +pkg crypto/x509, type RevocationList struct, AuthorityKeyId []uint8 #50674 +pkg crypto/x509, type RevocationList struct, Extensions []pkix.Extension #50674 +pkg crypto/x509, type RevocationList struct, Issuer pkix.Name #50674 +pkg crypto/x509, type RevocationList struct, Raw []uint8 #50674 +pkg crypto/x509, type RevocationList struct, RawIssuer []uint8 #50674 +pkg crypto/x509, type RevocationList struct, RawTBSRevocationList []uint8 #50674 +pkg crypto/x509, type RevocationList struct, Signature []uint8 #50674 diff --git a/api/next/51082.txt b/api/next/51082.txt new file mode 100644 index 0000000000..b05997f985 --- /dev/null +++ b/api/next/51082.txt @@ -0,0 +1,61 @@ +pkg go/doc, method (*Package) HTML(string) []uint8 #51082 +pkg go/doc, method (*Package) Markdown(string) []uint8 #51082 +pkg go/doc, method (*Package) Parser() *comment.Parser #51082 +pkg go/doc, method (*Package) Printer() *comment.Printer #51082 +pkg go/doc, method (*Package) Synopsis(string) string #51082 +pkg go/doc, method (*Package) Text(string) []uint8 #51082 +pkg go/doc/comment, func DefaultLookupPackage(string) (string, bool) #51082 +pkg go/doc/comment, method (*DocLink) DefaultURL(string) string #51082 +pkg go/doc/comment, method (*Heading) DefaultID() string #51082 +pkg go/doc/comment, method (*List) BlankBefore() bool #51082 +pkg go/doc/comment, method (*List) BlankBetween() bool #51082 +pkg go/doc/comment, method (*Parser) Parse(string) *Doc #51082 +pkg go/doc/comment, method (*Printer) Comment(*Doc) []uint8 #51082 +pkg go/doc/comment, method (*Printer) HTML(*Doc) []uint8 #51082 +pkg go/doc/comment, method (*Printer) Markdown(*Doc) []uint8 #51082 +pkg go/doc/comment, method (*Printer) Text(*Doc) []uint8 #51082 +pkg go/doc/comment, type Block interface, unexported methods #51082 +pkg go/doc/comment, type Code struct #51082 +pkg go/doc/comment, type Code struct, Text string #51082 +pkg go/doc/comment, type Doc struct #51082 +pkg go/doc/comment, type Doc struct, Content []Block #51082 +pkg go/doc/comment, type Doc struct, Links []*LinkDef #51082 +pkg go/doc/comment, type DocLink struct #51082 +pkg go/doc/comment, type DocLink struct, ImportPath string #51082 +pkg go/doc/comment, type DocLink struct, Name string #51082 +pkg go/doc/comment, type DocLink struct, Recv string #51082 +pkg go/doc/comment, type DocLink struct, Text []Text #51082 +pkg go/doc/comment, type Heading struct #51082 +pkg go/doc/comment, type Heading struct, Text []Text #51082 +pkg go/doc/comment, type Italic string #51082 +pkg go/doc/comment, type Link struct #51082 +pkg go/doc/comment, type Link struct, Auto bool #51082 +pkg go/doc/comment, type Link struct, Text []Text #51082 +pkg go/doc/comment, type Link struct, URL string #51082 +pkg go/doc/comment, type LinkDef struct #51082 +pkg go/doc/comment, type LinkDef struct, Text string #51082 +pkg go/doc/comment, type LinkDef struct, URL string #51082 +pkg go/doc/comment, type LinkDef struct, Used bool #51082 +pkg go/doc/comment, type List struct #51082 +pkg go/doc/comment, type List struct, ForceBlankBefore bool #51082 +pkg go/doc/comment, type List struct, ForceBlankBetween bool #51082 +pkg go/doc/comment, type List struct, Items []*ListItem #51082 +pkg go/doc/comment, type ListItem struct #51082 +pkg go/doc/comment, type ListItem struct, Content []Block #51082 +pkg go/doc/comment, type ListItem struct, Number string #51082 +pkg go/doc/comment, type Paragraph struct #51082 +pkg go/doc/comment, type Paragraph struct, Text []Text #51082 +pkg go/doc/comment, type Parser struct #51082 +pkg go/doc/comment, type Parser struct, LookupPackage func(string) (string, bool) #51082 +pkg go/doc/comment, type Parser struct, LookupSym func(string, string) bool #51082 +pkg go/doc/comment, type Parser struct, Words map[string]string #51082 +pkg go/doc/comment, type Plain string #51082 +pkg go/doc/comment, type Printer struct #51082 +pkg go/doc/comment, type Printer struct, DocLinkBaseURL string #51082 +pkg go/doc/comment, type Printer struct, DocLinkURL func(*DocLink) string #51082 +pkg go/doc/comment, type Printer struct, HeadingID func(*Heading) string #51082 +pkg go/doc/comment, type Printer struct, HeadingLevel int #51082 +pkg go/doc/comment, type Printer struct, TextCodePrefix string #51082 +pkg go/doc/comment, type Printer struct, TextPrefix string #51082 +pkg go/doc/comment, type Printer struct, TextWidth int #51082 +pkg go/doc/comment, type Text interface, unexported methods #51082 diff --git a/api/next/51644.txt b/api/next/51644.txt new file mode 100644 index 0000000000..d93dbbf184 --- /dev/null +++ b/api/next/51644.txt @@ -0,0 +1,2 @@ +pkg encoding/binary, func AppendUvarint([]uint8, uint64) []uint8 #51644 +pkg encoding/binary, func AppendVarint([]uint8, int64) []uint8 #51644 diff --git a/doc/go1.19.html b/doc/go1.19.html index 857d8ed8ce..a813d59cb8 100644 --- a/doc/go1.19.html +++ b/doc/go1.19.html @@ -32,7 +32,22 @@ Do not send CLs removing the interior tags from such phrases.

Go command

- TODO: complete this section, or delete if not needed + TODO: complete this section. +

+ + +

+ The -trimpath flag, if set, is now included in the build settings + stamped into Go binaries by go build, and can be + examined using + go version -m + or debug.ReadBuildInfo. +

+

+ go generate now sets the GOROOT + environment variable explicitly in the generator's environment, so that + generators can locate the correct GOROOT even if built + with -trimpath.

New unix build constraint

@@ -76,6 +91,19 @@ Do not send CLs removing the interior tags from such phrases.

TODO: complete this section

+ +
image/draw
+
+

+ Draw with the Src operator preserves + non-premultiplied-alpha colors when destination and source images are + both *image.NRGBA (or both *image.NRGBA64). + This reverts a behavior change accidentally introduced by a Go 1.18 + library optimization, to match the behavior in Go 1.17 and earlier. +

+
+
+
net

@@ -93,9 +121,9 @@ Do not send CLs removing the interior tags from such phrases.

When a net package function or method returns an "I/O timeout" error, the error will now satisfy errors.Is(err, - context.Canceled). When a net package function returns - an "operation was canceled" error, the error will now satisfy - errors.Is(err, context.DeadlineExceeded). + context.DeadlineExceeded). When a net package function + returns an "operation was canceled" error, the error will now + satisfy errors.Is(err, context.Canceled). These changes are intended to make it easier for code to test for cases in which a context cancelation or timeout causes a net package function or method to return an error, while preserving @@ -104,6 +132,17 @@ Do not send CLs removing the interior tags from such phrases.

+
runtime
+
+

+ The GOROOT function now returns the empty string + (instead of "go") when the binary was built with + the -trimpath flag set and the GOROOT + variable is not set in the process environment. +

+
+
+
strconv

diff --git a/misc/cgo/gmp/gmp.go b/misc/cgo/gmp/gmp.go index 971a10aaac..0835fdc8de 100644 --- a/misc/cgo/gmp/gmp.go +++ b/misc/cgo/gmp/gmp.go @@ -333,10 +333,9 @@ func (z *Int) Abs(x *Int) *Int { // CmpInt compares x and y. The result is // -// -1 if x < y -// 0 if x == y -// +1 if x > y -// +// -1 if x < y +// 0 if x == y +// +1 if x > y func CmpInt(x, y *Int) int { x.doinit() y.doinit() diff --git a/misc/cgo/testcshared/cshared_test.go b/misc/cgo/testcshared/cshared_test.go index c9e9e5fe63..e4898778be 100644 --- a/misc/cgo/testcshared/cshared_test.go +++ b/misc/cgo/testcshared/cshared_test.go @@ -5,6 +5,7 @@ package cshared_test import ( + "bufio" "bytes" "debug/elf" "debug/pe" @@ -838,3 +839,51 @@ func TestGo2C2Go(t *testing.T) { run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m2") runExe(t, runenv, bin) } + +func TestIssue36233(t *testing.T) { + t.Parallel() + + // Test that the export header uses GoComplex64 and GoComplex128 + // for complex types. + + tmpdir, err := os.MkdirTemp("", "cshared-TestIssue36233") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + const exportHeader = "issue36233.h" + + run(t, nil, "go", "tool", "cgo", "-exportheader", exportHeader, "-objdir", tmpdir, "./issue36233/issue36233.go") + data, err := os.ReadFile(exportHeader) + if err != nil { + t.Fatal(err) + } + + funcs := []struct{ name, signature string }{ + {"exportComplex64", "GoComplex64 exportComplex64(GoComplex64 v)"}, + {"exportComplex128", "GoComplex128 exportComplex128(GoComplex128 v)"}, + {"exportComplexfloat", "GoComplex64 exportComplexfloat(GoComplex64 v)"}, + {"exportComplexdouble", "GoComplex128 exportComplexdouble(GoComplex128 v)"}, + } + + scanner := bufio.NewScanner(bytes.NewReader(data)) + var found int + for scanner.Scan() { + b := scanner.Bytes() + for _, fn := range funcs { + if bytes.Contains(b, []byte(fn.name)) { + found++ + if !bytes.Contains(b, []byte(fn.signature)) { + t.Errorf("function signature mismatch; got %q, want %q", b, fn.signature) + } + } + } + } + if err = scanner.Err(); err != nil { + t.Errorf("scanner encountered error: %v", err) + } + if found != len(funcs) { + t.Error("missing functions") + } +} diff --git a/misc/cgo/testcshared/testdata/issue36233/issue36233.go b/misc/cgo/testcshared/testdata/issue36233/issue36233.go new file mode 100644 index 0000000000..d0d1e5d50a --- /dev/null +++ b/misc/cgo/testcshared/testdata/issue36233/issue36233.go @@ -0,0 +1,29 @@ +// Copyright 2022 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 + +// #include +import "C" + +//export exportComplex64 +func exportComplex64(v complex64) complex64 { + return v +} + +//export exportComplex128 +func exportComplex128(v complex128) complex128 { + return v +} + +//export exportComplexfloat +func exportComplexfloat(v C.complexfloat) C.complexfloat { + return v +} + +//export exportComplexdouble +func exportComplexdouble(v C.complexdouble) C.complexdouble { + return v +} + +func main() {} diff --git a/misc/ios/go_ios_exec.go b/misc/ios/go_ios_exec.go index 34a734cda7..c275dd339c 100644 --- a/misc/ios/go_ios_exec.go +++ b/misc/ios/go_ios_exec.go @@ -13,9 +13,11 @@ // binary. // // This script requires that three environment variables be set: -// GOIOS_DEV_ID: The codesigning developer id or certificate identifier -// GOIOS_APP_ID: The provisioning app id prefix. Must support wildcard app ids. -// GOIOS_TEAM_ID: The team id that owns the app id prefix. +// +// GOIOS_DEV_ID: The codesigning developer id or certificate identifier +// GOIOS_APP_ID: The provisioning app id prefix. Must support wildcard app ids. +// GOIOS_TEAM_ID: The team id that owns the app id prefix. +// // $GOROOT/misc/ios contains a script, detect.go, that attempts to autodetect these. package main diff --git a/src/archive/tar/common.go b/src/archive/tar/common.go index c99b5c1920..f6d701d925 100644 --- a/src/archive/tar/common.go +++ b/src/archive/tar/common.go @@ -221,9 +221,11 @@ func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length } // that the file has no data in it, which is rather odd. // // As an example, if the underlying raw file contains the 10-byte data: +// // var compactFile = "abcdefgh" // // And the sparse map has the following entries: +// // var spd sparseDatas = []sparseEntry{ // {Offset: 2, Length: 5}, // Data fragment for 2..6 // {Offset: 18, Length: 3}, // Data fragment for 18..20 @@ -235,6 +237,7 @@ func (s sparseEntry) endOffset() int64 { return s.Offset + s.Length } // } // // Then the content of the resulting sparse file with a Header.Size of 25 is: +// // var sparseFile = "\x00"*2 + "abcde" + "\x00"*11 + "fgh" + "\x00"*4 type ( sparseDatas []sparseEntry @@ -293,9 +296,9 @@ func alignSparseEntries(src []sparseEntry, size int64) []sparseEntry { // The input must have been already validated. // // This function mutates src and returns a normalized map where: -// * adjacent fragments are coalesced together -// * only the last fragment may be empty -// * the endOffset of the last fragment is the total size +// - adjacent fragments are coalesced together +// - only the last fragment may be empty +// - the endOffset of the last fragment is the total size func invertSparseEntries(src []sparseEntry, size int64) []sparseEntry { dst := src[:0] var pre sparseEntry diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go index 4b11909bc9..f1b35c34f6 100644 --- a/src/archive/tar/reader.go +++ b/src/archive/tar/reader.go @@ -336,9 +336,9 @@ func parsePAX(r io.Reader) (map[string]string, error) { // header in case further processing is required. // // The err will be set to io.EOF only when one of the following occurs: -// * Exactly 0 bytes are read and EOF is hit. -// * Exactly 1 block of zeros is read and EOF is hit. -// * At least 2 blocks of zeros are read. +// - Exactly 0 bytes are read and EOF is hit. +// - Exactly 1 block of zeros is read and EOF is hit. +// - At least 2 blocks of zeros are read. func (tr *Reader) readHeader() (*Header, *block, error) { // Two blocks of zero bytes marks the end of the archive. if _, err := io.ReadFull(tr.r, tr.blk[:]); err != nil { diff --git a/src/archive/tar/strconv.go b/src/archive/tar/strconv.go index 275db6f026..ac3196370e 100644 --- a/src/archive/tar/strconv.go +++ b/src/archive/tar/strconv.go @@ -306,6 +306,7 @@ func formatPAXRecord(k, v string) (string, error) { // validPAXRecord reports whether the key-value pair is valid where each // record is formatted as: +// // "%d %s=%s\n" % (size, key, value) // // Keys and values should be UTF-8, but the number of bad writers out there diff --git a/src/archive/zip/reader.go b/src/archive/zip/reader.go index 92fd6f6a92..b4f6a8d714 100644 --- a/src/archive/zip/reader.go +++ b/src/archive/zip/reader.go @@ -229,6 +229,9 @@ func (r *checksumReader) Read(b []byte) (n int, err error) { n, err = r.rc.Read(b) r.hash.Write(b[:n]) r.nread += uint64(n) + if r.nread > r.f.UncompressedSize64 { + return 0, ErrFormat + } if err == nil { return } diff --git a/src/archive/zip/reader_test.go b/src/archive/zip/reader_test.go index 9bc23642c0..fd0a171304 100644 --- a/src/archive/zip/reader_test.go +++ b/src/archive/zip/reader_test.go @@ -1407,3 +1407,30 @@ func TestCVE202141772(t *testing.T) { t.Errorf("Inconsistent name in info entry: %v", name) } } + +func TestUnderSize(t *testing.T) { + z, err := OpenReader("testdata/readme.zip") + if err != nil { + t.Fatal(err) + } + defer z.Close() + + for _, f := range z.File { + f.UncompressedSize64 = 1 + } + + for _, f := range z.File { + t.Run(f.Name, func(t *testing.T) { + rd, err := f.Open() + if err != nil { + t.Fatal(err) + } + defer rd.Close() + + _, err = io.Copy(io.Discard, rd) + if err != ErrFormat { + t.Fatalf("Error mismatch\n\tGot: %v\n\tWant: %v", err, ErrFormat) + } + }) + } +} diff --git a/src/builtin/builtin.go b/src/builtin/builtin.go index 8997902f8f..e3e4df9de6 100644 --- a/src/builtin/builtin.go +++ b/src/builtin/builtin.go @@ -137,9 +137,12 @@ type ComplexType complex64 // new elements. If it does not, a new underlying array will be allocated. // Append returns the updated slice. It is therefore necessary to store the // result of append, often in the variable holding the slice itself: +// // slice = append(slice, elem1, elem2) // slice = append(slice, anotherSlice...) +// // As a special case, it is legal to append a string to a byte slice, like this: +// // slice = append([]byte("hello "), "world"...) func append(slice []Type, elems ...Type) []Type @@ -156,24 +159,28 @@ func copy(dst, src []Type) int func delete(m map[Type]Type1, key Type) // The len built-in function returns the length of v, according to its type: +// // Array: the number of elements in v. // Pointer to array: the number of elements in *v (even if v is nil). // Slice, or map: the number of elements in v; if v is nil, len(v) is zero. // String: the number of bytes in v. // Channel: the number of elements queued (unread) in the channel buffer; // if v is nil, len(v) is zero. +// // For some arguments, such as a string literal or a simple array expression, the // result can be a constant. See the Go language specification's "Length and // capacity" section for details. func len(v Type) int // The cap built-in function returns the capacity of v, according to its type: +// // Array: the number of elements in v (same as len(v)). // Pointer to array: the number of elements in *v (same as len(v)). // Slice: the maximum length the slice can reach when resliced; // if v is nil, cap(v) is zero. // Channel: the channel buffer capacity, in units of elements; // if v is nil, cap(v) is zero. +// // For some arguments, such as a simple array expression, the result can be a // constant. See the Go language specification's "Length and capacity" section for // details. @@ -184,6 +191,7 @@ func cap(v Type) int // value. Unlike new, make's return type is the same as the type of its // argument, not a pointer to it. The specification of the result depends on // the type: +// // Slice: The size specifies the length. The capacity of the slice is // equal to its length. A second integer argument may be provided to // specify a different capacity; it must be no smaller than the @@ -225,7 +233,9 @@ func imag(c ComplexType) FloatType // the last sent value is received. After the last value has been received // from a closed channel c, any receive from c will succeed without // blocking, returning the zero value for the channel element. The form +// // x, ok := <-c +// // will also set ok to false for a closed channel. func close(c chan<- Type) diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index e3dab4d035..659a82bcc8 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -30,7 +30,7 @@ func Compare(a, b []byte) int { // explode splits s into a slice of UTF-8 sequences, one per Unicode code point (still slices of bytes), // up to a maximum of n byte slices. Invalid UTF-8 sequences are chopped into individual bytes. func explode(s []byte, n int) [][]byte { - if n <= 0 { + if n <= 0 || n > len(s) { n = len(s) } a := make([][]byte, n) @@ -348,6 +348,9 @@ func genSplit(s, sep []byte, sepSave, n int) [][]byte { if n < 0 { n = Count(s, sep) + 1 } + if n > len(s)+1 { + n = len(s) + 1 + } a := make([][]byte, n) n-- @@ -369,9 +372,10 @@ func genSplit(s, sep []byte, sepSave, n int) [][]byte { // the subslices between those separators. // If sep is empty, SplitN splits after each UTF-8 sequence. // The count determines the number of subslices to return: -// n > 0: at most n subslices; the last subslice will be the unsplit remainder. -// n == 0: the result is nil (zero subslices) -// n < 0: all subslices +// +// n > 0: at most n subslices; the last subslice will be the unsplit remainder. +// n == 0: the result is nil (zero subslices) +// n < 0: all subslices // // To split around the first instance of a separator, see Cut. func SplitN(s, sep []byte, n int) [][]byte { return genSplit(s, sep, 0, n) } @@ -380,9 +384,10 @@ func SplitN(s, sep []byte, n int) [][]byte { return genSplit(s, sep, 0, n) } // returns a slice of those subslices. // If sep is empty, SplitAfterN splits after each UTF-8 sequence. // The count determines the number of subslices to return: -// n > 0: at most n subslices; the last subslice will be the unsplit remainder. -// n == 0: the result is nil (zero subslices) -// n < 0: all subslices +// +// n > 0: at most n subslices; the last subslice will be the unsplit remainder. +// n == 0: the result is nil (zero subslices) +// n < 0: all subslices func SplitAfterN(s, sep []byte, n int) [][]byte { return genSplit(s, sep, len(sep), n) } @@ -1139,7 +1144,7 @@ func ReplaceAll(s, old, new []byte) []byte { } // EqualFold reports whether s and t, interpreted as UTF-8 strings, -// are equal under Unicode case-folding, which is a more general +// are equal under simple Unicode case-folding, which is a more general // form of case-insensitivity. func EqualFold(s, t []byte) bool { for len(s) != 0 && len(t) != 0 { diff --git a/src/bytes/bytes_test.go b/src/bytes/bytes_test.go index 2e6ab31540..b702efb239 100644 --- a/src/bytes/bytes_test.go +++ b/src/bytes/bytes_test.go @@ -8,6 +8,7 @@ import ( . "bytes" "fmt" "internal/testenv" + "math" "math/rand" "reflect" "strings" @@ -723,6 +724,7 @@ var splittests = []SplitTest{ {"1 2", " ", 3, []string{"1", "2"}}, {"123", "", 2, []string{"1", "23"}}, {"123", "", 17, []string{"1", "2", "3"}}, + {"bT", "T", math.MaxInt / 4, []string{"b", ""}}, } func TestSplit(t *testing.T) { diff --git a/src/cmd/addr2line/main.go b/src/cmd/addr2line/main.go index 018802940b..6e005a8fac 100644 --- a/src/cmd/addr2line/main.go +++ b/src/cmd/addr2line/main.go @@ -6,6 +6,7 @@ // just enough to support pprof. // // Usage: +// // go tool addr2line binary // // Addr2line reads hexadecimal addresses, one per line and with optional 0x prefix, diff --git a/src/cmd/asm/doc.go b/src/cmd/asm/doc.go index 4a0c785aad..098f063909 100644 --- a/src/cmd/asm/doc.go +++ b/src/cmd/asm/doc.go @@ -3,11 +3,11 @@ // license that can be found in the LICENSE file. /* -Asm, typically invoked as ``go tool asm'', assembles the source file into an object +Asm, typically invoked as “go tool asm”, assembles the source file into an object file named for the basename of the argument source file with a .o suffix. The object file can then be combined with other objects into a package archive. -Command Line +# Command Line Usage: diff --git a/src/cmd/asm/internal/asm/parse.go b/src/cmd/asm/internal/asm/parse.go index 59aedbf0cc..acd03e1399 100644 --- a/src/cmd/asm/internal/asm/parse.go +++ b/src/cmd/asm/internal/asm/parse.go @@ -162,7 +162,7 @@ func (p *Parser) nextToken() lex.ScanToken { // line consumes a single assembly line from p.lex of the form // -// {label:} WORD[.cond] [ arg {, arg} ] (';' | '\n') +// {label:} WORD[.cond] [ arg {, arg} ] (';' | '\n') // // It adds any labels to p.pendingLabels and returns the word, cond, // operand list, and true. If there is an error or EOF, it returns @@ -891,7 +891,7 @@ func (p *Parser) symRefAttrs(name string, issueError bool) (bool, obj.ABI) { // constrained form of the operand syntax that's always SB-based, // non-static, and has at most a simple integer offset: // -// [$|*]sym[][+Int](SB) +// [$|*]sym[][+Int](SB) func (p *Parser) funcAddress() (string, obj.ABI, bool) { switch p.peek() { case '$', '*': @@ -1041,9 +1041,13 @@ func (p *Parser) registerIndirect(a *obj.Addr, prefix rune) { // // For 386/AMD64 register list specifies 4VNNIW-style multi-source operand. // For range of 4 elements, Intel manual uses "+3" notation, for example: +// // VP4DPWSSDS zmm1{k1}{z}, zmm2+3, m128 +// // Given asm line: +// // VP4DPWSSDS Z5, [Z10-Z13], (AX) +// // zmm2 is Z10, and Z13 is the only valid value for it (Z10+3). // Only simple ranges are accepted, like [Z0-Z3]. // diff --git a/src/cmd/asm/internal/lex/tokenizer.go b/src/cmd/asm/internal/lex/tokenizer.go index 861a2d421d..4db88e20c3 100644 --- a/src/cmd/asm/internal/lex/tokenizer.go +++ b/src/cmd/asm/internal/lex/tokenizer.go @@ -109,7 +109,7 @@ func (t *Tokenizer) Next() ScanToken { } text := s.TokenText() t.line += strings.Count(text, "\n") - // TODO: Use constraint.IsGoBuild once it exists. + // TODO: Use constraint.IsGoBuild once #44505 fixed. if strings.HasPrefix(text, "//go:build") { t.tok = BuildComment break diff --git a/src/cmd/buildid/doc.go b/src/cmd/buildid/doc.go index d1ec155c97..a554d798c0 100644 --- a/src/cmd/buildid/doc.go +++ b/src/cmd/buildid/doc.go @@ -6,6 +6,7 @@ Buildid displays or updates the build ID stored in a Go package or binary. Usage: + go tool buildid [-w] file By default, buildid prints the build ID found in the named file. diff --git a/src/cmd/cgo/doc.go b/src/cmd/cgo/doc.go index a6787f6405..4c62c5d70e 100644 --- a/src/cmd/cgo/doc.go +++ b/src/cmd/cgo/doc.go @@ -3,10 +3,9 @@ // license that can be found in the LICENSE file. /* - Cgo enables the creation of Go packages that call C code. -Using cgo with the go command +# Using cgo with the go command To use cgo write normal Go code that imports a pseudo-package "C". The Go code can then refer to types such as C.size_t, variables such @@ -91,11 +90,11 @@ file. This allows pre-compiled static libraries to be included in the package directory and linked properly. For example if package foo is in the directory /go/src/foo: - // #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo + // #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo Will be expanded to: - // #cgo LDFLAGS: -L/go/src/foo/libs -lfoo + // #cgo LDFLAGS: -L/go/src/foo/libs -lfoo When the Go tool sees that one or more Go files use the special import "C", it will look for other non-Go files in the directory and compile @@ -139,7 +138,7 @@ or you can set the CC environment variable any time you run the go tool. The CXX_FOR_TARGET, CXX_FOR_${GOOS}_${GOARCH}, and CXX environment variables work in a similar way for C++ code. -Go references to C +# Go references to C Within the Go file, C's struct field names that are keywords in Go can be accessed by prefixing them with an underscore: if x points at a C @@ -291,7 +290,7 @@ the helper function crashes the program, like when Go itself runs out of memory. Because C.malloc cannot fail, it has no two-result form that returns errno. -C references to Go +# C references to Go Go functions can be exported for use by C code in the following way: @@ -327,7 +326,7 @@ definitions and declarations, then the two output files will produce duplicate symbols and the linker will fail. To avoid this, definitions must be placed in preambles in other files, or in C source files. -Passing pointers +# Passing pointers Go is a garbage collected language, and the garbage collector needs to know the location of every pointer to Go memory. Because of this, @@ -398,7 +397,7 @@ passing uninitialized C memory to Go code if the Go code is going to store pointer values in it. Zero out the memory in C before passing it to Go. -Special cases +# Special cases A few special C types which would normally be represented by a pointer type in Go are instead represented by a uintptr. Those include: @@ -449,9 +448,10 @@ to auto-update code from Go 1.14 and earlier: go tool fix -r eglconf -Using cgo directly +# Using cgo directly Usage: + go tool cgo [cgo options] [-- compiler options] gofiles... Cgo transforms the specified input Go source files into several output diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 9877182fc4..a52163fd65 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -114,11 +114,11 @@ func (p *Package) addToFlag(flag string, args []string) { // // For example, the following string: // -// `a b:"c d" 'e''f' "g\""` +// `a b:"c d" 'e''f' "g\""` // // Would be parsed as: // -// []string{"a", "b:c d", "ef", `g"`} +// []string{"a", "b:c d", "ef", `g"`} func splitQuoted(s string) (r []string, err error) { var args []string arg := make([]rune, len(s)) @@ -1137,13 +1137,19 @@ func (p *Package) mangle(f *File, arg *ast.Expr, addPosition bool) (ast.Expr, bo // checkIndex checks whether arg has the form &a[i], possibly inside // type conversions. If so, then in the general case it writes -// _cgoIndexNN := a -// _cgoNN := &cgoIndexNN[i] // with type conversions, if any +// +// _cgoIndexNN := a +// _cgoNN := &cgoIndexNN[i] // with type conversions, if any +// // to sb, and writes -// _cgoCheckPointer(_cgoNN, _cgoIndexNN) +// +// _cgoCheckPointer(_cgoNN, _cgoIndexNN) +// // to sbCheck, and returns true. If a is a simple variable or field reference, // it writes -// _cgoIndexNN := &a +// +// _cgoIndexNN := &a +// // and dereferences the uses of _cgoIndexNN. Taking the address avoids // making a copy of an array. // @@ -1191,10 +1197,14 @@ func (p *Package) checkIndex(sb, sbCheck *bytes.Buffer, arg ast.Expr, i int) boo // checkAddr checks whether arg has the form &x, possibly inside type // conversions. If so, it writes -// _cgoBaseNN := &x -// _cgoNN := _cgoBaseNN // with type conversions, if any +// +// _cgoBaseNN := &x +// _cgoNN := _cgoBaseNN // with type conversions, if any +// // to sb, and writes -// _cgoCheckPointer(_cgoBaseNN, true) +// +// _cgoCheckPointer(_cgoBaseNN, true) +// // to sbCheck, and returns true. This tells _cgoCheckPointer to check // just the contents of the pointer being passed, not any other part // of the memory allocation. This is run after checkIndex, which looks diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 8ead173e64..adbb761e38 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -1399,6 +1399,19 @@ func (p *Package) cgoType(e ast.Expr) *Type { case *ast.ChanType: return &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("GoChan")} case *ast.Ident: + goTypesFixup := func(r *Type) *Type { + if r.Size == 0 { // int or uint + rr := new(Type) + *rr = *r + rr.Size = p.IntSize + rr.Align = p.IntSize + r = rr + } + if r.Align > p.PtrSize { + r.Align = p.PtrSize + } + return r + } // Look up the type in the top level declarations. // TODO: Handle types defined within a function. for _, d := range p.Decl { @@ -1417,6 +1430,17 @@ func (p *Package) cgoType(e ast.Expr) *Type { } } if def := typedef[t.Name]; def != nil { + if defgo, ok := def.Go.(*ast.Ident); ok { + switch defgo.Name { + case "complex64", "complex128": + // MSVC does not support the _Complex keyword + // nor the complex macro. + // Use GoComplex64 and GoComplex128 instead, + // which are typedef-ed to a compatible type. + // See go.dev/issues/36233. + return goTypesFixup(goTypes[defgo.Name]) + } + } return def } if t.Name == "uintptr" { @@ -1430,17 +1454,7 @@ func (p *Package) cgoType(e ast.Expr) *Type { return &Type{Size: 2 * p.PtrSize, Align: p.PtrSize, C: c("GoInterface")} } if r, ok := goTypes[t.Name]; ok { - if r.Size == 0 { // int or uint - rr := new(Type) - *rr = *r - rr.Size = p.IntSize - rr.Align = p.IntSize - r = rr - } - if r.Align > p.PtrSize { - r.Align = p.PtrSize - } - return r + return goTypesFixup(r) } error_(e.Pos(), "unrecognized Go type %s", t.Name) return &Type{Size: 4, Align: 4, C: c("int")} @@ -1895,8 +1909,14 @@ typedef GoUintGOINTBITS GoUint; typedef size_t GoUintptr; typedef float GoFloat32; typedef double GoFloat64; +#ifdef _MSC_VER +#include +typedef _Fcomplex GoComplex64; +typedef _Dcomplex GoComplex128; +#else typedef float _Complex GoComplex64; typedef double _Complex GoComplex128; +#endif /* static assertion to make sure the file is being used on architecture diff --git a/src/cmd/compile/internal/abi/abiutils.go b/src/cmd/compile/internal/abi/abiutils.go index 07ece87c41..aa5063f741 100644 --- a/src/cmd/compile/internal/abi/abiutils.go +++ b/src/cmd/compile/internal/abi/abiutils.go @@ -258,7 +258,7 @@ type RegAmounts struct { // by the ABI rules for parameter passing and result returning. type ABIConfig struct { // Do we need anything more than this? - offsetForLocals int64 // e.g., obj.(*Link).FixedFrameSize() -- extra linkage information on some architectures. + offsetForLocals int64 // e.g., obj.(*Link).Arch.FixedFrameSize -- extra linkage information on some architectures. regAmounts RegAmounts regsForTypeCache map[*types.Type]int } diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index d34fdc611b..c9667bd04a 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -111,7 +111,9 @@ func moveByType(t *types.Type) obj.As { } // opregreg emits instructions for -// dest := dest(To) op src(From) +// +// dest := dest(To) op src(From) +// // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). func opregreg(s *ssagen.State, op obj.As, dest, src int16) *obj.Prog { @@ -280,8 +282,15 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Reg = v.Reg() p.SetFrom3Reg(v.Args[1].Reg()) + case ssa.OpAMD64SARXL, ssa.OpAMD64SARXQ, + ssa.OpAMD64SHLXL, ssa.OpAMD64SHLXQ, + ssa.OpAMD64SHRXL, ssa.OpAMD64SHRXQ: + p := opregreg(s, v.Op.Asm(), v.Reg(), v.Args[1].Reg()) + p.SetFrom3Reg(v.Args[0].Reg()) + case ssa.OpAMD64SHLXLload, ssa.OpAMD64SHLXQload, - ssa.OpAMD64SHRXLload, ssa.OpAMD64SHRXQload: + ssa.OpAMD64SHRXLload, ssa.OpAMD64SHRXQload, + ssa.OpAMD64SARXLload, ssa.OpAMD64SARXQload: p := opregreg(s, v.Op.Asm(), v.Reg(), v.Args[1].Reg()) m := obj.Addr{Type: obj.TYPE_MEM, Reg: v.Args[0].Reg()} ssagen.AddAux(&m, v) @@ -289,8 +298,10 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { case ssa.OpAMD64SHLXLloadidx1, ssa.OpAMD64SHLXLloadidx4, ssa.OpAMD64SHLXLloadidx8, ssa.OpAMD64SHRXLloadidx1, ssa.OpAMD64SHRXLloadidx4, ssa.OpAMD64SHRXLloadidx8, + ssa.OpAMD64SARXLloadidx1, ssa.OpAMD64SARXLloadidx4, ssa.OpAMD64SARXLloadidx8, ssa.OpAMD64SHLXQloadidx1, ssa.OpAMD64SHLXQloadidx8, - ssa.OpAMD64SHRXQloadidx1, ssa.OpAMD64SHRXQloadidx8: + ssa.OpAMD64SHRXQloadidx1, ssa.OpAMD64SHRXQloadidx8, + ssa.OpAMD64SARXQloadidx1, ssa.OpAMD64SARXQloadidx8: p := opregreg(s, v.Op.Asm(), v.Reg(), v.Args[2].Reg()) m := obj.Addr{Type: obj.TYPE_MEM} memIdx(&m, v) @@ -786,7 +797,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() case ssa.OpAMD64MOVBloadidx1, ssa.OpAMD64MOVWloadidx1, ssa.OpAMD64MOVLloadidx1, ssa.OpAMD64MOVQloadidx1, ssa.OpAMD64MOVSSloadidx1, ssa.OpAMD64MOVSDloadidx1, - ssa.OpAMD64MOVQloadidx8, ssa.OpAMD64MOVSDloadidx8, ssa.OpAMD64MOVLloadidx8, ssa.OpAMD64MOVLloadidx4, ssa.OpAMD64MOVSSloadidx4, ssa.OpAMD64MOVWloadidx2: + ssa.OpAMD64MOVQloadidx8, ssa.OpAMD64MOVSDloadidx8, ssa.OpAMD64MOVLloadidx8, ssa.OpAMD64MOVLloadidx4, ssa.OpAMD64MOVSSloadidx4, ssa.OpAMD64MOVWloadidx2, + ssa.OpAMD64MOVBELloadidx1, ssa.OpAMD64MOVBELloadidx4, ssa.OpAMD64MOVBELloadidx8, ssa.OpAMD64MOVBEQloadidx1, ssa.OpAMD64MOVBEQloadidx8: p := s.Prog(v.Op.Asm()) memIdx(&p.From, v) ssagen.AddAux(&p.From, v) @@ -808,7 +820,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { ssa.OpAMD64SUBLmodifyidx1, ssa.OpAMD64SUBLmodifyidx4, ssa.OpAMD64SUBLmodifyidx8, ssa.OpAMD64SUBQmodifyidx1, ssa.OpAMD64SUBQmodifyidx8, ssa.OpAMD64ANDLmodifyidx1, ssa.OpAMD64ANDLmodifyidx4, ssa.OpAMD64ANDLmodifyidx8, ssa.OpAMD64ANDQmodifyidx1, ssa.OpAMD64ANDQmodifyidx8, ssa.OpAMD64ORLmodifyidx1, ssa.OpAMD64ORLmodifyidx4, ssa.OpAMD64ORLmodifyidx8, ssa.OpAMD64ORQmodifyidx1, ssa.OpAMD64ORQmodifyidx8, - ssa.OpAMD64XORLmodifyidx1, ssa.OpAMD64XORLmodifyidx4, ssa.OpAMD64XORLmodifyidx8, ssa.OpAMD64XORQmodifyidx1, ssa.OpAMD64XORQmodifyidx8: + ssa.OpAMD64XORLmodifyidx1, ssa.OpAMD64XORLmodifyidx4, ssa.OpAMD64XORLmodifyidx8, ssa.OpAMD64XORQmodifyidx1, ssa.OpAMD64XORQmodifyidx8, + ssa.OpAMD64MOVBEWstoreidx1, ssa.OpAMD64MOVBEWstoreidx2, ssa.OpAMD64MOVBELstoreidx1, ssa.OpAMD64MOVBELstoreidx4, ssa.OpAMD64MOVBELstoreidx8, ssa.OpAMD64MOVBEQstoreidx1, ssa.OpAMD64MOVBEQstoreidx8: p := s.Prog(v.Op.Asm()) p.From.Type = obj.TYPE_REG p.From.Reg = v.Args[2].Reg() @@ -1087,7 +1100,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { } p := s.Prog(mov) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() // 0 on amd64, just to be consistent with other architectures + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize // 0 on amd64, just to be consistent with other architectures p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -1387,6 +1400,16 @@ func ssaGenBlock(s *ssagen.State, b, next *ssa.Block) { } } + case ssa.BlockAMD64JUMPTABLE: + // JMP *(TABLE)(INDEX*8) + p := s.Prog(obj.AJMP) + p.To.Type = obj.TYPE_MEM + p.To.Reg = b.Controls[1].Reg() + p.To.Index = b.Controls[0].Reg() + p.To.Scale = 8 + // Save jump tables for later resolution of the target blocks. + s.JumpTables = append(s.JumpTables, b) + default: b.Fatalf("branch not implemented: %s", b.LongString()) } diff --git a/src/cmd/compile/internal/amd64/versions_test.go b/src/cmd/compile/internal/amd64/versions_test.go index e28aefbd3c..1ef06f7e58 100644 --- a/src/cmd/compile/internal/amd64/versions_test.go +++ b/src/cmd/compile/internal/amd64/versions_test.go @@ -241,6 +241,7 @@ var featureToOpcodes = map[string][]string{ // 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"}, + "bmi2": {"sarxq", "sarxl", "sarx", "shlxq", "shlxl", "shlx", "shrxq", "shrxl", "shrx"}, "sse41": {"roundsd"}, "fma": {"vfmadd231sd"}, "movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"}, diff --git a/src/cmd/compile/internal/arm/ssa.go b/src/cmd/compile/internal/arm/ssa.go index 063fb65b33..a53f51bd13 100644 --- a/src/cmd/compile/internal/arm/ssa.go +++ b/src/cmd/compile/internal/arm/ssa.go @@ -854,7 +854,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(arm.AMOVW) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() diff --git a/src/cmd/compile/internal/arm64/ssa.go b/src/cmd/compile/internal/arm64/ssa.go index 48eb2190b2..3b6e6f6723 100644 --- a/src/cmd/compile/internal/arm64/ssa.go +++ b/src/cmd/compile/internal/arm64/ssa.go @@ -171,7 +171,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { for _, a := range v.Block.Func.RegArgs { // Pass the spill/unspill information along to the assembler, offset by size of // the saved LR slot. - addr := ssagen.SpillSlotAddr(a, arm64.REGSP, base.Ctxt.FixedFrameSize()) + addr := ssagen.SpillSlotAddr(a, arm64.REGSP, base.Ctxt.Arch.FixedFrameSize) s.FuncInfo().AddSpill( obj.RegSpill{Reg: a.Reg, Addr: addr, Unspill: loadByType(a.Type), Spill: storeByType(a.Type)}) } @@ -1128,7 +1128,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(arm64.AMOVD) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() diff --git a/src/cmd/compile/internal/deadcode/deadcode.go b/src/cmd/compile/internal/deadcode/deadcode.go index c37a5a6990..decd261183 100644 --- a/src/cmd/compile/internal/deadcode/deadcode.go +++ b/src/cmd/compile/internal/deadcode/deadcode.go @@ -6,6 +6,7 @@ package deadcode import ( "go/constant" + "go/token" "cmd/compile/internal/base" "cmd/compile/internal/ir" @@ -86,6 +87,85 @@ func stmts(nn *ir.Nodes) { } } } + if n.Op() == ir.OSWITCH { + n := n.(*ir.SwitchStmt) + // Use a closure wrapper here so we can use "return" to abort the analysis. + func() { + if n.Tag != nil && n.Tag.Op() == ir.OTYPESW { + return // no special type-switch case yet. + } + var x constant.Value // value we're switching on + if n.Tag != nil { + if ir.ConstType(n.Tag) == constant.Unknown { + return + } + x = n.Tag.Val() + } else { + x = constant.MakeBool(true) // switch { ... } => switch true { ... } + } + var def *ir.CaseClause + for _, cas := range n.Cases { + if len(cas.List) == 0 { // default case + def = cas + continue + } + for _, c := range cas.List { + if ir.ConstType(c) == constant.Unknown { + return // can't statically tell if it matches or not - give up. + } + if constant.Compare(x, token.EQL, c.Val()) { + for _, n := range cas.Body { + if n.Op() == ir.OFALL { + return // fallthrough makes it complicated - abort. + } + } + // This switch entry is the one that always triggers. + for _, cas2 := range n.Cases { + for _, c2 := range cas2.List { + if cas2 != cas || c2 != c { + ir.Visit(c2, markHiddenClosureDead) + } + } + if cas2 != cas { + ir.VisitList(cas2.Body, markHiddenClosureDead) + } + } + + cas.List[0] = c + cas.List = cas.List[:1] + n.Cases[0] = cas + n.Cases = n.Cases[:1] + return + } + } + } + if def != nil { + for _, n := range def.Body { + if n.Op() == ir.OFALL { + return // fallthrough makes it complicated - abort. + } + } + for _, cas := range n.Cases { + if cas != def { + ir.VisitList(cas.List, markHiddenClosureDead) + ir.VisitList(cas.Body, markHiddenClosureDead) + } + } + n.Cases[0] = def + n.Cases = n.Cases[:1] + return + } + + // TODO: handle case bodies ending with panic/return as we do in the IF case above. + + // entire switch is a nop - no case ever triggers + for _, cas := range n.Cases { + ir.VisitList(cas.List, markHiddenClosureDead) + ir.VisitList(cas.Body, markHiddenClosureDead) + } + n.Cases = n.Cases[:0] + }() + } if len(n.Init()) != 0 { stmts(n.(ir.InitNode).PtrInit()) diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go index e249a52e57..f84368ece3 100644 --- a/src/cmd/compile/internal/dwarfgen/dwarf.go +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -339,7 +339,7 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var { localAutoOffset := func() int64 { offs = n.FrameOffset() - if base.Ctxt.FixedFrameSize() == 0 { + if base.Ctxt.Arch.FixedFrameSize == 0 { offs -= int64(types.PtrSize) } if buildcfg.FramePointerEnabled { @@ -357,7 +357,7 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var { if n.IsOutputParamInRegisters() { offs = localAutoOffset() } else { - offs = n.FrameOffset() + base.Ctxt.FixedFrameSize() + offs = n.FrameOffset() + base.Ctxt.Arch.FixedFrameSize } default: @@ -547,7 +547,7 @@ func RecordFlags(flags ...string) { fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get()) } - // Adds flag to producer string singalling whether regabi is turned on or + // Adds flag to producer string signaling whether regabi is turned on or // off. // Once regabi is turned on across the board and the relative GOEXPERIMENT // knobs no longer exist this code should be removed. diff --git a/src/cmd/compile/internal/escape/desugar.go b/src/cmd/compile/internal/escape/desugar.go index 8b3cc25cf9..6c21981aca 100644 --- a/src/cmd/compile/internal/escape/desugar.go +++ b/src/cmd/compile/internal/escape/desugar.go @@ -24,9 +24,9 @@ func fixRecoverCall(call *ir.CallExpr) { pos := call.Pos() - // FP is equal to caller's SP plus FixedFrameSize(). + // FP is equal to caller's SP plus FixedFrameSize. var fp ir.Node = ir.NewCallExpr(pos, ir.OGETCALLERSP, nil, nil) - if off := base.Ctxt.FixedFrameSize(); off != 0 { + if off := base.Ctxt.Arch.FixedFrameSize; off != 0 { fp = ir.NewBinaryExpr(fp.Pos(), ir.OADD, fp, ir.NewInt(off)) } // TODO(mdempsky): Replace *int32 with unsafe.Pointer, without upsetting checkptr. diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go index 4713ecddca..4408a531ec 100644 --- a/src/cmd/compile/internal/escape/escape.go +++ b/src/cmd/compile/internal/escape/escape.go @@ -210,6 +210,9 @@ func (b *batch) walkFunc(fn *ir.Func) { switch n.Op() { case ir.OLABEL: n := n.(*ir.LabelStmt) + if n.Label.IsBlank() { + break + } if e.labels == nil { e.labels = make(map[*types.Sym]labelState) } diff --git a/src/cmd/compile/internal/escape/stmt.go b/src/cmd/compile/internal/escape/stmt.go index 0afb5d64ef..4e8dd904ff 100644 --- a/src/cmd/compile/internal/escape/stmt.go +++ b/src/cmd/compile/internal/escape/stmt.go @@ -50,6 +50,9 @@ func (e *escape) stmt(n ir.Node) { case ir.OLABEL: n := n.(*ir.LabelStmt) + if n.Label.IsBlank() { + break + } switch e.labels[n.Label] { case nonlooping: if base.Flag.LowerM > 2 { diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 74e4c0a890..fe8b6e9d45 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -271,6 +271,9 @@ func addGCLocals() { objw.Global(x, int32(len(x.P)), obj.RODATA|obj.DUPOK) x.Set(obj.AttrStatic, true) } + for _, jt := range fn.JumpTables { + objw.Global(jt.Sym, int32(len(jt.Targets)*base.Ctxt.Arch.PtrSize), obj.RODATA) + } } } diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index be01914d08..8c2ea49c8f 100644 --- a/src/cmd/compile/internal/inline/inl.go +++ b/src/cmd/compile/internal/inline/inl.go @@ -522,7 +522,8 @@ func InlineCalls(fn *ir.Func) { // but then you may as well do it here. so this is cleaner and // shorter and less complicated. // The result of inlnode MUST be assigned back to n, e.g. -// n.Left = inlnode(n.Left) +// +// n.Left = inlnode(n.Left) func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node { if n == nil { return n @@ -657,7 +658,8 @@ var NewInline = func(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.InlinedCa // inlined function body, and (List, Rlist) contain the (input, output) // parameters. // The result of mkinlcall MUST be assigned back to n, e.g. -// n.Left = mkinlcall(n.Left, fn, isddd) +// +// n.Left = mkinlcall(n.Left, fn, isddd) func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node { if fn.Inl == nil { if logopt.Enabled() { diff --git a/src/cmd/compile/internal/ir/const.go b/src/cmd/compile/internal/ir/const.go index eaa4d5b6b1..f0b66957f1 100644 --- a/src/cmd/compile/internal/ir/const.go +++ b/src/cmd/compile/internal/ir/const.go @@ -26,7 +26,7 @@ func NewString(s string) Node { } const ( - // Maximum size in bits for big.Ints before signalling + // Maximum size in bits for big.Ints before signaling // overflow and also mantissa precision for big.Floats. ConstPrec = 512 ) diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index b5c0983d6a..4f1f582fa1 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -951,11 +951,11 @@ var IsIntrinsicCall = func(*CallExpr) bool { return false } // instead of computing both. SameSafeExpr assumes that l and r are // used in the same statement or expression. In order for it to be // safe to reuse l or r, they must: -// * be the same expression -// * not have side-effects (no function calls, no channel ops); -// however, panics are ok -// * not cause inappropriate aliasing; e.g. two string to []byte -// conversions, must result in two distinct slices +// - be the same expression +// - not have side-effects (no function calls, no channel ops); +// however, panics are ok +// - not cause inappropriate aliasing; e.g. two string to []byte +// conversions, must result in two distinct slices // // The handling of OINDEXMAP is subtle. OINDEXMAP can occur both // as an lvalue (map assignment) and an rvalue (map access). This is diff --git a/src/cmd/compile/internal/ir/node.go b/src/cmd/compile/internal/ir/node.go index 5e5868abb2..9ccb8e3c30 100644 --- a/src/cmd/compile/internal/ir/node.go +++ b/src/cmd/compile/internal/ir/node.go @@ -310,6 +310,7 @@ const ( ORESULT // result of a function call; Xoffset is stack offset OINLMARK // start of an inlined body, with file/line of caller. Xoffset is an index into the inline tree. OLINKSYMOFFSET // offset within a name + OJUMPTABLE // A jump table structure for implementing dense expression switches // opcodes for generics ODYNAMICDOTTYPE // x = i.(T) where T is a type parameter (or derived from a type parameter) @@ -551,7 +552,8 @@ func SetPos(n Node) src.XPos { } // The result of InitExpr MUST be assigned back to n, e.g. -// n.X = InitExpr(init, n.X) +// +// n.X = InitExpr(init, n.X) func InitExpr(init []Node, expr Node) Node { if len(init) == 0 { return expr diff --git a/src/cmd/compile/internal/ir/node_gen.go b/src/cmd/compile/internal/ir/node_gen.go index 22ff885d68..8d6fc8c607 100644 --- a/src/cmd/compile/internal/ir/node_gen.go +++ b/src/cmd/compile/internal/ir/node_gen.go @@ -712,6 +712,28 @@ func (n *InstExpr) editChildren(edit func(Node) Node) { editNodes(n.Targs, edit) } +func (n *JumpTableStmt) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } +func (n *JumpTableStmt) copy() Node { + c := *n + c.init = copyNodes(c.init) + return &c +} +func (n *JumpTableStmt) doChildren(do func(Node) bool) bool { + if doNodes(n.init, do) { + return true + } + if n.Idx != nil && do(n.Idx) { + return true + } + return false +} +func (n *JumpTableStmt) editChildren(edit func(Node) Node) { + editNodes(n.init, edit) + if n.Idx != nil { + n.Idx = edit(n.Idx).(Node) + } +} + func (n *KeyExpr) Format(s fmt.State, verb rune) { fmtNode(n, s, verb) } func (n *KeyExpr) copy() Node { c := *n diff --git a/src/cmd/compile/internal/ir/op_string.go b/src/cmd/compile/internal/ir/op_string.go index 14eb84083a..8927f18cea 100644 --- a/src/cmd/compile/internal/ir/op_string.go +++ b/src/cmd/compile/internal/ir/op_string.go @@ -154,19 +154,20 @@ func _() { _ = x[ORESULT-143] _ = x[OINLMARK-144] _ = x[OLINKSYMOFFSET-145] - _ = x[ODYNAMICDOTTYPE-146] - _ = x[ODYNAMICDOTTYPE2-147] - _ = x[ODYNAMICTYPE-148] - _ = x[OTAILCALL-149] - _ = x[OGETG-150] - _ = x[OGETCALLERPC-151] - _ = x[OGETCALLERSP-152] - _ = x[OEND-153] + _ = x[OJUMPTABLE-146] + _ = x[ODYNAMICDOTTYPE-147] + _ = x[ODYNAMICDOTTYPE2-148] + _ = x[ODYNAMICTYPE-149] + _ = x[OTAILCALL-150] + _ = x[OGETG-151] + _ = x[OGETCALLERPC-152] + _ = x[OGETCALLERSP-153] + _ = x[OEND-154] } -const _Op_name = "XXXNAMENONAMETYPELITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESSLICE2ARRPTRASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCAPCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVIDATACONVNOPCOPYDCLDCLFUNCDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERRECOVERRECOVERFPRECVRUNESTRSELRECV2REALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFUNSAFEADDUNSAFESLICEMETHEXPRMETHVALUEBLOCKBREAKCASECONTINUEDEFERFALLFORFORUNTILGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWFUNCINSTTFUNCINLCALLEFACEITABIDATASPTRCFUNCCHECKNILVARDEFVARKILLVARLIVERESULTINLMARKLINKSYMOFFSETDYNAMICDOTTYPEDYNAMICDOTTYPE2DYNAMICTYPETAILCALLGETGGETCALLERPCGETCALLERSPEND" +const _Op_name = "XXXNAMENONAMETYPELITERALNILADDSUBORXORADDSTRADDRANDANDAPPENDBYTES2STRBYTES2STRTMPRUNES2STRSTR2BYTESSTR2BYTESTMPSTR2RUNESSLICE2ARRPTRASAS2AS2DOTTYPEAS2FUNCAS2MAPRAS2RECVASOPCALLCALLFUNCCALLMETHCALLINTERCAPCLOSECLOSURECOMPLITMAPLITSTRUCTLITARRAYLITSLICELITPTRLITCONVCONVIFACECONVIDATACONVNOPCOPYDCLDCLFUNCDCLCONSTDCLTYPEDELETEDOTDOTPTRDOTMETHDOTINTERXDOTDOTTYPEDOTTYPE2EQNELTLEGEGTDEREFINDEXINDEXMAPKEYSTRUCTKEYLENMAKEMAKECHANMAKEMAPMAKESLICEMAKESLICECOPYMULDIVMODLSHRSHANDANDNOTNEWNOTBITNOTPLUSNEGORORPANICPRINTPRINTNPARENSENDSLICESLICEARRSLICESTRSLICE3SLICE3ARRSLICEHEADERRECOVERRECOVERFPRECVRUNESTRSELRECV2REALIMAGCOMPLEXALIGNOFOFFSETOFSIZEOFUNSAFEADDUNSAFESLICEMETHEXPRMETHVALUEBLOCKBREAKCASECONTINUEDEFERFALLFORFORUNTILGOTOIFLABELGORANGERETURNSELECTSWITCHTYPESWFUNCINSTTFUNCINLCALLEFACEITABIDATASPTRCFUNCCHECKNILVARDEFVARKILLVARLIVERESULTINLMARKLINKSYMOFFSETJUMPTABLEDYNAMICDOTTYPEDYNAMICDOTTYPE2DYNAMICTYPETAILCALLGETGGETCALLERPCGETCALLERSPEND" -var _Op_index = [...]uint16{0, 3, 7, 13, 17, 24, 27, 30, 33, 35, 38, 44, 48, 54, 60, 69, 81, 90, 99, 111, 120, 132, 134, 137, 147, 154, 161, 168, 172, 176, 184, 192, 201, 204, 209, 216, 223, 229, 238, 246, 254, 260, 264, 273, 282, 289, 293, 296, 303, 311, 318, 324, 327, 333, 340, 348, 352, 359, 367, 369, 371, 373, 375, 377, 379, 384, 389, 397, 400, 409, 412, 416, 424, 431, 440, 453, 456, 459, 462, 465, 468, 471, 477, 480, 483, 489, 493, 496, 500, 505, 510, 516, 521, 525, 530, 538, 546, 552, 561, 572, 579, 588, 592, 599, 607, 611, 615, 622, 629, 637, 643, 652, 663, 671, 680, 685, 690, 694, 702, 707, 711, 714, 722, 726, 728, 733, 735, 740, 746, 752, 758, 764, 772, 777, 784, 789, 793, 798, 802, 807, 815, 821, 828, 835, 841, 848, 861, 875, 890, 901, 909, 913, 924, 935, 938} +var _Op_index = [...]uint16{0, 3, 7, 13, 17, 24, 27, 30, 33, 35, 38, 44, 48, 54, 60, 69, 81, 90, 99, 111, 120, 132, 134, 137, 147, 154, 161, 168, 172, 176, 184, 192, 201, 204, 209, 216, 223, 229, 238, 246, 254, 260, 264, 273, 282, 289, 293, 296, 303, 311, 318, 324, 327, 333, 340, 348, 352, 359, 367, 369, 371, 373, 375, 377, 379, 384, 389, 397, 400, 409, 412, 416, 424, 431, 440, 453, 456, 459, 462, 465, 468, 471, 477, 480, 483, 489, 493, 496, 500, 505, 510, 516, 521, 525, 530, 538, 546, 552, 561, 572, 579, 588, 592, 599, 607, 611, 615, 622, 629, 637, 643, 652, 663, 671, 680, 685, 690, 694, 702, 707, 711, 714, 722, 726, 728, 733, 735, 740, 746, 752, 758, 764, 772, 777, 784, 789, 793, 798, 802, 807, 815, 821, 828, 835, 841, 848, 861, 870, 884, 899, 910, 918, 922, 933, 944, 947} func (i Op) String() string { if i >= Op(len(_Op_index)-1) { diff --git a/src/cmd/compile/internal/ir/stmt.go b/src/cmd/compile/internal/ir/stmt.go index 80bd205436..0e76f17440 100644 --- a/src/cmd/compile/internal/ir/stmt.go +++ b/src/cmd/compile/internal/ir/stmt.go @@ -8,6 +8,7 @@ import ( "cmd/compile/internal/base" "cmd/compile/internal/types" "cmd/internal/src" + "go/constant" ) // A Decl is a declaration of a const, type, or var. (A declared func is a Func.) @@ -262,6 +263,37 @@ func NewIfStmt(pos src.XPos, cond Node, body, els []Node) *IfStmt { return n } +// A JumpTableStmt is used to implement switches. Its semantics are: +// tmp := jt.Idx +// if tmp == Cases[0] goto Targets[0] +// if tmp == Cases[1] goto Targets[1] +// ... +// if tmp == Cases[n] goto Targets[n] +// Note that a JumpTableStmt is more like a multiway-goto than +// a multiway-if. In particular, the case bodies are just +// labels to jump to, not not full Nodes lists. +type JumpTableStmt struct { + miniStmt + + // Value used to index the jump table. + // We support only integer types that + // are at most the size of a uintptr. + Idx Node + + // If Idx is equal to Cases[i], jump to Targets[i]. + // Cases entries must be distinct and in increasing order. + // The length of Cases and Targets must be equal. + Cases []constant.Value + Targets []*types.Sym +} + +func NewJumpTableStmt(pos src.XPos, idx Node) *JumpTableStmt { + n := &JumpTableStmt{Idx: idx} + n.pos = pos + n.op = OJUMPTABLE + return n +} + // An InlineMarkStmt is a marker placed just before an inlined body. type InlineMarkStmt struct { miniStmt diff --git a/src/cmd/compile/internal/liveness/plive.go b/src/cmd/compile/internal/liveness/plive.go index 3202e506c8..bd0a6fa1a3 100644 --- a/src/cmd/compile/internal/liveness/plive.go +++ b/src/cmd/compile/internal/liveness/plive.go @@ -244,8 +244,10 @@ func (lv *liveness) initcache() { // liveness effects on a variable. // // The possible flags are: +// // uevar - used by the instruction // varkill - killed by the instruction (set) +// // A kill happens after the use (for an instruction that updates a value, for example). type liveEffect int @@ -1460,14 +1462,14 @@ func (lv *liveness) emitStackObjects() *obj.LSym { // isfat reports whether a variable of type t needs multiple assignments to initialize. // For example: // -// type T struct { x, y int } -// x := T{x: 0, y: 1} +// type T struct { x, y int } +// x := T{x: 0, y: 1} // // Then we need: // -// var t T -// t.x = 0 -// t.y = 1 +// var t T +// t.x = 0 +// t.y = 1 // // to fully initialize t. func isfat(t *types.Type) bool { diff --git a/src/cmd/compile/internal/mips/ggen.go b/src/cmd/compile/internal/mips/ggen.go index 1a5125207d..a18440e7b3 100644 --- a/src/cmd/compile/internal/mips/ggen.go +++ b/src/cmd/compile/internal/mips/ggen.go @@ -20,7 +20,7 @@ func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog } if cnt < int64(4*types.PtrSize) { for i := int64(0); i < cnt; i += int64(types.PtrSize) { - p = pp.Append(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, base.Ctxt.FixedFrameSize()+off+i) + p = pp.Append(p, mips.AMOVW, obj.TYPE_REG, mips.REGZERO, 0, obj.TYPE_MEM, mips.REGSP, base.Ctxt.Arch.FixedFrameSize+off+i) } } else { //fmt.Printf("zerorange frame:%v, lo: %v, hi:%v \n", frame ,lo, hi) @@ -30,7 +30,7 @@ func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog // MOVW R0, (Widthptr)r1 // ADD $Widthptr, r1 // BNE r1, r2, loop - p = pp.Append(p, mips.AADD, obj.TYPE_CONST, 0, base.Ctxt.FixedFrameSize()+off-4, obj.TYPE_REG, mips.REGRT1, 0) + p = pp.Append(p, mips.AADD, obj.TYPE_CONST, 0, base.Ctxt.Arch.FixedFrameSize+off-4, obj.TYPE_REG, mips.REGRT1, 0) p.Reg = mips.REGSP p = pp.Append(p, mips.AADD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, mips.REGRT2, 0) p.Reg = mips.REGRT1 diff --git a/src/cmd/compile/internal/mips/ssa.go b/src/cmd/compile/internal/mips/ssa.go index 6326f966bf..0411756c8d 100644 --- a/src/cmd/compile/internal/mips/ssa.go +++ b/src/cmd/compile/internal/mips/ssa.go @@ -792,7 +792,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(mips.AMOVW) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() diff --git a/src/cmd/compile/internal/mips64/ssa.go b/src/cmd/compile/internal/mips64/ssa.go index 6e12c6cb94..f3e372c3bc 100644 --- a/src/cmd/compile/internal/mips64/ssa.go +++ b/src/cmd/compile/internal/mips64/ssa.go @@ -762,7 +762,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(mips.AMOVV) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() diff --git a/src/cmd/compile/internal/noder/expr.go b/src/cmd/compile/internal/noder/expr.go index 566abda963..e37e4cd661 100644 --- a/src/cmd/compile/internal/noder/expr.go +++ b/src/cmd/compile/internal/noder/expr.go @@ -6,6 +6,7 @@ package noder import ( "fmt" + "go/constant" "cmd/compile/internal/base" "cmd/compile/internal/ir" @@ -62,6 +63,14 @@ func (g *irgen) expr(expr syntax.Expr) ir.Node { case types2.UntypedNil: // ok; can appear in type switch case clauses // TODO(mdempsky): Handle as part of type switches instead? + case types2.UntypedInt, types2.UntypedFloat, types2.UntypedComplex: + // Untyped rhs of non-constant shift, e.g. x << 1.0. + // If we have a constant value, it must be an int >= 0. + if tv.Value != nil { + s := constant.ToInt(tv.Value) + assert(s.Kind() == constant.Int && constant.Sign(s) >= 0) + } + typ = types2.Typ[types2.Uint] case types2.UntypedBool: typ = types2.Typ[types2.Bool] // expression in "if" or "for" condition case types2.UntypedString: diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index 77ca642183..cc5610acda 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -33,30 +33,36 @@ func LoadPackage(filenames []string) { sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10) noders := make([]*noder, len(filenames)) - for i, filename := range filenames { + for i := range noders { p := noder{ err: make(chan syntax.Error), } noders[i] = &p - - filename := filename - go func() { - sem <- struct{}{} - defer func() { <-sem }() - defer close(p.err) - fbase := syntax.NewFileBase(filename) - - f, err := os.Open(filename) - if err != nil { - p.error(syntax.Error{Msg: err.Error()}) - return - } - defer f.Close() - - p.file, _ = syntax.Parse(fbase, f, p.error, p.pragma, mode) // errors are tracked via p.error - }() } + // Move the entire syntax processing logic into a separate goroutine to avoid blocking on the "sem". + go func() { + for i, filename := range filenames { + filename := filename + p := noders[i] + sem <- struct{}{} + go func() { + defer func() { <-sem }() + defer close(p.err) + fbase := syntax.NewFileBase(filename) + + f, err := os.Open(filename) + if err != nil { + p.error(syntax.Error{Msg: err.Error()}) + return + } + defer f.Close() + + p.file, _ = syntax.Parse(fbase, f, p.error, p.pragma, mode) // errors are tracked via p.error + }() + } + }() + var lines uint for _, p := range noders { for e := range p.err { diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index eeac8d8de7..41435a7afe 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -741,6 +741,7 @@ func (g *genInst) genericSubst(newsym *types.Sym, nameNode *ir.Name, tparams []* // Pos of the instantiated function is same as the generic function newf := ir.NewFunc(gf.Pos()) newf.Pragma = gf.Pragma // copy over pragmas from generic function to stenciled implementation. + newf.Endlineno = gf.Endlineno newf.Nname = ir.NewNameAt(gf.Pos(), newsym) newf.Nname.Func = newf newf.Nname.Defn = newf diff --git a/src/cmd/compile/internal/noder/unified.go b/src/cmd/compile/internal/noder/unified.go index 2c1f2362ad..e7a4001cec 100644 --- a/src/cmd/compile/internal/noder/unified.go +++ b/src/cmd/compile/internal/noder/unified.go @@ -33,38 +33,38 @@ var localPkgReader *pkgReader // // The pipeline contains 2 steps: // -// 1) Generate package export data "stub". +// 1. Generate package export data "stub". // -// 2) Generate package IR from package export data. +// 2. Generate package IR from package export data. // // The package data "stub" at step (1) contains everything from the local package, // but nothing that have been imported. When we're actually writing out export data // to the output files (see writeNewExport function), we run the "linker", which does // a few things: // -// + Updates compiler extensions data (e.g., inlining cost, escape analysis results). +// - Updates compiler extensions data (e.g., inlining cost, escape analysis results). // -// + Handles re-exporting any transitive dependencies. +// - Handles re-exporting any transitive dependencies. // -// + Prunes out any unnecessary details (e.g., non-inlineable functions, because any -// downstream importers only care about inlinable functions). +// - Prunes out any unnecessary details (e.g., non-inlineable functions, because any +// downstream importers only care about inlinable functions). // // The source files are typechecked twice, once before writing export data // using types2 checker, once after read export data using gc/typecheck. // This duplication of work will go away once we always use types2 checker, // we can remove the gc/typecheck pass. The reason it is still here: // -// + It reduces engineering costs in maintaining a fork of typecheck -// (e.g., no need to backport fixes like CL 327651). +// - It reduces engineering costs in maintaining a fork of typecheck +// (e.g., no need to backport fixes like CL 327651). // -// + It makes it easier to pass toolstash -cmp. +// - It makes it easier to pass toolstash -cmp. // -// + Historically, we would always re-run the typechecker after import, even though -// we know the imported data is valid. It's not ideal, but also not causing any -// problem either. +// - Historically, we would always re-run the typechecker after import, even though +// we know the imported data is valid. It's not ideal, but also not causing any +// problem either. // -// + There's still transformation that being done during gc/typecheck, like rewriting -// multi-valued function call, or transform ir.OINDEX -> ir.OINDEXMAP. +// - There's still transformation that being done during gc/typecheck, like rewriting +// multi-valued function call, or transform ir.OINDEX -> ir.OINDEXMAP. // // Using syntax+types2 tree, which already has a complete representation of generics, // the unified IR has the full typed AST for doing introspection during step (1). diff --git a/src/cmd/compile/internal/pkginit/init.go b/src/cmd/compile/internal/pkginit/init.go index 40f1408260..32e95bedc2 100644 --- a/src/cmd/compile/internal/pkginit/init.go +++ b/src/cmd/compile/internal/pkginit/init.go @@ -65,9 +65,9 @@ func MakeInit() { // Task makes and returns an initialization record for the package. // See runtime/proc.go:initTask for its layout. // The 3 tasks for initialization are: -// 1) Initialize all of the packages the current package depends on. -// 2) Initialize all the variables that have initializers. -// 3) Run any init functions. +// 1. Initialize all of the packages the current package depends on. +// 2. Initialize all the variables that have initializers. +// 3. Run any init functions. func Task() *ir.Name { var deps []*obj.LSym // initTask records for packages the current package depends on var fns []*obj.LSym // functions to call for package initialization diff --git a/src/cmd/compile/internal/ppc64/ggen.go b/src/cmd/compile/internal/ppc64/ggen.go index 7877be3336..4c935cfc71 100644 --- a/src/cmd/compile/internal/ppc64/ggen.go +++ b/src/cmd/compile/internal/ppc64/ggen.go @@ -19,17 +19,17 @@ func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog } if cnt < int64(4*types.PtrSize) { for i := int64(0); i < cnt; i += int64(types.PtrSize) { - p = pp.Append(p, ppc64.AMOVD, obj.TYPE_REG, ppc64.REGZERO, 0, obj.TYPE_MEM, ppc64.REGSP, base.Ctxt.FixedFrameSize()+off+i) + p = pp.Append(p, ppc64.AMOVD, obj.TYPE_REG, ppc64.REGZERO, 0, obj.TYPE_MEM, ppc64.REGSP, base.Ctxt.Arch.FixedFrameSize+off+i) } } else if cnt <= int64(128*types.PtrSize) { - p = pp.Append(p, ppc64.AADD, obj.TYPE_CONST, 0, base.Ctxt.FixedFrameSize()+off-8, obj.TYPE_REG, ppc64.REGRT1, 0) + p = pp.Append(p, ppc64.AADD, obj.TYPE_CONST, 0, base.Ctxt.Arch.FixedFrameSize+off-8, obj.TYPE_REG, ppc64.REGRT1, 0) p.Reg = ppc64.REGSP p = pp.Append(p, obj.ADUFFZERO, obj.TYPE_NONE, 0, 0, obj.TYPE_MEM, 0, 0) p.To.Name = obj.NAME_EXTERN p.To.Sym = ir.Syms.Duffzero p.To.Offset = 4 * (128 - cnt/int64(types.PtrSize)) } else { - p = pp.Append(p, ppc64.AMOVD, obj.TYPE_CONST, 0, base.Ctxt.FixedFrameSize()+off-8, obj.TYPE_REG, ppc64.REGTMP, 0) + p = pp.Append(p, ppc64.AMOVD, obj.TYPE_CONST, 0, base.Ctxt.Arch.FixedFrameSize+off-8, obj.TYPE_REG, ppc64.REGTMP, 0) p = pp.Append(p, ppc64.AADD, obj.TYPE_REG, ppc64.REGTMP, 0, obj.TYPE_REG, ppc64.REGRT1, 0) p.Reg = ppc64.REGSP p = pp.Append(p, ppc64.AMOVD, obj.TYPE_CONST, 0, cnt, obj.TYPE_REG, ppc64.REGTMP, 0) diff --git a/src/cmd/compile/internal/ppc64/ssa.go b/src/cmd/compile/internal/ppc64/ssa.go index da74cacd95..8689bd8b27 100644 --- a/src/cmd/compile/internal/ppc64/ssa.go +++ b/src/cmd/compile/internal/ppc64/ssa.go @@ -476,7 +476,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(ppc64.AMOVD) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() @@ -509,7 +509,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { for _, a := range v.Block.Func.RegArgs { // Pass the spill/unspill information along to the assembler, offset by size of // the saved LR slot. - addr := ssagen.SpillSlotAddr(a, ppc64.REGSP, base.Ctxt.FixedFrameSize()) + addr := ssagen.SpillSlotAddr(a, ppc64.REGSP, base.Ctxt.Arch.FixedFrameSize) s.FuncInfo().AddSpill( obj.RegSpill{Reg: a.Reg, Addr: addr, Unspill: loadByType(a.Type), Spill: storeByType(a.Type)}) } diff --git a/src/cmd/compile/internal/reflectdata/alg.go b/src/cmd/compile/internal/reflectdata/alg.go index d000618bd6..9fe90da0fe 100644 --- a/src/cmd/compile/internal/reflectdata/alg.go +++ b/src/cmd/compile/internal/reflectdata/alg.go @@ -412,22 +412,25 @@ func geneq(t *types.Type) *obj.LSym { // // if eq(p[0], q[0]) && eq(p[1], q[1]) && ... { // } else { - // return + // goto neq // } // // And so on. // // Otherwise it generates: // - // for i := 0; i < nelem; i++ { - // if eq(p[i], q[i]) { + // iterateTo := nelem/unroll*unroll + // for i := 0; i < iterateTo; i += unroll { + // if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) { // } else { // goto neq // } // } + // if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... { + // } else { + // goto neq + // } // - // TODO(josharian): consider doing some loop unrolling - // for larger nelem as well, processing a few elements at a time in a loop. checkAll := func(unroll int64, last bool, eq func(pi, qi ir.Node) ir.Node) { // checkIdx generates a node to check for equality at index i. checkIdx := func(i ir.Node) ir.Node { @@ -442,38 +445,62 @@ func geneq(t *types.Type) *obj.LSym { return eq(pi, qi) } - if nelem <= unroll { - if last { - // Do last comparison in a different manner. - nelem-- - } - // Generate a series of checks. - for i := int64(0); i < nelem; i++ { - // if check {} else { goto neq } - nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(i)), nil, nil) - nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) - fn.Body.Append(nif) - } - if last { - fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, checkIdx(ir.NewInt(nelem)))) - } - } else { - // Generate a for loop. - // for i := 0; i < nelem; i++ + iterations := nelem / unroll + iterateTo := iterations * unroll + // If a loop is iterated only once, there shouldn't be any loop at all. + if iterations == 1 { + iterateTo = 0 + } + + if iterateTo > 0 { + // Generate an unrolled for loop. + // for i := 0; i < nelem/unroll*unroll; i += unroll i := typecheck.Temp(types.Types[types.TINT]) init := ir.NewAssignStmt(base.Pos, i, ir.NewInt(0)) - cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(nelem)) - post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(1))) - loop := ir.NewForStmt(base.Pos, nil, cond, post, nil) + cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(iterateTo)) + loop := ir.NewForStmt(base.Pos, nil, cond, nil, nil) loop.PtrInit().Append(init) - // if eq(pi, qi) {} else { goto neq } - nif := ir.NewIfStmt(base.Pos, checkIdx(i), nil, nil) - nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) - loop.Body.Append(nif) - fn.Body.Append(loop) - if last { - fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true))) + + // if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) { + // } else { + // goto neq + // } + for j := int64(0); j < unroll; j++ { + // if check {} else { goto neq } + nif := ir.NewIfStmt(base.Pos, checkIdx(i), nil, nil) + nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) + loop.Body.Append(nif) + post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(1))) + loop.Body.Append(post) } + + fn.Body.Append(loop) + + if nelem == iterateTo { + if last { + fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true))) + } + return + } + } + + // Generate remaining checks, if nelem is not a multiple of unroll. + if last { + // Do last comparison in a different manner. + nelem-- + } + // if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... { + // } else { + // goto neq + // } + for j := iterateTo; j < nelem; j++ { + // if check {} else { goto neq } + nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(j)), nil, nil) + nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq)) + fn.Body.Append(nif) + } + if last { + fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, checkIdx(ir.NewInt(nelem)))) } } @@ -481,7 +508,6 @@ func geneq(t *types.Type) *obj.LSym { case types.TSTRING: // Do two loops. First, check that all the lengths match (cheap). // Second, check that all the contents match (expensive). - // TODO: when the array size is small, unroll the length match checks. checkAll(3, false, func(pi, qi ir.Node) ir.Node { // Compare lengths. eqlen, _ := EqString(pi, qi) @@ -655,7 +681,8 @@ func anyCall(fn *ir.Func) bool { } // eqfield returns the node -// p.field == q.field +// +// p.field == q.field func eqfield(p ir.Node, q ir.Node, field *types.Sym) ir.Node { nx := ir.NewSelectorExpr(base.Pos, ir.OXDOT, p, field) ny := ir.NewSelectorExpr(base.Pos, ir.OXDOT, q, field) @@ -664,9 +691,13 @@ func eqfield(p ir.Node, q ir.Node, field *types.Sym) ir.Node { } // EqString returns the nodes -// len(s) == len(t) +// +// len(s) == len(t) +// // and -// memequal(s.ptr, t.ptr, len(s)) +// +// memequal(s.ptr, t.ptr, len(s)) +// // which can be used to construct string equality comparison. // eqlen must be evaluated before eqmem, and shortcircuiting is required. func EqString(s, t ir.Node) (eqlen *ir.BinaryExpr, eqmem *ir.CallExpr) { @@ -688,9 +719,13 @@ func EqString(s, t ir.Node) (eqlen *ir.BinaryExpr, eqmem *ir.CallExpr) { } // EqInterface returns the nodes -// s.tab == t.tab (or s.typ == t.typ, as appropriate) +// +// s.tab == t.tab (or s.typ == t.typ, as appropriate) +// // and -// ifaceeq(s.tab, s.data, t.data) (or efaceeq(s.typ, s.data, t.data), as appropriate) +// +// ifaceeq(s.tab, s.data, t.data) (or efaceeq(s.typ, s.data, t.data), as appropriate) +// // which can be used to construct interface equality comparison. // eqtab must be evaluated before eqdata, and shortcircuiting is required. func EqInterface(s, t ir.Node) (eqtab *ir.BinaryExpr, eqdata *ir.CallExpr) { @@ -724,7 +759,8 @@ func EqInterface(s, t ir.Node) (eqtab *ir.BinaryExpr, eqdata *ir.CallExpr) { } // eqmem returns the node -// memequal(&p.field, &q.field [, size]) +// +// memequal(&p.field, &q.field [, size]) func eqmem(p ir.Node, q ir.Node, field *types.Sym, size int64) ir.Node { nx := typecheck.Expr(typecheck.NodAddr(ir.NewSelectorExpr(base.Pos, ir.OXDOT, p, field))) ny := typecheck.Expr(typecheck.NodAddr(ir.NewSelectorExpr(base.Pos, ir.OXDOT, q, field))) diff --git a/src/cmd/compile/internal/reflectdata/alg_test.go b/src/cmd/compile/internal/reflectdata/alg_test.go new file mode 100644 index 0000000000..1e57b913fd --- /dev/null +++ b/src/cmd/compile/internal/reflectdata/alg_test.go @@ -0,0 +1,76 @@ +// 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 reflectdata_test + +import "testing" + +func BenchmarkEqArrayOfStrings5(b *testing.B) { + var a [5]string + var c [5]string + + for i := 0; i < 5; i++ { + a[i] = "aaaa" + c[i] = "cccc" + } + + for j := 0; j < b.N; j++ { + _ = a == c + } +} + +func BenchmarkEqArrayOfStrings64(b *testing.B) { + var a [64]string + var c [64]string + + for i := 0; i < 64; i++ { + a[i] = "aaaa" + c[i] = "cccc" + } + + for j := 0; j < b.N; j++ { + _ = a == c + } +} + +func BenchmarkEqArrayOfStrings1024(b *testing.B) { + var a [1024]string + var c [1024]string + + for i := 0; i < 1024; i++ { + a[i] = "aaaa" + c[i] = "cccc" + } + + for j := 0; j < b.N; j++ { + _ = a == c + } +} + +func BenchmarkEqArrayOfFloats5(b *testing.B) { + var a [5]float32 + var c [5]float32 + + for i := 0; i < b.N; i++ { + _ = a == c + } +} + +func BenchmarkEqArrayOfFloats64(b *testing.B) { + var a [64]float32 + var c [64]float32 + + for i := 0; i < b.N; i++ { + _ = a == c + } +} + +func BenchmarkEqArrayOfFloats1024(b *testing.B) { + var a [1024]float32 + var c [1024]float32 + + for i := 0; i < b.N; i++ { + _ = a == c + } +} diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index 1137101650..affc6799ab 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -667,10 +667,10 @@ var kinds = []int{ // tflag is documented in reflect/type.go. // // tflag values must be kept in sync with copies in: -// - cmd/compile/internal/reflectdata/reflect.go -// - cmd/link/internal/ld/decodesym.go -// - reflect/type.go -// - runtime/type.go +// - cmd/compile/internal/reflectdata/reflect.go +// - cmd/link/internal/ld/decodesym.go +// - reflect/type.go +// - runtime/type.go const ( tflagUncommon = 1 << 0 tflagExtraStar = 1 << 1 @@ -1355,21 +1355,21 @@ func writeITab(lsym *obj.LSym, typ, iface *types.Type, allowNonImplement bool) { // type itab struct { // inter *interfacetype // _type *_type - // hash uint32 + // hash uint32 // copy of _type.hash. Used for type switches. // _ [4]byte - // fun [1]uintptr // variable sized + // fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. // } o := objw.SymPtr(lsym, 0, writeType(iface), 0) o = objw.SymPtr(lsym, o, writeType(typ), 0) o = objw.Uint32(lsym, o, types.TypeHash(typ)) // copy of type hash o += 4 // skip unused field + if !completeItab { + // If typ doesn't implement iface, make method entries be zero. + o = objw.Uintptr(lsym, o, 0) + entries = entries[:0] + } for _, fn := range entries { - if !completeItab { - // If typ doesn't implement iface, make method entries be zero. - o = objw.Uintptr(lsym, o, 0) - } else { - o = objw.SymPtrWeak(lsym, o, fn, 0) // method pointer for each method - } + o = objw.SymPtrWeak(lsym, o, fn, 0) // method pointer for each method } // Nothing writes static itabs, so they are read only. objw.Global(lsym, int32(o), int16(obj.DUPOK|obj.RODATA)) @@ -1821,13 +1821,17 @@ func NeedEmit(typ *types.Type) bool { // Also wraps methods on instantiated generic types for use in itab entries. // For an instantiated generic type G[int], we generate wrappers like: // G[int] pointer shaped: +// // func (x G[int]) f(arg) { // .inst.G[int].f(dictionary, x, arg) -// } +// } +// // G[int] not pointer shaped: +// // func (x *G[int]) f(arg) { // .inst.G[int].f(dictionary, *x, arg) -// } +// } +// // These wrappers are always fully stenciled. func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSym { orig := rcvr diff --git a/src/cmd/compile/internal/riscv64/ggen.go b/src/cmd/compile/internal/riscv64/ggen.go index 0f37f65fcf..44488e4327 100644 --- a/src/cmd/compile/internal/riscv64/ggen.go +++ b/src/cmd/compile/internal/riscv64/ggen.go @@ -19,7 +19,7 @@ func zeroRange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog } // Adjust the frame to account for LR. - off += base.Ctxt.FixedFrameSize() + off += base.Ctxt.Arch.FixedFrameSize if cnt < int64(4*types.PtrSize) { for i := int64(0); i < cnt; i += int64(types.PtrSize) { diff --git a/src/cmd/compile/internal/riscv64/ssa.go b/src/cmd/compile/internal/riscv64/ssa.go index b6e6dc1a03..5f74fd876c 100644 --- a/src/cmd/compile/internal/riscv64/ssa.go +++ b/src/cmd/compile/internal/riscv64/ssa.go @@ -237,7 +237,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { for _, a := range v.Block.Func.RegArgs { // Pass the spill/unspill information along to the assembler, offset by size of // the saved LR slot. - addr := ssagen.SpillSlotAddr(a, riscv.REG_SP, base.Ctxt.FixedFrameSize()) + addr := ssagen.SpillSlotAddr(a, riscv.REG_SP, base.Ctxt.Arch.FixedFrameSize) s.FuncInfo().AddSpill( obj.RegSpill{Reg: a.Reg, Addr: addr, Unspill: loadByType(a.Type), Spill: storeByType(a.Type)}) } @@ -669,7 +669,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(riscv.AMOV) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() diff --git a/src/cmd/compile/internal/s390x/ggen.go b/src/cmd/compile/internal/s390x/ggen.go index 488a080c46..70e4031224 100644 --- a/src/cmd/compile/internal/s390x/ggen.go +++ b/src/cmd/compile/internal/s390x/ggen.go @@ -24,7 +24,7 @@ func zerorange(pp *objw.Progs, p *obj.Prog, off, cnt int64, _ *uint32) *obj.Prog } // Adjust the frame to account for LR. - off += base.Ctxt.FixedFrameSize() + off += base.Ctxt.Arch.FixedFrameSize reg := int16(s390x.REGSP) // If the off cannot fit in a 12-bit unsigned displacement then we diff --git a/src/cmd/compile/internal/s390x/ssa.go b/src/cmd/compile/internal/s390x/ssa.go index deb6c79006..7d9b31de4c 100644 --- a/src/cmd/compile/internal/s390x/ssa.go +++ b/src/cmd/compile/internal/s390x/ssa.go @@ -132,7 +132,9 @@ func moveByType(t *types.Type) obj.As { } // opregreg emits instructions for -// dest := dest(To) op src(From) +// +// dest := dest(To) op src(From) +// // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). func opregreg(s *ssagen.State, op obj.As, dest, src int16) *obj.Prog { @@ -145,7 +147,9 @@ func opregreg(s *ssagen.State, op obj.As, dest, src int16) *obj.Prog { } // opregregimm emits instructions for +// // dest := src(From) op off +// // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). func opregregimm(s *ssagen.State, op obj.As, dest, src int16, off int64) *obj.Prog { @@ -546,7 +550,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is FixedFrameSize below the address of the first arg p := s.Prog(s390x.AMOVD) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() diff --git a/src/cmd/compile/internal/ssa/addressingmodes.go b/src/cmd/compile/internal/ssa/addressingmodes.go index 28fa86cd64..469ba0d494 100644 --- a/src/cmd/compile/internal/ssa/addressingmodes.go +++ b/src/cmd/compile/internal/ssa/addressingmodes.go @@ -131,10 +131,14 @@ var needSplit = map[Op]bool{ } // For each entry k, v in this map, if we have a value x with: -// x.Op == k[0] -// x.Args[0].Op == k[1] +// +// x.Op == k[0] +// x.Args[0].Op == k[1] +// // then we can set x.Op to v and set x.Args like this: -// x.Args[0].Args + x.Args[1:] +// +// x.Args[0].Args + x.Args[1:] +// // Additionally, the Aux/AuxInt from x.Args[0] is merged into x. var combine = map[[2]Op]Op{ // amd64 @@ -340,11 +344,18 @@ var combine = map[[2]Op]Op{ [2]Op{OpAMD64DIVSDload, OpAMD64LEAQ1}: OpAMD64DIVSDloadidx1, [2]Op{OpAMD64DIVSDload, OpAMD64LEAQ8}: OpAMD64DIVSDloadidx8, + [2]Op{OpAMD64SARXLload, OpAMD64ADDQ}: OpAMD64SARXLloadidx1, + [2]Op{OpAMD64SARXQload, OpAMD64ADDQ}: OpAMD64SARXQloadidx1, [2]Op{OpAMD64SHLXLload, OpAMD64ADDQ}: OpAMD64SHLXLloadidx1, [2]Op{OpAMD64SHLXQload, OpAMD64ADDQ}: OpAMD64SHLXQloadidx1, [2]Op{OpAMD64SHRXLload, OpAMD64ADDQ}: OpAMD64SHRXLloadidx1, [2]Op{OpAMD64SHRXQload, OpAMD64ADDQ}: OpAMD64SHRXQloadidx1, + [2]Op{OpAMD64SARXLload, OpAMD64LEAQ1}: OpAMD64SARXLloadidx1, + [2]Op{OpAMD64SARXLload, OpAMD64LEAQ4}: OpAMD64SARXLloadidx4, + [2]Op{OpAMD64SARXLload, OpAMD64LEAQ8}: OpAMD64SARXLloadidx8, + [2]Op{OpAMD64SARXQload, OpAMD64LEAQ1}: OpAMD64SARXQloadidx1, + [2]Op{OpAMD64SARXQload, OpAMD64LEAQ8}: OpAMD64SARXQloadidx8, [2]Op{OpAMD64SHLXLload, OpAMD64LEAQ1}: OpAMD64SHLXLloadidx1, [2]Op{OpAMD64SHLXLload, OpAMD64LEAQ4}: OpAMD64SHLXLloadidx4, [2]Op{OpAMD64SHLXLload, OpAMD64LEAQ8}: OpAMD64SHLXLloadidx8, @@ -356,6 +367,26 @@ var combine = map[[2]Op]Op{ [2]Op{OpAMD64SHRXQload, OpAMD64LEAQ1}: OpAMD64SHRXQloadidx1, [2]Op{OpAMD64SHRXQload, OpAMD64LEAQ8}: OpAMD64SHRXQloadidx8, + // amd64/v3 + [2]Op{OpAMD64MOVBELload, OpAMD64ADDQ}: OpAMD64MOVBELloadidx1, + [2]Op{OpAMD64MOVBEQload, OpAMD64ADDQ}: OpAMD64MOVBEQloadidx1, + [2]Op{OpAMD64MOVBELload, OpAMD64LEAQ1}: OpAMD64MOVBELloadidx1, + [2]Op{OpAMD64MOVBELload, OpAMD64LEAQ4}: OpAMD64MOVBELloadidx4, + [2]Op{OpAMD64MOVBELload, OpAMD64LEAQ8}: OpAMD64MOVBELloadidx8, + [2]Op{OpAMD64MOVBEQload, OpAMD64LEAQ1}: OpAMD64MOVBEQloadidx1, + [2]Op{OpAMD64MOVBEQload, OpAMD64LEAQ8}: OpAMD64MOVBEQloadidx8, + + [2]Op{OpAMD64MOVBEWstore, OpAMD64ADDQ}: OpAMD64MOVBEWstoreidx1, + [2]Op{OpAMD64MOVBELstore, OpAMD64ADDQ}: OpAMD64MOVBELstoreidx1, + [2]Op{OpAMD64MOVBEQstore, OpAMD64ADDQ}: OpAMD64MOVBEQstoreidx1, + [2]Op{OpAMD64MOVBEWstore, OpAMD64LEAQ1}: OpAMD64MOVBEWstoreidx1, + [2]Op{OpAMD64MOVBEWstore, OpAMD64LEAQ2}: OpAMD64MOVBEWstoreidx2, + [2]Op{OpAMD64MOVBELstore, OpAMD64LEAQ1}: OpAMD64MOVBELstoreidx1, + [2]Op{OpAMD64MOVBELstore, OpAMD64LEAQ4}: OpAMD64MOVBELstoreidx4, + [2]Op{OpAMD64MOVBELstore, OpAMD64LEAQ8}: OpAMD64MOVBELstoreidx8, + [2]Op{OpAMD64MOVBEQstore, OpAMD64LEAQ1}: OpAMD64MOVBEQstoreidx1, + [2]Op{OpAMD64MOVBEQstore, OpAMD64LEAQ8}: OpAMD64MOVBEQstoreidx8, + // 386 [2]Op{Op386MOVBload, Op386ADDL}: Op386MOVBloadidx1, [2]Op{Op386MOVWload, Op386ADDL}: Op386MOVWloadidx1, diff --git a/src/cmd/compile/internal/ssa/block.go b/src/cmd/compile/internal/ssa/block.go index 4d21ade3e3..db7df3f338 100644 --- a/src/cmd/compile/internal/ssa/block.go +++ b/src/cmd/compile/internal/ssa/block.go @@ -71,19 +71,25 @@ type Block struct { // Edge represents a CFG edge. // Example edges for b branching to either c or d. // (c and d have other predecessors.) -// b.Succs = [{c,3}, {d,1}] -// c.Preds = [?, ?, ?, {b,0}] -// d.Preds = [?, {b,1}, ?] +// +// b.Succs = [{c,3}, {d,1}] +// c.Preds = [?, ?, ?, {b,0}] +// d.Preds = [?, {b,1}, ?] +// // These indexes allow us to edit the CFG in constant time. // In addition, it informs phi ops in degenerate cases like: -// b: -// if k then c else c -// c: -// v = Phi(x, y) +// +// b: +// if k then c else c +// c: +// v = Phi(x, y) +// // Then the indexes tell you whether x is chosen from // the if or else branch from b. -// b.Succs = [{c,0},{c,1}] -// c.Preds = [{b,0},{b,1}] +// +// b.Succs = [{c,0},{c,1}] +// c.Preds = [{b,0},{b,1}] +// // means x is chosen if k is true. type Edge struct { // block edge goes to (in a Succs list) or from (in a Preds list) @@ -106,12 +112,13 @@ func (e Edge) String() string { } // BlockKind is the kind of SSA block. -// kind controls successors -// ------------------------------------------ -// Exit [return mem] [] -// Plain [] [next] -// If [boolean Value] [then, else] -// Defer [mem] [nopanic, panic] (control opcode should be OpStaticCall to runtime.deferproc) +// +// kind controls successors +// ------------------------------------------ +// Exit [return mem] [] +// Plain [] [next] +// If [boolean Value] [then, else] +// Defer [mem] [nopanic, panic] (control opcode should be OpStaticCall to runtime.deferproc) type BlockKind int8 // short form print @@ -330,10 +337,12 @@ func (b *Block) swapSuccessors() { // // b.removePred(i) // for _, v := range b.Values { -// if v.Op != OpPhi { -// continue -// } -// b.removeArg(v, i) +// +// if v.Op != OpPhi { +// continue +// } +// b.removeArg(v, i) +// // } func (b *Block) removePhiArg(phi *Value, i int) { n := len(b.Preds) diff --git a/src/cmd/compile/internal/ssa/branchelim.go b/src/cmd/compile/internal/ssa/branchelim.go index 59773ef31b..7a08654f4e 100644 --- a/src/cmd/compile/internal/ssa/branchelim.go +++ b/src/cmd/compile/internal/ssa/branchelim.go @@ -11,11 +11,11 @@ import "cmd/internal/src" // // Search for basic blocks that look like // -// bb0 bb0 -// | \ / \ -// | bb1 or bb1 bb2 <- trivial if/else blocks -// | / \ / -// bb2 bb3 +// bb0 bb0 +// | \ / \ +// | bb1 or bb1 bb2 <- trivial if/else blocks +// | / \ / +// bb2 bb3 // // where the intermediate blocks are mostly empty (with no side-effects); // rewrite Phis in the postdominator as CondSelects. diff --git a/src/cmd/compile/internal/ssa/check.go b/src/cmd/compile/internal/ssa/check.go index 28edfd2237..df677e674a 100644 --- a/src/cmd/compile/internal/ssa/check.go +++ b/src/cmd/compile/internal/ssa/check.go @@ -100,6 +100,10 @@ func checkFunc(f *Func) { if b.NumControls() != 0 { f.Fatalf("plain/dead block %s has a control value", b) } + case BlockJumpTable: + if b.NumControls() != 1 { + f.Fatalf("jumpTable block %s has no control value", b) + } } if len(b.Succs) != 2 && b.Likely != BranchUnknown { f.Fatalf("likeliness prediction %d for block %s with %d successors", b.Likely, b, len(b.Succs)) diff --git a/src/cmd/compile/internal/ssa/compile.go b/src/cmd/compile/internal/ssa/compile.go index f95140eaf9..5e898ab96f 100644 --- a/src/cmd/compile/internal/ssa/compile.go +++ b/src/cmd/compile/internal/ssa/compile.go @@ -250,8 +250,8 @@ var GenssaDump map[string]bool = make(map[string]bool) // names of functions to // version is used as a regular expression to match the phase name(s). // // Special cases that have turned out to be useful: -// - ssa/check/on enables checking after each phase -// - ssa/all/time enables time reporting for all phases +// - ssa/check/on enables checking after each phase +// - ssa/all/time enables time reporting for all phases // // See gc/lex.go for dissection of the option string. // Example uses: diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go index b9c98bdba9..931ef454fc 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -168,6 +168,9 @@ type Frontend interface { // MyImportPath provides the import name (roughly, the package) for the function being compiled. MyImportPath() string + + // LSym returns the linker symbol of the function being compiled. + LSym() string } // NewConfig returns a new configuration object for the given architecture. @@ -297,8 +300,8 @@ func NewConfig(arch string, types Types, ctxt *obj.Link, optimize, softfloat boo c.registers = registersRISCV64[:] c.gpRegMask = gpRegMaskRISCV64 c.fpRegMask = fpRegMaskRISCV64 - // c.intParamRegs = paramIntRegRISCV64 - // c.floatParamRegs = paramFloatRegRISCV64 + c.intParamRegs = paramIntRegRISCV64 + c.floatParamRegs = paramFloatRegRISCV64 c.FPReg = framepointerRegRISCV64 c.hasGReg = true case "wasm": @@ -329,8 +332,8 @@ func NewConfig(arch string, types Types, ctxt *obj.Link, optimize, softfloat boo c.floatParamRegs = nil // no FP registers in softfloat mode } - c.ABI0 = abi.NewABIConfig(0, 0, ctxt.FixedFrameSize()) - c.ABI1 = abi.NewABIConfig(len(c.intParamRegs), len(c.floatParamRegs), ctxt.FixedFrameSize()) + c.ABI0 = abi.NewABIConfig(0, 0, ctxt.Arch.FixedFrameSize) + c.ABI1 = abi.NewABIConfig(len(c.intParamRegs), len(c.floatParamRegs), ctxt.Arch.FixedFrameSize) // On Plan 9, floating point operations are not allowed in note handler. if buildcfg.GOOS == "plan9" { diff --git a/src/cmd/compile/internal/ssa/cse.go b/src/cmd/compile/internal/ssa/cse.go index ade5e0648e..f4b799394c 100644 --- a/src/cmd/compile/internal/ssa/cse.go +++ b/src/cmd/compile/internal/ssa/cse.go @@ -235,14 +235,15 @@ type eqclass []*Value // partitionValues partitions the values into equivalence classes // based on having all the following features match: -// - opcode -// - type -// - auxint -// - aux -// - nargs -// - block # if a phi op -// - first two arg's opcodes and auxint -// - NOT first two arg's aux; that can break CSE. +// - opcode +// - type +// - auxint +// - aux +// - nargs +// - block # if a phi op +// - first two arg's opcodes and auxint +// - NOT first two arg's aux; that can break CSE. +// // partitionValues returns a list of equivalence classes, each // being a sorted by ID list of *Values. The eqclass slices are // backed by the same storage as the input slice. diff --git a/src/cmd/compile/internal/ssa/debug.go b/src/cmd/compile/internal/ssa/debug.go index 08dc5c468e..2c18d35204 100644 --- a/src/cmd/compile/internal/ssa/debug.go +++ b/src/cmd/compile/internal/ssa/debug.go @@ -402,28 +402,28 @@ func (sc *slotCanonicalizer) canonSlot(idx SlKeyIdx) LocalSlot { // OpArg{Int,Float}Reg values, inserting additional values in // cases where they are missing. Example: // -// func foo(s string, used int, notused int) int { -// return len(s) + used -// } +// func foo(s string, used int, notused int) int { +// return len(s) + used +// } // // In the function above, the incoming parameter "used" is fully live, // "notused" is not live, and "s" is partially live (only the length // field of the string is used). At the point where debug value // analysis runs, we might expect to see an entry block with: // -// b1: -// v4 = ArgIntReg {s+8} [0] : BX -// v5 = ArgIntReg {used} [0] : CX +// b1: +// v4 = ArgIntReg {s+8} [0] : BX +// v5 = ArgIntReg {used} [0] : CX // // While this is an accurate picture of the live incoming params, // we also want to have debug locations for non-live params (or // their non-live pieces), e.g. something like // -// b1: -// v9 = ArgIntReg <*uint8> {s+0} [0] : AX -// v4 = ArgIntReg {s+8} [0] : BX -// v5 = ArgIntReg {used} [0] : CX -// v10 = ArgIntReg {unused} [0] : DI +// b1: +// v9 = ArgIntReg <*uint8> {s+0} [0] : AX +// v4 = ArgIntReg {s+8} [0] : BX +// v5 = ArgIntReg {used} [0] : CX +// v10 = ArgIntReg {unused} [0] : DI // // This function examines the live OpArg{Int,Float}Reg values and // synthesizes new (dead) values for the non-live params or the @@ -1489,14 +1489,14 @@ func setupLocList(ctxt *obj.Link, f *Func, list []byte, st, en ID) ([]byte, int) // that spills a register arg. It returns the ID of that instruction // Example: // -// b1: -// v3 = ArgIntReg {p1+0} [0] : AX -// ... more arg regs .. -// v4 = ArgFloatReg {f1+0} [0] : X0 -// v52 = MOVQstore {p1} v2 v3 v1 -// ... more stores ... -// v68 = MOVSSstore {f4} v2 v67 v66 -// v38 = MOVQstoreconst {blob} [val=0,off=0] v2 v32 +// b1: +// v3 = ArgIntReg {p1+0} [0] : AX +// ... more arg regs .. +// v4 = ArgFloatReg {f1+0} [0] : X0 +// v52 = MOVQstore {p1} v2 v3 v1 +// ... more stores ... +// v68 = MOVSSstore {f4} v2 v67 v66 +// v38 = MOVQstoreconst {blob} [val=0,off=0] v2 v32 // // Important: locatePrologEnd is expected to work properly only with // optimization turned off (e.g. "-N"). If optimization is enabled diff --git a/src/cmd/compile/internal/ssa/debug_test.go b/src/cmd/compile/internal/ssa/debug_test.go index 2fc12557c0..c807863ea6 100644 --- a/src/cmd/compile/internal/ssa/debug_test.go +++ b/src/cmd/compile/internal/ssa/debug_test.go @@ -84,7 +84,7 @@ var optimizedLibs = (!strings.Contains(gogcflags, "-N") && !strings.Contains(gog // "O" is an explicit indication that we expect it to be optimized out. // For example: // -// if len(os.Args) > 1 { //gdb-dbg=(hist/A,cannedInput/A) //dlv-dbg=(hist/A,cannedInput/A) +// if len(os.Args) > 1 { //gdb-dbg=(hist/A,cannedInput/A) //dlv-dbg=(hist/A,cannedInput/A) // // TODO: not implemented for Delve yet, but this is the plan // diff --git a/src/cmd/compile/internal/ssa/expand_calls.go b/src/cmd/compile/internal/ssa/expand_calls.go index a3cea855f2..90ea2d5040 100644 --- a/src/cmd/compile/internal/ssa/expand_calls.go +++ b/src/cmd/compile/internal/ssa/expand_calls.go @@ -656,15 +656,16 @@ outer: // It decomposes a Load or an Arg into smaller parts and returns the new mem. // If the type does not match one of the expected aggregate types, it returns nil instead. // Parameters: -// pos -- the location of any generated code. -// b -- the block into which any generated code should normally be placed -// source -- the value, possibly an aggregate, to be stored. -// mem -- the mem flowing into this decomposition (loads depend on it, stores updated it) -// t -- the type of the value to be stored -// storeOffset -- if the value is stored in memory, it is stored at base (see storeRc) + storeOffset -// loadRegOffset -- regarding source as a value in registers, the register offset in ABI1. Meaningful only if source is OpArg. -// storeRc -- storeRC; if the value is stored in registers, this specifies the registers. -// StoreRc also identifies whether the target is registers or memory, and has the base for the store operation. +// +// pos -- the location of any generated code. +// b -- the block into which any generated code should normally be placed +// source -- the value, possibly an aggregate, to be stored. +// mem -- the mem flowing into this decomposition (loads depend on it, stores updated it) +// t -- the type of the value to be stored +// storeOffset -- if the value is stored in memory, it is stored at base (see storeRc) + storeOffset +// loadRegOffset -- regarding source as a value in registers, the register offset in ABI1. Meaningful only if source is OpArg. +// storeRc -- storeRC; if the value is stored in registers, this specifies the registers. +// StoreRc also identifies whether the target is registers or memory, and has the base for the store operation. func (x *expandState) decomposeArg(pos src.XPos, b *Block, source, mem *Value, t *types.Type, storeOffset int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { pa := x.prAssignForArg(source) @@ -777,15 +778,16 @@ func (x *expandState) splitSlotsIntoNames(locs []*LocalSlot, suffix string, off // It decomposes a Load into smaller parts and returns the new mem. // If the type does not match one of the expected aggregate types, it returns nil instead. // Parameters: -// pos -- the location of any generated code. -// b -- the block into which any generated code should normally be placed -// source -- the value, possibly an aggregate, to be stored. -// mem -- the mem flowing into this decomposition (loads depend on it, stores updated it) -// t -- the type of the value to be stored -// storeOffset -- if the value is stored in memory, it is stored at base (see storeRc) + offset -// loadRegOffset -- regarding source as a value in registers, the register offset in ABI1. Meaningful only if source is OpArg. -// storeRc -- storeRC; if the value is stored in registers, this specifies the registers. -// StoreRc also identifies whether the target is registers or memory, and has the base for the store operation. +// +// pos -- the location of any generated code. +// b -- the block into which any generated code should normally be placed +// source -- the value, possibly an aggregate, to be stored. +// mem -- the mem flowing into this decomposition (loads depend on it, stores updated it) +// t -- the type of the value to be stored +// storeOffset -- if the value is stored in memory, it is stored at base (see storeRc) + offset +// loadRegOffset -- regarding source as a value in registers, the register offset in ABI1. Meaningful only if source is OpArg. +// storeRc -- storeRC; if the value is stored in registers, this specifies the registers. +// StoreRc also identifies whether the target is registers or memory, and has the base for the store operation. // // TODO -- this needs cleanup; it just works for SSA-able aggregates, and won't fully generalize to register-args aggregates. func (x *expandState) decomposeLoad(pos src.XPos, b *Block, source, mem *Value, t *types.Type, storeOffset int64, loadRegOffset Abi1RO, storeRc registerCursor) *Value { @@ -1106,7 +1108,7 @@ func (x *expandState) rewriteArgs(v *Value, firstArg int) { a0 := a.Args[0] if a0.Op == OpLocalAddr { n := a0.Aux.(*ir.Name) - if n.Class == ir.PPARAM && n.FrameOffset()+x.f.Config.ctxt.FixedFrameSize() == aOffset { + if n.Class == ir.PPARAM && n.FrameOffset()+x.f.Config.ctxt.Arch.FixedFrameSize == aOffset { continue } } @@ -1127,7 +1129,7 @@ func (x *expandState) rewriteArgs(v *Value, firstArg int) { // It's common for a tail call passing the same arguments (e.g. method wrapper), // so this would be a self copy. Detect this and optimize it out. n := a.Aux.(*ir.Name) - if n.Class == ir.PPARAM && n.FrameOffset()+x.f.Config.ctxt.FixedFrameSize() == aOffset { + if n.Class == ir.PPARAM && n.FrameOffset()+x.f.Config.ctxt.Arch.FixedFrameSize == aOffset { continue } } diff --git a/src/cmd/compile/internal/ssa/export_test.go b/src/cmd/compile/internal/ssa/export_test.go index c4e87ec7d0..87d1b41419 100644 --- a/src/cmd/compile/internal/ssa/export_test.go +++ b/src/cmd/compile/internal/ssa/export_test.go @@ -102,6 +102,9 @@ func (d TestFrontend) Debug_checknil() bool { retu func (d TestFrontend) MyImportPath() string { return "my/import/path" } +func (d TestFrontend) LSym() string { + return "my/import/path.function" +} var testTypes Types diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 0b5392f0f0..35a9382663 100644 --- a/src/cmd/compile/internal/ssa/func.go +++ b/src/cmd/compile/internal/ssa/func.go @@ -820,17 +820,22 @@ func (f *Func) invalidateCFG() { } // DebugHashMatch reports whether environment variable evname -// 1) is empty (this is a special more-quickly implemented case of 3) -// 2) is "y" or "Y" -// 3) is a suffix of the sha1 hash of name -// 4) is a suffix of the environment variable +// 1. is empty (this is a special more-quickly implemented case of 3) +// 2. is "y" or "Y" +// 3. is a suffix of the sha1 hash of name +// 4. is a suffix of the environment variable // fmt.Sprintf("%s%d", evname, n) // provided that all such variables are nonempty for 0 <= i <= n +// // Otherwise it returns false. // When true is returned the message -// "%s triggered %s\n", evname, name +// +// "%s triggered %s\n", evname, name +// // is printed on the file named in environment variable -// GSHS_LOGFILE +// +// GSHS_LOGFILE +// // or standard out if that is empty or there is an error // opening the file. func (f *Func) DebugHashMatch(evname string) bool { diff --git a/src/cmd/compile/internal/ssa/fuse.go b/src/cmd/compile/internal/ssa/fuse.go index fec2ba8773..2b176dfa7b 100644 --- a/src/cmd/compile/internal/ssa/fuse.go +++ b/src/cmd/compile/internal/ssa/fuse.go @@ -55,19 +55,21 @@ func fuse(f *Func, typ fuseType) { // fuseBlockIf handles the following cases where s0 and s1 are empty blocks. // -// b b b b -// \ / \ / | \ / \ / | | | -// s0 s1 | s1 s0 | | | -// \ / | / \ | | | -// ss ss ss ss +// b b b b +// \ / \ / | \ / \ / | | | +// s0 s1 | s1 s0 | | | +// \ / | / \ | | | +// ss ss ss ss // // If all Phi ops in ss have identical variables for slots corresponding to // s0, s1 and b then the branch can be dropped. // This optimization often comes up in switch statements with multiple // expressions in a case clause: -// switch n { -// case 1,2,3: return 4 -// } +// +// switch n { +// case 1,2,3: return 4 +// } +// // TODO: If ss doesn't contain any OpPhis, are s0 and s1 dead code anyway. func fuseBlockIf(b *Block) bool { if b.Kind != BlockIf { diff --git a/src/cmd/compile/internal/ssa/fuse_branchredirect.go b/src/cmd/compile/internal/ssa/fuse_branchredirect.go index 27449db55a..59570968a2 100644 --- a/src/cmd/compile/internal/ssa/fuse_branchredirect.go +++ b/src/cmd/compile/internal/ssa/fuse_branchredirect.go @@ -8,21 +8,24 @@ package ssa // of an If block can be derived from its predecessor If block, in // some such cases, we can redirect the predecessor If block to the // corresponding successor block directly. For example: -// p: -// v11 = Less64 v10 v8 -// If v11 goto b else u -// b: <- p ... -// v17 = Leq64 v10 v8 -// If v17 goto s else o +// +// p: +// v11 = Less64 v10 v8 +// If v11 goto b else u +// b: <- p ... +// v17 = Leq64 v10 v8 +// If v17 goto s else o +// // We can redirect p to s directly. // // The implementation here borrows the framework of the prove pass. -// 1, Traverse all blocks of function f to find If blocks. -// 2, For any If block b, traverse all its predecessors to find If blocks. -// 3, For any If block predecessor p, update relationship p->b. -// 4, Traverse all successors of b. -// 5, For any successor s of b, try to update relationship b->s, if a -// contradiction is found then redirect p to another successor of b. +// +// 1, Traverse all blocks of function f to find If blocks. +// 2, For any If block b, traverse all its predecessors to find If blocks. +// 3, For any If block predecessor p, update relationship p->b. +// 4, Traverse all successors of b. +// 5, For any successor s of b, try to update relationship b->s, if a +// contradiction is found then redirect p to another successor of b. func fuseBranchRedirect(f *Func) bool { ft := newFactsTable(f) ft.checkpoint() diff --git a/src/cmd/compile/internal/ssa/fuse_comparisons.go b/src/cmd/compile/internal/ssa/fuse_comparisons.go index d843fc3fda..f5fb84b0d7 100644 --- a/src/cmd/compile/internal/ssa/fuse_comparisons.go +++ b/src/cmd/compile/internal/ssa/fuse_comparisons.go @@ -9,22 +9,22 @@ package ssa // // Look for branch structure like: // -// p -// |\ -// | b -// |/ \ -// s0 s1 +// p +// |\ +// | b +// |/ \ +// s0 s1 // // In our example, p has control '1 <= x', b has control 'x < 5', // and s0 and s1 are the if and else results of the comparison. // // This will be optimized into: // -// p -// \ -// b -// / \ -// s0 s1 +// p +// \ +// b +// / \ +// s0 s1 // // where b has the combined control value 'unsigned(x-1) < 4'. // Later passes will then fuse p and b. diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index d50bdf2a17..81fdebaf49 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -206,6 +206,11 @@ (Rsh16x(64|32|16|8) x y) && shiftIsBounded(v) => (SARW x y) (Rsh8x(64|32|16|8) x y) && shiftIsBounded(v) => (SARB x y) +// Prefer SARX/SHLX/SHRX instruction because it has less register restriction on the shift input. +(SAR(Q|L) x y) && buildcfg.GOAMD64 >= 3 => (SARX(Q|L) x y) +(SHL(Q|L) x y) && buildcfg.GOAMD64 >= 3 => (SHLX(Q|L) x y) +(SHR(Q|L) x y) && buildcfg.GOAMD64 >= 3 => (SHRX(Q|L) x y) + // Lowering integer comparisons (Less(64|32|16|8) x y) => (SETL (CMP(Q|L|W|B) x y)) (Less(64|32|16|8)U x y) => (SETB (CMP(Q|L|W|B) x y)) @@ -512,6 +517,8 @@ (If cond yes no) => (NE (TESTB cond cond) yes no) +(JumpTable idx) => (JUMPTABLE {makeJumpTableSym(b)} idx (LEAQ {makeJumpTableSym(b)} (SB))) + // Atomic loads. Other than preserving their ordering with respect to other loads, nothing special here. (AtomicLoad8 ptr mem) => (MOVBatomicload ptr mem) (AtomicLoad32 ptr mem) => (MOVLatomicload ptr mem) @@ -590,6 +597,8 @@ // mutandis, for UGE and SETAE, and CC and SETCC. ((NE|EQ) (TESTL (SHLL (MOVLconst [1]) x) y)) => ((ULT|UGE) (BTL x y)) ((NE|EQ) (TESTQ (SHLQ (MOVQconst [1]) x) y)) => ((ULT|UGE) (BTQ x y)) +((NE|EQ) (TESTL (SHLXL (MOVLconst [1]) x) y)) => ((ULT|UGE) (BTL x y)) +((NE|EQ) (TESTQ (SHLXQ (MOVQconst [1]) x) y)) => ((ULT|UGE) (BTQ x y)) ((NE|EQ) (TESTLconst [c] x)) && isUint32PowerOfTwo(int64(c)) => ((ULT|UGE) (BTLconst [int8(log32(c))] x)) ((NE|EQ) (TESTQconst [c] x)) && isUint64PowerOfTwo(int64(c)) @@ -598,6 +607,8 @@ => ((ULT|UGE) (BTQconst [int8(log64(c))] x)) (SET(NE|EQ) (TESTL (SHLL (MOVLconst [1]) x) y)) => (SET(B|AE) (BTL x y)) (SET(NE|EQ) (TESTQ (SHLQ (MOVQconst [1]) x) y)) => (SET(B|AE) (BTQ x y)) +(SET(NE|EQ) (TESTL (SHLXL (MOVLconst [1]) x) y)) => (SET(B|AE) (BTL x y)) +(SET(NE|EQ) (TESTQ (SHLXQ (MOVQconst [1]) x) y)) => (SET(B|AE) (BTQ x y)) (SET(NE|EQ) (TESTLconst [c] x)) && isUint32PowerOfTwo(int64(c)) => (SET(B|AE) (BTLconst [int8(log32(c))] x)) (SET(NE|EQ) (TESTQconst [c] x)) && isUint64PowerOfTwo(int64(c)) @@ -609,6 +620,10 @@ => (SET(B|AE)store [off] {sym} ptr (BTL x y) mem) (SET(NE|EQ)store [off] {sym} ptr (TESTQ (SHLQ (MOVQconst [1]) x) y) mem) => (SET(B|AE)store [off] {sym} ptr (BTQ x y) mem) +(SET(NE|EQ)store [off] {sym} ptr (TESTL (SHLXL (MOVLconst [1]) x) y) mem) + => (SET(B|AE)store [off] {sym} ptr (BTL x y) mem) +(SET(NE|EQ)store [off] {sym} ptr (TESTQ (SHLXQ (MOVQconst [1]) x) y) mem) + => (SET(B|AE)store [off] {sym} ptr (BTQ x y) mem) (SET(NE|EQ)store [off] {sym} ptr (TESTLconst [c] x) mem) && isUint32PowerOfTwo(int64(c)) => (SET(B|AE)store [off] {sym} ptr (BTLconst [int8(log32(c))] x) mem) (SET(NE|EQ)store [off] {sym} ptr (TESTQconst [c] x) mem) && isUint64PowerOfTwo(int64(c)) @@ -621,9 +636,10 @@ (BT(Q|L)const [c] (SHRQconst [d] x)) && (c+d)<64 => (BTQconst [c+d] x) (BT(Q|L)const [c] (SHLQconst [d] x)) && c>d => (BT(Q|L)const [c-d] x) (BT(Q|L)const [0] s:(SHRQ x y)) => (BTQ y x) +(BT(Q|L)const [0] s:(SHRXQ x y)) => (BTQ y x) (BTLconst [c] (SHRLconst [d] x)) && (c+d)<32 => (BTLconst [c+d] x) (BTLconst [c] (SHLLconst [d] x)) && c>d => (BTLconst [c-d] x) -(BTLconst [0] s:(SHRL x y)) => (BTL y x) +(BTLconst [0] s:(SHR(L|XL) x y)) => (BTL y x) // Rewrite a & 1 != 1 into a & 1 == 0. // Among other things, this lets us turn (a>>b)&1 != 1 into a bit test. @@ -635,6 +651,8 @@ // Recognize bit setting (a |= 1< (BTS(Q|L) x y) (XOR(Q|L) (SHL(Q|L) (MOV(Q|L)const [1]) y) x) => (BTC(Q|L) x y) +(OR(Q|L) (SHLX(Q|L) (MOV(Q|L)const [1]) y) x) => (BTS(Q|L) x y) +(XOR(Q|L) (SHLX(Q|L) (MOV(Q|L)const [1]) y) x) => (BTC(Q|L) x y) // Convert ORconst into BTS, if the code gets smaller, with boundary being // (ORL $40,AX is 3 bytes, ORL $80,AX is 6 bytes). @@ -650,6 +668,8 @@ // Recognize bit clearing: a &^= 1< (BTR(Q|L) x y) (ANDN(Q|L) x (SHL(Q|L) (MOV(Q|L)const [1]) y)) => (BTR(Q|L) x y) +(AND(Q|L) (NOT(Q|L) (SHLX(Q|L) (MOV(Q|L)const [1]) y)) x) => (BTR(Q|L) x y) +(ANDN(Q|L) x (SHLX(Q|L) (MOV(Q|L)const [1]) y)) => (BTR(Q|L) x y) (ANDQconst [c] x) && isUint64PowerOfTwo(int64(^c)) && uint64(^c) >= 128 => (BTRQconst [int8(log32(^c))] x) (ANDLconst [c] x) && isUint32PowerOfTwo(int64(^c)) && uint64(^c) >= 128 @@ -791,6 +811,8 @@ (SHLQ x (MOV(Q|L)const [c])) => (SHLQconst [int8(c&63)] x) (SHLL x (MOV(Q|L)const [c])) => (SHLLconst [int8(c&31)] x) +(SHLXQ x (MOV(Q|L)const [c])) => (SHLQconst [int8(c&63)] x) +(SHLXL x (MOV(Q|L)const [c])) => (SHLLconst [int8(c&31)] x) (SHRQ x (MOV(Q|L)const [c])) => (SHRQconst [int8(c&63)] x) (SHRL x (MOV(Q|L)const [c])) => (SHRLconst [int8(c&31)] x) @@ -798,33 +820,36 @@ (SHRW _ (MOV(Q|L)const [c])) && c&31 >= 16 => (MOVLconst [0]) (SHRB x (MOV(Q|L)const [c])) && c&31 < 8 => (SHRBconst [int8(c&31)] x) (SHRB _ (MOV(Q|L)const [c])) && c&31 >= 8 => (MOVLconst [0]) +(SHRXQ x (MOV(Q|L)const [c])) => (SHRQconst [int8(c&63)] x) +(SHRXL x (MOV(Q|L)const [c])) => (SHRLconst [int8(c&31)] x) (SARQ x (MOV(Q|L)const [c])) => (SARQconst [int8(c&63)] x) (SARL x (MOV(Q|L)const [c])) => (SARLconst [int8(c&31)] x) (SARW x (MOV(Q|L)const [c])) => (SARWconst [int8(min(int64(c)&31,15))] x) (SARB x (MOV(Q|L)const [c])) => (SARBconst [int8(min(int64(c)&31,7))] x) - +(SARXQ x (MOV(Q|L)const [c])) => (SARQconst [int8(c&63)] x) +(SARXL x (MOV(Q|L)const [c])) => (SARLconst [int8(c&31)] x) // Operations which don't affect the low 6/5 bits of the shift amount are NOPs. -((SHLQ|SHRQ|SARQ) x (ADDQconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ) x y) -((SHLQ|SHRQ|SARQ) x (NEGQ (ADDQconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ) x (NEGQ y)) -((SHLQ|SHRQ|SARQ) x (ANDQconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ) x y) -((SHLQ|SHRQ|SARQ) x (NEGQ (ANDQconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ) x (NEGQ y)) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (ADDQconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGQ (ADDQconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGQ y)) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (ANDQconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGQ (ANDQconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGQ y)) -((SHLL|SHRL|SARL) x (ADDQconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL) x y) -((SHLL|SHRL|SARL) x (NEGQ (ADDQconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL) x (NEGQ y)) -((SHLL|SHRL|SARL) x (ANDQconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL) x y) -((SHLL|SHRL|SARL) x (NEGQ (ANDQconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL) x (NEGQ y)) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (ADDQconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x y) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGQ (ADDQconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGQ y)) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (ANDQconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x y) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGQ (ANDQconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGQ y)) -((SHLQ|SHRQ|SARQ) x (ADDLconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ) x y) -((SHLQ|SHRQ|SARQ) x (NEGL (ADDLconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ) x (NEGL y)) -((SHLQ|SHRQ|SARQ) x (ANDLconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ) x y) -((SHLQ|SHRQ|SARQ) x (NEGL (ANDLconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ) x (NEGL y)) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (ADDLconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGL (ADDLconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGL y)) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (ANDLconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGL (ANDLconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SHLXQ|SHRXQ|SARXQ) x (NEGL y)) -((SHLL|SHRL|SARL) x (ADDLconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL) x y) -((SHLL|SHRL|SARL) x (NEGL (ADDLconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL) x (NEGL y)) -((SHLL|SHRL|SARL) x (ANDLconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL) x y) -((SHLL|SHRL|SARL) x (NEGL (ANDLconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL) x (NEGL y)) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (ADDLconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x y) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGL (ADDLconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGL y)) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (ANDLconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x y) +((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGL (ANDLconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL|SHLXL|SHRXL|SARXL) x (NEGL y)) // Constant rotate instructions ((ADDQ|ORQ|XORQ) (SHLQconst x [c]) (SHRQconst x [d])) && d==64-c => (ROLQconst x [c]) @@ -856,9 +881,13 @@ // it in order to strip it out. (ORQ (SHLQ x y) (ANDQ (SHRQ x (NEG(Q|L) y)) (SBBQcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [63]) [-64])) [64])))) => (ROLQ x y) (ORQ (SHRQ x y) (ANDQ (SHLQ x (NEG(Q|L) y)) (SBBQcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [63]) [-64])) [64])))) => (RORQ x y) +(ORQ (SHLXQ x y) (ANDQ (SHRXQ x (NEG(Q|L) y)) (SBBQcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [63]) [-64])) [64])))) => (ROLQ x y) +(ORQ (SHRXQ x y) (ANDQ (SHLXQ x (NEG(Q|L) y)) (SBBQcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [63]) [-64])) [64])))) => (RORQ x y) (ORL (SHLL x y) (ANDL (SHRL x (NEG(Q|L) y)) (SBBLcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [31]) [-32])) [32])))) => (ROLL x y) (ORL (SHRL x y) (ANDL (SHLL x (NEG(Q|L) y)) (SBBLcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [31]) [-32])) [32])))) => (RORL x y) +(ORL (SHLXL x y) (ANDL (SHRXL x (NEG(Q|L) y)) (SBBLcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [31]) [-32])) [32])))) => (ROLL x y) +(ORL (SHRXL x y) (ANDL (SHLXL x (NEG(Q|L) y)) (SBBLcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [31]) [-32])) [32])))) => (RORL x y) // Help with rotate detection (CMPQconst (NEGQ (ADDQconst [-16] (ANDQconst [15] _))) [32]) => (FlagLT_ULT) @@ -873,6 +902,15 @@ (SHLL x (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [15]) [-16])))) && v.Type.Size() == 2 => (RORW x y) +(ORL (SHLXL x (AND(Q|L)const y [15])) + (ANDL (SHRW x (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [15]) [-16]))) + (SBBLcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [15]) [-16])) [16])))) + && v.Type.Size() == 2 + => (ROLW x y) +(ORL (SHRW x (AND(Q|L)const y [15])) + (SHLXL x (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [15]) [-16])))) + && v.Type.Size() == 2 + => (RORW x y) (ORL (SHLL x (AND(Q|L)const y [ 7])) (ANDL (SHRB x (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [ 7]) [ -8]))) @@ -883,6 +921,15 @@ (SHLL x (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [ 7]) [ -8])))) && v.Type.Size() == 1 => (RORB x y) +(ORL (SHLXL x (AND(Q|L)const y [ 7])) + (ANDL (SHRB x (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [ 7]) [ -8]))) + (SBBLcarrymask (CMP(Q|L)const (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [ 7]) [ -8])) [ 8])))) + && v.Type.Size() == 1 + => (ROLB x y) +(ORL (SHRB x (AND(Q|L)const y [ 7])) + (SHLXL x (NEG(Q|L) (ADD(Q|L)const (AND(Q|L)const y [ 7]) [ -8])))) + && v.Type.Size() == 1 + => (RORB x y) // rotate left negative = rotate right (ROLQ x (NEG(Q|L) y)) => (RORQ x y) @@ -916,6 +963,7 @@ // Multi-register shifts (ORQ (SH(R|L)Q lo bits) (SH(L|R)Q hi (NEGQ bits))) => (SH(R|L)DQ lo hi bits) +(ORQ (SH(R|L)XQ lo bits) (SH(L|R)XQ hi (NEGQ bits))) => (SH(R|L)DQ lo hi bits) // Note: the word and byte shifts keep the low 5 bits (not the low 4 or 3 bits) // because the x86 instructions are defined to use all 5 bits of the shift even @@ -2252,5 +2300,10 @@ && clobber(x0, x1, sh) => @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p1 mem) -(SHL(Q|L) l:(MOV(Q|L)load [off] {sym} ptr mem) x) && buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l) => (SHLX(Q|L)load [off] {sym} ptr x mem) -(SHR(Q|L) l:(MOV(Q|L)load [off] {sym} ptr mem) x) && buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l) => (SHRX(Q|L)load [off] {sym} ptr x mem) +(SARX(Q|L) l:(MOV(Q|L)load [off] {sym} ptr mem) x) && canMergeLoad(v, l) && clobber(l) => (SARX(Q|L)load [off] {sym} ptr x mem) +(SHLX(Q|L) l:(MOV(Q|L)load [off] {sym} ptr mem) x) && canMergeLoad(v, l) && clobber(l) => (SHLX(Q|L)load [off] {sym} ptr x mem) +(SHRX(Q|L) l:(MOV(Q|L)load [off] {sym} ptr mem) x) && canMergeLoad(v, l) && clobber(l) => (SHRX(Q|L)load [off] {sym} ptr x mem) + +((SHL|SHR|SAR)XQload [off] {sym} ptr (MOVQconst [c]) mem) => ((SHL|SHR|SAR)Qconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) +((SHL|SHR|SAR)XQload [off] {sym} ptr (MOVLconst [c]) mem) => ((SHL|SHR|SAR)Qconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) +((SHL|SHR|SAR)XLload [off] {sym} ptr (MOVLconst [c]) mem) => ((SHL|SHR|SAR)Lconst [int8(c&31)] (MOVLload [off] {sym} ptr mem)) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index d760d7d79e..fc42fa5e28 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -937,13 +937,41 @@ func init() { {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 + // indexed MOVBE loads + {name: "MOVBELloadidx1", argLength: 3, reg: gploadidx, commutative: true, asm: "MOVBEL", scale: 1, aux: "SymOff", typ: "UInt32", symEffect: "Read"}, // load and swap 4 bytes from arg0+arg1+auxint+aux. arg2=mem. Zero extend. + {name: "MOVBELloadidx4", argLength: 3, reg: gploadidx, asm: "MOVBEL", scale: 4, aux: "SymOff", typ: "UInt32", symEffect: "Read"}, // load and swap 4 bytes from arg0+4*arg1+auxint+aux. arg2=mem. Zero extend. + {name: "MOVBELloadidx8", argLength: 3, reg: gploadidx, asm: "MOVBEL", scale: 8, aux: "SymOff", typ: "UInt32", symEffect: "Read"}, // load and swap 4 bytes from arg0+8*arg1+auxint+aux. arg2=mem. Zero extend. + {name: "MOVBEQloadidx1", argLength: 3, reg: gploadidx, commutative: true, asm: "MOVBEQ", scale: 1, aux: "SymOff", typ: "UInt64", symEffect: "Read"}, // load and swap 8 bytes from arg0+arg1+auxint+aux. arg2=mem + {name: "MOVBEQloadidx8", argLength: 3, reg: gploadidx, asm: "MOVBEQ", scale: 8, aux: "SymOff", typ: "UInt64", symEffect: "Read"}, // load and swap 8 bytes from arg0+8*arg1+auxint+aux. arg2=mem + // indexed MOVBE stores + {name: "MOVBEWstoreidx1", argLength: 4, reg: gpstoreidx, commutative: true, asm: "MOVBEW", scale: 1, aux: "SymOff", symEffect: "Write"}, // swap and store 2 bytes in arg2 to arg0+arg1+auxint+aux. arg3=mem + {name: "MOVBEWstoreidx2", argLength: 4, reg: gpstoreidx, asm: "MOVBEW", scale: 2, aux: "SymOff", symEffect: "Write"}, // swap and store 2 bytes in arg2 to arg0+2*arg1+auxint+aux. arg3=mem + {name: "MOVBELstoreidx1", argLength: 4, reg: gpstoreidx, commutative: true, asm: "MOVBEL", scale: 1, aux: "SymOff", symEffect: "Write"}, // swap and store 4 bytes in arg2 to arg0+arg1+auxint+aux. arg3=mem + {name: "MOVBELstoreidx4", argLength: 4, reg: gpstoreidx, asm: "MOVBEL", scale: 4, aux: "SymOff", symEffect: "Write"}, // swap and store 4 bytes in arg2 to arg0+4*arg1+auxint+aux. arg3=mem + {name: "MOVBELstoreidx8", argLength: 4, reg: gpstoreidx, asm: "MOVBEL", scale: 8, aux: "SymOff", symEffect: "Write"}, // swap and store 4 bytes in arg2 to arg0+8*arg1+auxint+aux. arg3=mem + {name: "MOVBEQstoreidx1", argLength: 4, reg: gpstoreidx, commutative: true, asm: "MOVBEQ", scale: 1, aux: "SymOff", symEffect: "Write"}, // swap and store 8 bytes in arg2 to arg0+arg1+auxint+aux. arg3=mem + {name: "MOVBEQstoreidx8", argLength: 4, reg: gpstoreidx, asm: "MOVBEQ", scale: 8, aux: "SymOff", symEffect: "Write"}, // swap and store 8 bytes in arg2 to arg0+8*arg1+auxint+aux. arg3=mem // CPUID feature: BMI2. + {name: "SARXQ", argLength: 2, reg: gp21, asm: "SARXQ"}, // signed arg0 >> arg1, shift amount is mod 64 + {name: "SARXL", argLength: 2, reg: gp21, asm: "SARXL"}, // signed int32(arg0) >> arg1, shift amount is mod 32 + {name: "SHLXQ", argLength: 2, reg: gp21, asm: "SHLXQ"}, // arg0 << arg1, shift amount is mod 64 + {name: "SHLXL", argLength: 2, reg: gp21, asm: "SHLXL"}, // arg0 << arg1, shift amount is mod 32 + {name: "SHRXQ", argLength: 2, reg: gp21, asm: "SHRXQ"}, // unsigned arg0 >> arg1, shift amount is mod 64 + {name: "SHRXL", argLength: 2, reg: gp21, asm: "SHRXL"}, // unsigned uint32(arg0) >> arg1, shift amount is mod 32 + + {name: "SARXLload", argLength: 3, reg: gp21shxload, asm: "SARXL", aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // signed *(arg0+auxint+aux) >> arg1, arg2=mem, shift amount is mod 32 + {name: "SARXQload", argLength: 3, reg: gp21shxload, asm: "SARXQ", aux: "SymOff", typ: "Uint64", faultOnNilArg0: true, symEffect: "Read"}, // signed *(arg0+auxint+aux) >> arg1, arg2=mem, shift amount is mod 64 {name: "SHLXLload", argLength: 3, reg: gp21shxload, asm: "SHLXL", aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // *(arg0+auxint+aux) << arg1, arg2=mem, shift amount is mod 32 {name: "SHLXQload", argLength: 3, reg: gp21shxload, asm: "SHLXQ", aux: "SymOff", typ: "Uint64", faultOnNilArg0: true, symEffect: "Read"}, // *(arg0+auxint+aux) << arg1, arg2=mem, shift amount is mod 64 {name: "SHRXLload", argLength: 3, reg: gp21shxload, asm: "SHRXL", aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // unsigned *(arg0+auxint+aux) >> arg1, arg2=mem, shift amount is mod 32 {name: "SHRXQload", argLength: 3, reg: gp21shxload, asm: "SHRXQ", aux: "SymOff", typ: "Uint64", faultOnNilArg0: true, symEffect: "Read"}, // unsigned *(arg0+auxint+aux) >> arg1, arg2=mem, shift amount is mod 64 + {name: "SARXLloadidx1", argLength: 4, reg: gp21shxloadidx, asm: "SARXL", scale: 1, aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // signed *(arg0+1*arg1+auxint+aux) >> arg2, arg3=mem, shift amount is mod 32 + {name: "SARXLloadidx4", argLength: 4, reg: gp21shxloadidx, asm: "SARXL", scale: 4, aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // signed *(arg0+4*arg1+auxint+aux) >> arg2, arg3=mem, shift amount is mod 32 + {name: "SARXLloadidx8", argLength: 4, reg: gp21shxloadidx, asm: "SARXL", scale: 8, aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // signed *(arg0+8*arg1+auxint+aux) >> arg2, arg3=mem, shift amount is mod 32 + {name: "SARXQloadidx1", argLength: 4, reg: gp21shxloadidx, asm: "SARXQ", scale: 1, aux: "SymOff", typ: "Uint64", faultOnNilArg0: true, symEffect: "Read"}, // signed *(arg0+1*arg1+auxint+aux) >> arg2, arg3=mem, shift amount is mod 64 + {name: "SARXQloadidx8", argLength: 4, reg: gp21shxloadidx, asm: "SARXQ", scale: 8, aux: "SymOff", typ: "Uint64", faultOnNilArg0: true, symEffect: "Read"}, // signed *(arg0+8*arg1+auxint+aux) >> arg2, arg3=mem, shift amount is mod 64 {name: "SHLXLloadidx1", argLength: 4, reg: gp21shxloadidx, asm: "SHLXL", scale: 1, aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // *(arg0+1*arg1+auxint+aux) << arg2, arg3=mem, shift amount is mod 32 {name: "SHLXLloadidx4", argLength: 4, reg: gp21shxloadidx, asm: "SHLXL", scale: 4, aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // *(arg0+4*arg1+auxint+aux) << arg2, arg3=mem, shift amount is mod 32 {name: "SHLXLloadidx8", argLength: 4, reg: gp21shxloadidx, asm: "SHLXL", scale: 8, aux: "SymOff", typ: "Uint32", faultOnNilArg0: true, symEffect: "Read"}, // *(arg0+8*arg1+auxint+aux) << arg2, arg3=mem, shift amount is mod 32 @@ -973,6 +1001,12 @@ func init() { {name: "NEF", controls: 1}, {name: "ORD", controls: 1}, // FP, ordered comparison (parity zero) {name: "NAN", controls: 1}, // FP, unordered comparison (parity one) + + // JUMPTABLE implements jump tables. + // Aux is the symbol (an *obj.LSym) for the jump table. + // control[0] is the index into the jump table. + // control[1] is the address of the jump table (the address of the symbol stored in Aux). + {name: "JUMPTABLE", controls: 2, aux: "Sym"}, } archs = append(archs, arch{ diff --git a/src/cmd/compile/internal/ssa/gen/generic.rules b/src/cmd/compile/internal/ssa/gen/generic.rules index 6dbe9b47d0..d5cc107fab 100644 --- a/src/cmd/compile/internal/ssa/gen/generic.rules +++ b/src/cmd/compile/internal/ssa/gen/generic.rules @@ -515,15 +515,18 @@ // simplifications (Or(64|32|16|8) x x) => x -(Or(64|32|16|8) (Const(64|32|16|8) [0]) x) => x +(Or(64|32|16|8) (Const(64|32|16|8) [0]) x) => x (Or(64|32|16|8) (Const(64|32|16|8) [-1]) _) => (Const(64|32|16|8) [-1]) +(Or(64|32|16|8) (Com(64|32|16|8) x) x) => (Const(64|32|16|8) [-1]) (And(64|32|16|8) x x) => x (And(64|32|16|8) (Const(64|32|16|8) [-1]) x) => x -(And(64|32|16|8) (Const(64|32|16|8) [0]) _) => (Const(64|32|16|8) [0]) +(And(64|32|16|8) (Const(64|32|16|8) [0]) _) => (Const(64|32|16|8) [0]) +(And(64|32|16|8) (Com(64|32|16|8) x) x) => (Const(64|32|16|8) [0]) (Xor(64|32|16|8) x x) => (Const(64|32|16|8) [0]) (Xor(64|32|16|8) (Const(64|32|16|8) [0]) x) => x +(Xor(64|32|16|8) (Com(64|32|16|8) x) x) => (Const(64|32|16|8) [-1]) (Add(64|32|16|8) (Const(64|32|16|8) [0]) x) => x (Sub(64|32|16|8) x x) => (Const(64|32|16|8) [0]) @@ -533,6 +536,13 @@ (Com(64|32|16|8) (Const(64|32|16|8) [c])) => (Const(64|32|16|8) [^c]) (Neg(64|32|16|8) (Sub(64|32|16|8) x y)) => (Sub(64|32|16|8) y x) +(Add(64|32|16|8) x (Neg(64|32|16|8) y)) => (Sub(64|32|16|8) x y) + +(Xor(64|32|16|8) (Const(64|32|16|8) [-1]) x) => (Com(64|32|16|8) x) + +(Sub(64|32|16|8) (Neg(64|32|16|8) x) (Com(64|32|16|8) x)) => (Const(64|32|16|8) [1]) +(Sub(64|32|16|8) (Com(64|32|16|8) x) (Neg(64|32|16|8) x)) => (Const(64|32|16|8) [-1]) +(Add(64|32|16|8) (Com(64|32|16|8) x) x) => (Const(64|32|16|8) [-1]) // ^(x-1) == ^x+1 == -x (Add(64|32|16|8) (Const(64|32|16|8) [1]) (Com(64|32|16|8) x)) => (Neg(64|32|16|8) x) diff --git a/src/cmd/compile/internal/ssa/gen/genericOps.go b/src/cmd/compile/internal/ssa/gen/genericOps.go index 4f133b1ff6..e04b7db6e7 100644 --- a/src/cmd/compile/internal/ssa/gen/genericOps.go +++ b/src/cmd/compile/internal/ssa/gen/genericOps.go @@ -639,12 +639,13 @@ var genericOps = []opData{ // First [] [always, never] var genericBlocks = []blockData{ - {name: "Plain"}, // a single successor - {name: "If", controls: 1}, // if Controls[0] goto Succs[0] else goto Succs[1] - {name: "Defer", controls: 1}, // Succs[0]=defer queued, Succs[1]=defer recovered. Controls[0] is call op (of memory type) - {name: "Ret", controls: 1}, // no successors, Controls[0] value is memory result - {name: "RetJmp", controls: 1}, // no successors, Controls[0] value is a tail call - {name: "Exit", controls: 1}, // no successors, Controls[0] value generates a panic + {name: "Plain"}, // a single successor + {name: "If", controls: 1}, // if Controls[0] goto Succs[0] else goto Succs[1] + {name: "Defer", controls: 1}, // Succs[0]=defer queued, Succs[1]=defer recovered. Controls[0] is call op (of memory type) + {name: "Ret", controls: 1}, // no successors, Controls[0] value is memory result + {name: "RetJmp", controls: 1}, // no successors, Controls[0] value is a tail call + {name: "Exit", controls: 1}, // no successors, Controls[0] value generates a panic + {name: "JumpTable", controls: 1}, // multiple successors, the integer Controls[0] selects which one // transient block state used for dead code removal {name: "First"}, // 2 successors, always takes the first one (second is dead) diff --git a/src/cmd/compile/internal/ssa/gen/rulegen.go b/src/cmd/compile/internal/ssa/gen/rulegen.go index fe8db4ed1f..0f7e970372 100644 --- a/src/cmd/compile/internal/ssa/gen/rulegen.go +++ b/src/cmd/compile/internal/ssa/gen/rulegen.go @@ -1838,6 +1838,8 @@ func (op opData) auxIntType() string { // auxType returns the Go type that this block should store in its aux field. func (b blockData) auxType() string { switch b.aux { + case "Sym": + return "Sym" case "S390XCCMask", "S390XCCMaskInt8", "S390XCCMaskUint8": return "s390x.CCMask" case "S390XRotateParams": diff --git a/src/cmd/compile/internal/ssa/location.go b/src/cmd/compile/internal/ssa/location.go index d69db404ed..00aea87936 100644 --- a/src/cmd/compile/internal/ssa/location.go +++ b/src/cmd/compile/internal/ssa/location.go @@ -46,19 +46,19 @@ func (r *Register) GCNum() int16 { // variable that has been decomposed into multiple stack slots. // As an example, a string could have the following configurations: // -// stack layout LocalSlots +// stack layout LocalSlots // -// Optimizations are disabled. s is on the stack and represented in its entirety. -// [ ------- s string ---- ] { N: s, Type: string, Off: 0 } +// Optimizations are disabled. s is on the stack and represented in its entirety. +// [ ------- s string ---- ] { N: s, Type: string, Off: 0 } // -// s was not decomposed, but the SSA operates on its parts individually, so -// there is a LocalSlot for each of its fields that points into the single stack slot. -// [ ------- s string ---- ] { N: s, Type: *uint8, Off: 0 }, {N: s, Type: int, Off: 8} +// s was not decomposed, but the SSA operates on its parts individually, so +// there is a LocalSlot for each of its fields that points into the single stack slot. +// [ ------- s string ---- ] { N: s, Type: *uint8, Off: 0 }, {N: s, Type: int, Off: 8} // -// s was decomposed. Each of its fields is in its own stack slot and has its own LocalSLot. -// [ ptr *uint8 ] [ len int] { N: ptr, Type: *uint8, Off: 0, SplitOf: parent, SplitOffset: 0}, -// { N: len, Type: int, Off: 0, SplitOf: parent, SplitOffset: 8} -// parent = &{N: s, Type: string} +// s was decomposed. Each of its fields is in its own stack slot and has its own LocalSLot. +// [ ptr *uint8 ] [ len int] { N: ptr, Type: *uint8, Off: 0, SplitOf: parent, SplitOffset: 0}, +// { N: len, Type: int, Off: 0, SplitOf: parent, SplitOffset: 8} +// parent = &{N: s, Type: string} type LocalSlot struct { N *ir.Name // an ONAME *ir.Name representing a stack location. Type *types.Type // type of slot diff --git a/src/cmd/compile/internal/ssa/loopbce.go b/src/cmd/compile/internal/ssa/loopbce.go index 206aab2c5e..dd63541771 100644 --- a/src/cmd/compile/internal/ssa/loopbce.go +++ b/src/cmd/compile/internal/ssa/loopbce.go @@ -31,9 +31,10 @@ type indVar struct { // parseIndVar checks whether the SSA value passed as argument is a valid induction // variable, and, if so, extracts: -// * the minimum bound -// * the increment value -// * the "next" value (SSA value that is Phi'd into the induction variable every loop) +// - the minimum bound +// - the increment value +// - the "next" value (SSA value that is Phi'd into the induction variable every loop) +// // Currently, we detect induction variables that match (Phi min nxt), // with nxt being (Add inc ind). // If it can't parse the induction variable correctly, it returns (nil, nil, nil). @@ -66,19 +67,18 @@ func parseIndVar(ind *Value) (min, inc, nxt *Value) { // // Look for variables and blocks that satisfy the following // -// loop: -// ind = (Phi min nxt), -// if ind < max -// then goto enter_loop -// else goto exit_loop +// loop: +// ind = (Phi min nxt), +// if ind < max +// then goto enter_loop +// else goto exit_loop // -// enter_loop: -// do something -// nxt = inc + ind -// goto loop -// -// exit_loop: +// enter_loop: +// do something +// nxt = inc + ind +// goto loop // +// exit_loop: // // TODO: handle 32 bit operations func findIndVar(f *Func) []indVar { diff --git a/src/cmd/compile/internal/ssa/looprotate.go b/src/cmd/compile/internal/ssa/looprotate.go index 35010a78d8..2eefda1c8b 100644 --- a/src/cmd/compile/internal/ssa/looprotate.go +++ b/src/cmd/compile/internal/ssa/looprotate.go @@ -8,19 +8,19 @@ package ssa // to loops with a check-loop-condition-at-end. // This helps loops avoid extra unnecessary jumps. // -// loop: -// CMPQ ... -// JGE exit -// ... -// JMP loop -// exit: +// loop: +// CMPQ ... +// JGE exit +// ... +// JMP loop +// exit: // -// JMP entry -// loop: -// ... -// entry: -// CMPQ ... -// JLT loop +// JMP entry +// loop: +// ... +// entry: +// CMPQ ... +// JLT loop func loopRotate(f *Func) { loopnest := f.loopnest() if loopnest.hasIrreducible { diff --git a/src/cmd/compile/internal/ssa/magic.go b/src/cmd/compile/internal/ssa/magic.go index 93f8801bce..e903d92bb6 100644 --- a/src/cmd/compile/internal/ssa/magic.go +++ b/src/cmd/compile/internal/ssa/magic.go @@ -110,7 +110,8 @@ type umagicData struct { // umagic computes the constants needed to strength reduce unsigned n-bit divides by the constant uint64(c). // The return values satisfy for all 0 <= x < 2^n -// floor(x / uint64(c)) = x * (m + 2^n) >> (n+s) +// +// floor(x / uint64(c)) = x * (m + 2^n) >> (n+s) func umagic(n uint, c int64) umagicData { // Convert from ConstX auxint values to the real uint64 constant they represent. d := uint64(c) << (64 - n) >> (64 - n) @@ -183,7 +184,8 @@ type smagicData struct { // magic computes the constants needed to strength reduce signed n-bit divides by the constant c. // Must have c>0. // The return values satisfy for all -2^(n-1) <= x < 2^(n-1) -// trunc(x / c) = x * m >> (n+s) + (x < 0 ? 1 : 0) +// +// trunc(x / c) = x * m >> (n+s) + (x < 0 ? 1 : 0) func smagic(n uint, c int64) smagicData { C := new(big.Int).SetInt64(c) s := C.BitLen() - 1 diff --git a/src/cmd/compile/internal/ssa/op.go b/src/cmd/compile/internal/ssa/op.go index a1835dcd30..a3e8dcd2f6 100644 --- a/src/cmd/compile/internal/ssa/op.go +++ b/src/cmd/compile/internal/ssa/op.go @@ -391,9 +391,9 @@ const ( // A Sym represents a symbolic offset from a base register. // Currently a Sym can be one of 3 things: -// - a *gc.Node, for an offset from SP (the stack pointer) -// - a *obj.LSym, for an offset from SB (the global pointer) -// - nil, for no offset +// - a *gc.Node, for an offset from SP (the stack pointer) +// - a *obj.LSym, for an offset from SB (the global pointer) +// - nil, for no offset type Sym interface { CanBeAnSSASym() CanBeAnSSAAux() @@ -479,12 +479,13 @@ const ( ) // boundsAPI determines which register arguments a bounds check call should use. For an [a:b:c] slice, we do: -// CMPQ c, cap -// JA fail1 -// CMPQ b, c -// JA fail2 -// CMPQ a, b -// JA fail3 +// +// CMPQ c, cap +// JA fail1 +// CMPQ b, c +// JA fail2 +// CMPQ a, b +// JA fail3 // // fail1: CALL panicSlice3Acap (c, cap) // fail2: CALL panicSlice3B (b, c) diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 005a033a40..0357fdb12a 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -50,6 +50,7 @@ const ( BlockAMD64NEF BlockAMD64ORD BlockAMD64NAN + BlockAMD64JUMPTABLE BlockARMEQ BlockARMNE @@ -149,6 +150,7 @@ const ( BlockRet BlockRetJmp BlockExit + BlockJumpTable BlockFirst ) @@ -172,22 +174,23 @@ var blockString = [...]string{ Block386ORD: "ORD", Block386NAN: "NAN", - BlockAMD64EQ: "EQ", - BlockAMD64NE: "NE", - BlockAMD64LT: "LT", - BlockAMD64LE: "LE", - BlockAMD64GT: "GT", - BlockAMD64GE: "GE", - BlockAMD64OS: "OS", - BlockAMD64OC: "OC", - BlockAMD64ULT: "ULT", - BlockAMD64ULE: "ULE", - BlockAMD64UGT: "UGT", - BlockAMD64UGE: "UGE", - BlockAMD64EQF: "EQF", - BlockAMD64NEF: "NEF", - BlockAMD64ORD: "ORD", - BlockAMD64NAN: "NAN", + BlockAMD64EQ: "EQ", + BlockAMD64NE: "NE", + BlockAMD64LT: "LT", + BlockAMD64LE: "LE", + BlockAMD64GT: "GT", + BlockAMD64GE: "GE", + BlockAMD64OS: "OS", + BlockAMD64OC: "OC", + BlockAMD64ULT: "ULT", + BlockAMD64ULE: "ULE", + BlockAMD64UGT: "UGT", + BlockAMD64UGE: "UGE", + BlockAMD64EQF: "EQF", + BlockAMD64NEF: "NEF", + BlockAMD64ORD: "ORD", + BlockAMD64NAN: "NAN", + BlockAMD64JUMPTABLE: "JUMPTABLE", BlockARMEQ: "EQ", BlockARMNE: "NE", @@ -281,13 +284,14 @@ var blockString = [...]string{ BlockS390XCLIJ: "CLIJ", BlockS390XCLGIJ: "CLGIJ", - BlockPlain: "Plain", - BlockIf: "If", - BlockDefer: "Defer", - BlockRet: "Ret", - BlockRetJmp: "RetJmp", - BlockExit: "Exit", - BlockFirst: "First", + BlockPlain: "Plain", + BlockIf: "If", + BlockDefer: "Defer", + BlockRet: "Ret", + BlockRetJmp: "RetJmp", + BlockExit: "Exit", + BlockJumpTable: "JumpTable", + BlockFirst: "First", } func (k BlockKind) String() string { return blockString[k] } @@ -1050,10 +1054,35 @@ const ( OpAMD64MOVBELstore OpAMD64MOVBEQload OpAMD64MOVBEQstore + OpAMD64MOVBELloadidx1 + OpAMD64MOVBELloadidx4 + OpAMD64MOVBELloadidx8 + OpAMD64MOVBEQloadidx1 + OpAMD64MOVBEQloadidx8 + OpAMD64MOVBEWstoreidx1 + OpAMD64MOVBEWstoreidx2 + OpAMD64MOVBELstoreidx1 + OpAMD64MOVBELstoreidx4 + OpAMD64MOVBELstoreidx8 + OpAMD64MOVBEQstoreidx1 + OpAMD64MOVBEQstoreidx8 + OpAMD64SARXQ + OpAMD64SARXL + OpAMD64SHLXQ + OpAMD64SHLXL + OpAMD64SHRXQ + OpAMD64SHRXL + OpAMD64SARXLload + OpAMD64SARXQload OpAMD64SHLXLload OpAMD64SHLXQload OpAMD64SHRXLload OpAMD64SHRXQload + OpAMD64SARXLloadidx1 + OpAMD64SARXLloadidx4 + OpAMD64SARXLloadidx8 + OpAMD64SARXQloadidx1 + OpAMD64SARXQloadidx8 OpAMD64SHLXLloadidx1 OpAMD64SHLXLloadidx4 OpAMD64SHLXLloadidx8 @@ -13910,6 +13939,319 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "MOVBELloadidx1", + auxType: auxSymOff, + argLen: 3, + commutative: true, + symEffect: SymRead, + asm: x86.AMOVBEL, + scale: 1, + 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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "MOVBELloadidx4", + auxType: auxSymOff, + argLen: 3, + symEffect: SymRead, + asm: x86.AMOVBEL, + scale: 4, + 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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "MOVBELloadidx8", + auxType: auxSymOff, + argLen: 3, + symEffect: SymRead, + asm: x86.AMOVBEL, + scale: 8, + 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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "MOVBEQloadidx1", + auxType: auxSymOff, + argLen: 3, + commutative: true, + symEffect: SymRead, + asm: x86.AMOVBEQ, + scale: 1, + 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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "MOVBEQloadidx8", + auxType: auxSymOff, + argLen: 3, + symEffect: SymRead, + asm: x86.AMOVBEQ, + scale: 8, + 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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "MOVBEWstoreidx1", + auxType: auxSymOff, + argLen: 4, + commutative: true, + symEffect: SymWrite, + asm: x86.AMOVBEW, + scale: 1, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 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: "MOVBEWstoreidx2", + auxType: auxSymOff, + argLen: 4, + symEffect: SymWrite, + asm: x86.AMOVBEW, + scale: 2, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 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: "MOVBELstoreidx1", + auxType: auxSymOff, + argLen: 4, + commutative: true, + symEffect: SymWrite, + asm: x86.AMOVBEL, + scale: 1, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 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: "MOVBELstoreidx4", + auxType: auxSymOff, + argLen: 4, + symEffect: SymWrite, + asm: x86.AMOVBEL, + scale: 4, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 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: "MOVBELstoreidx8", + auxType: auxSymOff, + argLen: 4, + symEffect: SymWrite, + asm: x86.AMOVBEL, + scale: 8, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 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: "MOVBEQstoreidx1", + auxType: auxSymOff, + argLen: 4, + commutative: true, + symEffect: SymWrite, + asm: x86.AMOVBEQ, + scale: 1, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 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: "MOVBEQstoreidx8", + auxType: auxSymOff, + argLen: 4, + symEffect: SymWrite, + asm: x86.AMOVBEQ, + scale: 8, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49151}, // AX CX DX BX SP BP SI DI R8 R9 R10 R11 R12 R13 R15 + {2, 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: "SARXQ", + argLen: 2, + asm: x86.ASARXQ, + reg: regInfo{ + inputs: []inputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SARXL", + argLen: 2, + asm: x86.ASARXL, + reg: regInfo{ + inputs: []inputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SHLXQ", + argLen: 2, + asm: x86.ASHLXQ, + reg: regInfo{ + inputs: []inputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SHLXL", + argLen: 2, + asm: x86.ASHLXL, + reg: regInfo{ + inputs: []inputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SHRXQ", + argLen: 2, + asm: x86.ASHRXQ, + reg: regInfo{ + inputs: []inputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SHRXL", + argLen: 2, + asm: x86.ASHRXL, + reg: regInfo{ + inputs: []inputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {1, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SARXLload", + auxType: auxSymOff, + argLen: 3, + faultOnNilArg0: true, + symEffect: SymRead, + asm: x86.ASARXL, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49135}, // AX CX DX BX 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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SARXQload", + auxType: auxSymOff, + argLen: 3, + faultOnNilArg0: true, + symEffect: SymRead, + asm: x86.ASARXQ, + reg: regInfo{ + inputs: []inputInfo{ + {1, 49135}, // AX CX DX BX 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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, { name: "SHLXLload", auxType: auxSymOff, @@ -13978,6 +14320,101 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + name: "SARXLloadidx1", + auxType: auxSymOff, + argLen: 4, + faultOnNilArg0: true, + symEffect: SymRead, + asm: x86.ASARXL, + scale: 1, + reg: regInfo{ + inputs: []inputInfo{ + {2, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SARXLloadidx4", + auxType: auxSymOff, + argLen: 4, + faultOnNilArg0: true, + symEffect: SymRead, + asm: x86.ASARXL, + scale: 4, + reg: regInfo{ + inputs: []inputInfo{ + {2, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SARXLloadidx8", + auxType: auxSymOff, + argLen: 4, + faultOnNilArg0: true, + symEffect: SymRead, + asm: x86.ASARXL, + scale: 8, + reg: regInfo{ + inputs: []inputInfo{ + {2, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SARXQloadidx1", + auxType: auxSymOff, + argLen: 4, + faultOnNilArg0: true, + symEffect: SymRead, + asm: x86.ASARXQ, + scale: 1, + reg: regInfo{ + inputs: []inputInfo{ + {2, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, + { + name: "SARXQloadidx8", + auxType: auxSymOff, + argLen: 4, + faultOnNilArg0: true, + symEffect: SymRead, + asm: x86.ASARXQ, + scale: 8, + reg: regInfo{ + inputs: []inputInfo{ + {2, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + {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 + }, + outputs: []outputInfo{ + {0, 49135}, // AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R15 + }, + }, + }, { name: "SHLXLloadidx1", auxType: auxSymOff, diff --git a/src/cmd/compile/internal/ssa/phielim.go b/src/cmd/compile/internal/ssa/phielim.go index 761cb7a392..4fc942375f 100644 --- a/src/cmd/compile/internal/ssa/phielim.go +++ b/src/cmd/compile/internal/ssa/phielim.go @@ -8,13 +8,19 @@ package ssa // A phi is redundant if its arguments are all equal. For // purposes of counting, ignore the phi itself. Both of // these phis are redundant: -// v = phi(x,x,x) -// v = phi(x,v,x,v) +// +// v = phi(x,x,x) +// v = phi(x,v,x,v) +// // We repeat this process to also catch situations like: -// v = phi(x, phi(x, x), phi(x, v)) +// +// v = phi(x, phi(x, x), phi(x, v)) +// // TODO: Can we also simplify cases like: -// v = phi(v, w, x) -// w = phi(v, w, x) +// +// v = phi(v, w, x) +// w = phi(v, w, x) +// // and would that be useful? func phielim(f *Func) { for { diff --git a/src/cmd/compile/internal/ssa/phiopt.go b/src/cmd/compile/internal/ssa/phiopt.go index 0357442ae9..037845eacf 100644 --- a/src/cmd/compile/internal/ssa/phiopt.go +++ b/src/cmd/compile/internal/ssa/phiopt.go @@ -7,20 +7,22 @@ package ssa // phiopt eliminates boolean Phis based on the previous if. // // Main use case is to transform: -// x := false -// if b { -// x = true -// } +// +// x := false +// if b { +// x = true +// } +// // into x = b. // // In SSA code this appears as // -// b0 -// If b -> b1 b2 -// b1 -// Plain -> b2 -// b2 -// x = (OpPhi (ConstBool [true]) (ConstBool [false])) +// b0 +// If b -> b1 b2 +// b1 +// Plain -> b2 +// b2 +// x = (OpPhi (ConstBool [true]) (ConstBool [false])) // // In this case we can replace x with a copy of b. func phiopt(f *Func) { diff --git a/src/cmd/compile/internal/ssa/poset.go b/src/cmd/compile/internal/ssa/poset.go index 200106e66d..a3b4f0fea4 100644 --- a/src/cmd/compile/internal/ssa/poset.go +++ b/src/cmd/compile/internal/ssa/poset.go @@ -140,11 +140,11 @@ type posetNode struct { // to record that A= w { -// newR := r & (eq|gt) -// } -// if v != w { -// newR := r & (lt|gt) -// } +// if v < w { +// newR := r & lt +// } +// if v >= w { +// newR := r & (eq|gt) +// } +// if v != w { +// newR := r & (lt|gt) +// } type relation uint const ( @@ -746,19 +750,19 @@ func (ft *factsTable) cleanup(f *Func) { // By far, the most common redundant pair are generated by bounds checking. // For example for the code: // -// a[i] = 4 -// foo(a[i]) +// a[i] = 4 +// foo(a[i]) // // The compiler will generate the following code: // -// if i >= len(a) { -// panic("not in bounds") -// } -// a[i] = 4 -// if i >= len(a) { -// panic("not in bounds") -// } -// foo(a[i]) +// if i >= len(a) { +// panic("not in bounds") +// } +// a[i] = 4 +// if i >= len(a) { +// panic("not in bounds") +// } +// foo(a[i]) // // The second comparison i >= len(a) is clearly redundant because if the // else branch of the first comparison is executed, we already know that i < len(a). @@ -940,20 +944,31 @@ func prove(f *Func) { // getBranch returns the range restrictions added by p // when reaching b. p is the immediate dominator of b. func getBranch(sdom SparseTree, p *Block, b *Block) branch { - if p == nil || p.Kind != BlockIf { + if p == nil { return unknown } - // If p and p.Succs[0] are dominators it means that every path - // from entry to b passes through p and p.Succs[0]. We care that - // no path from entry to b passes through p.Succs[1]. If p.Succs[0] - // has one predecessor then (apart from the degenerate case), - // there is no path from entry that can reach b through p.Succs[1]. - // TODO: how about p->yes->b->yes, i.e. a loop in yes. - if sdom.IsAncestorEq(p.Succs[0].b, b) && len(p.Succs[0].b.Preds) == 1 { - return positive - } - if sdom.IsAncestorEq(p.Succs[1].b, b) && len(p.Succs[1].b.Preds) == 1 { - return negative + switch p.Kind { + case BlockIf: + // If p and p.Succs[0] are dominators it means that every path + // from entry to b passes through p and p.Succs[0]. We care that + // no path from entry to b passes through p.Succs[1]. If p.Succs[0] + // has one predecessor then (apart from the degenerate case), + // there is no path from entry that can reach b through p.Succs[1]. + // TODO: how about p->yes->b->yes, i.e. a loop in yes. + if sdom.IsAncestorEq(p.Succs[0].b, b) && len(p.Succs[0].b.Preds) == 1 { + return positive + } + if sdom.IsAncestorEq(p.Succs[1].b, b) && len(p.Succs[1].b.Preds) == 1 { + return negative + } + case BlockJumpTable: + // TODO: this loop can lead to quadratic behavior, as + // getBranch can be called len(p.Succs) times. + for i, e := range p.Succs { + if sdom.IsAncestorEq(e.b, b) && len(e.b.Preds) == 1 { + return jumpTable0 + branch(i) + } + } } return unknown } @@ -984,11 +999,36 @@ func addIndVarRestrictions(ft *factsTable, b *Block, iv indVar) { // branching from Block b in direction br. func addBranchRestrictions(ft *factsTable, b *Block, br branch) { c := b.Controls[0] - switch br { - case negative: + switch { + case br == negative: addRestrictions(b, ft, boolean, nil, c, eq) - case positive: + case br == positive: addRestrictions(b, ft, boolean, nil, c, lt|gt) + case br >= jumpTable0: + idx := br - jumpTable0 + val := int64(idx) + if v, off := isConstDelta(c); v != nil { + // Establish the bound on the underlying value we're switching on, + // not on the offset-ed value used as the jump table index. + c = v + val -= off + } + old, ok := ft.limits[c.ID] + if !ok { + old = noLimit + } + ft.limitStack = append(ft.limitStack, limitFact{c.ID, old}) + if val < old.min || val > old.max || uint64(val) < old.umin || uint64(val) > old.umax { + ft.unsat = true + if b.Func.pass.debug > 2 { + b.Func.Warnl(b.Pos, "block=%s outedge=%d %s=%d unsat", b, idx, c, val) + } + } else { + ft.limits[c.ID] = limit{val, val, uint64(val), uint64(val)} + if b.Func.pass.debug > 2 { + b.Func.Warnl(b.Pos, "block=%s outedge=%d %s=%d", b, idx, c, val) + } + } default: panic("unknown branch") } @@ -1343,10 +1383,14 @@ func removeBranch(b *Block, branch branch) { // attempt to preserve statement marker. b.Pos = b.Pos.WithIsStmt() } - b.Kind = BlockFirst - b.ResetControls() - if branch == positive { - b.swapSuccessors() + if branch == positive || branch == negative { + b.Kind = BlockFirst + b.ResetControls() + if branch == positive { + b.swapSuccessors() + } + } else { + // TODO: figure out how to remove an entry from a jump table } } diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index eb8fa0c02a..4d615a064d 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -5,6 +5,7 @@ package ssa import ( + "cmd/compile/internal/base" "cmd/compile/internal/logopt" "cmd/compile/internal/types" "cmd/internal/obj" @@ -962,8 +963,9 @@ found: // clobber invalidates values. Returns true. // clobber is used by rewrite rules to: -// A) make sure the values are really dead and never used again. -// B) decrement use counts of the values' args. +// +// A) make sure the values are really dead and never used again. +// B) decrement use counts of the values' args. func clobber(vv ...*Value) bool { for _, v := range vv { v.reset(OpInvalid) @@ -985,7 +987,9 @@ func clobberIfDead(v *Value) bool { // noteRule is an easy way to track if a rule is matched when writing // new ones. Make the rule of interest also conditional on -// noteRule("note to self: rule of interest matched") +// +// noteRule("note to self: rule of interest matched") +// // and that message will print when the rule matches. func noteRule(s string) bool { fmt.Println(s) @@ -1789,9 +1793,11 @@ func sequentialAddresses(x, y *Value, n int64) bool { // We happen to match the semantics to those of arm/arm64. // Note that these semantics differ from x86: the carry flag has the opposite // sense on a subtraction! -// On amd64, C=1 represents a borrow, e.g. SBB on amd64 does x - y - C. -// On arm64, C=0 represents a borrow, e.g. SBC on arm64 does x - y - ^C. -// (because it does x + ^y + C). +// +// On amd64, C=1 represents a borrow, e.g. SBB on amd64 does x - y - C. +// On arm64, C=0 represents a borrow, e.g. SBC on arm64 does x - y - ^C. +// (because it does x + ^y + C). +// // See https://en.wikipedia.org/wiki/Carry_flag#Vs._borrow_flag type flagConstant uint8 @@ -1949,3 +1955,9 @@ func logicFlags32(x int32) flagConstant { fcb.N = x < 0 return fcb.encode() } + +func makeJumpTableSym(b *Block) *obj.LSym { + s := base.Ctxt.Lookup(fmt.Sprintf("%s.jump%d", b.Func.fe.LSym(), b.ID)) + s.Set(obj.AttrDuplicateOK, true) + return s +} diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index addfaaa3a8..36e69781a5 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -382,6 +382,14 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpAMD64SARW(v) case OpAMD64SARWconst: return rewriteValueAMD64_OpAMD64SARWconst(v) + case OpAMD64SARXL: + return rewriteValueAMD64_OpAMD64SARXL(v) + case OpAMD64SARXLload: + return rewriteValueAMD64_OpAMD64SARXLload(v) + case OpAMD64SARXQ: + return rewriteValueAMD64_OpAMD64SARXQ(v) + case OpAMD64SARXQload: + return rewriteValueAMD64_OpAMD64SARXQload(v) case OpAMD64SBBLcarrymask: return rewriteValueAMD64_OpAMD64SBBLcarrymask(v) case OpAMD64SBBQ: @@ -438,6 +446,14 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpAMD64SHLQ(v) case OpAMD64SHLQconst: return rewriteValueAMD64_OpAMD64SHLQconst(v) + case OpAMD64SHLXL: + return rewriteValueAMD64_OpAMD64SHLXL(v) + case OpAMD64SHLXLload: + return rewriteValueAMD64_OpAMD64SHLXLload(v) + case OpAMD64SHLXQ: + return rewriteValueAMD64_OpAMD64SHLXQ(v) + case OpAMD64SHLXQload: + return rewriteValueAMD64_OpAMD64SHLXQload(v) case OpAMD64SHRB: return rewriteValueAMD64_OpAMD64SHRB(v) case OpAMD64SHRBconst: @@ -454,6 +470,14 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpAMD64SHRW(v) case OpAMD64SHRWconst: return rewriteValueAMD64_OpAMD64SHRWconst(v) + case OpAMD64SHRXL: + return rewriteValueAMD64_OpAMD64SHRXL(v) + case OpAMD64SHRXLload: + return rewriteValueAMD64_OpAMD64SHRXLload(v) + case OpAMD64SHRXQ: + return rewriteValueAMD64_OpAMD64SHRXQ(v) + case OpAMD64SHRXQload: + return rewriteValueAMD64_OpAMD64SHRXQload(v) case OpAMD64SUBL: return rewriteValueAMD64_OpAMD64SUBL(v) case OpAMD64SUBLconst: @@ -2700,6 +2724,29 @@ func rewriteValueAMD64_OpAMD64ANDL(v *Value) bool { } break } + // match: (ANDL (NOTL (SHLXL (MOVLconst [1]) y)) x) + // result: (BTRL x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64NOTL { + continue + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAMD64SHLXL { + continue + } + y := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_0_0_0.AuxInt) != 1 { + continue + } + x := v_1 + v.reset(OpAMD64BTRL) + v.AddArg2(x, y) + return true + } + break + } // match: (ANDL (MOVLconst [c]) x) // cond: isUint32PowerOfTwo(int64(^c)) && uint64(^c) >= 128 // result: (BTRLconst [int8(log32(^c))] x) @@ -3117,6 +3164,22 @@ func rewriteValueAMD64_OpAMD64ANDNL(v *Value) bool { v.AddArg2(x, y) return true } + // match: (ANDNL x (SHLXL (MOVLconst [1]) y)) + // result: (BTRL x y) + for { + x := v_0 + if v_1.Op != OpAMD64SHLXL { + break + } + y := v_1.Args[1] + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_1_0.AuxInt) != 1 { + break + } + v.reset(OpAMD64BTRL) + v.AddArg2(x, y) + return true + } return false } func rewriteValueAMD64_OpAMD64ANDNQ(v *Value) bool { @@ -3138,6 +3201,22 @@ func rewriteValueAMD64_OpAMD64ANDNQ(v *Value) bool { v.AddArg2(x, y) return true } + // match: (ANDNQ x (SHLXQ (MOVQconst [1]) y)) + // result: (BTRQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64SHLXQ { + break + } + y := v_1.Args[1] + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_1_0.AuxInt) != 1 { + break + } + v.reset(OpAMD64BTRQ) + v.AddArg2(x, y) + return true + } return false } func rewriteValueAMD64_OpAMD64ANDQ(v *Value) bool { @@ -3166,6 +3245,29 @@ func rewriteValueAMD64_OpAMD64ANDQ(v *Value) bool { } break } + // match: (ANDQ (NOTQ (SHLXQ (MOVQconst [1]) y)) x) + // result: (BTRQ x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64NOTQ { + continue + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAMD64SHLXQ { + continue + } + y := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_0_0_0.AuxInt) != 1 { + continue + } + x := v_1 + v.reset(OpAMD64BTRQ) + v.AddArg2(x, y) + return true + } + break + } // match: (ANDQ (MOVQconst [c]) x) // cond: isUint64PowerOfTwo(^c) && uint64(^c) >= 128 // result: (BTRQconst [int8(log64(^c))] x) @@ -3869,6 +3971,22 @@ func rewriteValueAMD64_OpAMD64BTLconst(v *Value) bool { v.AddArg2(y, x) return true } + // match: (BTLconst [0] s:(SHRXQ x y)) + // result: (BTQ y x) + for { + if auxIntToInt8(v.AuxInt) != 0 { + break + } + s := v_0 + if s.Op != OpAMD64SHRXQ { + break + } + y := s.Args[1] + x := s.Args[0] + v.reset(OpAMD64BTQ) + v.AddArg2(y, x) + return true + } // match: (BTLconst [c] (SHRLconst [d] x)) // cond: (c+d)<32 // result: (BTLconst [c+d] x) @@ -3921,6 +4039,22 @@ func rewriteValueAMD64_OpAMD64BTLconst(v *Value) bool { v.AddArg2(y, x) return true } + // match: (BTLconst [0] s:(SHRXL x y)) + // result: (BTL y x) + for { + if auxIntToInt8(v.AuxInt) != 0 { + break + } + s := v_0 + if s.Op != OpAMD64SHRXL { + break + } + y := s.Args[1] + x := s.Args[0] + v.reset(OpAMD64BTL) + v.AddArg2(y, x) + return true + } return false } func rewriteValueAMD64_OpAMD64BTQconst(v *Value) bool { @@ -3977,6 +4111,22 @@ func rewriteValueAMD64_OpAMD64BTQconst(v *Value) bool { v.AddArg2(y, x) return true } + // match: (BTQconst [0] s:(SHRXQ x y)) + // result: (BTQ y x) + for { + if auxIntToInt8(v.AuxInt) != 0 { + break + } + s := v_0 + if s.Op != OpAMD64SHRXQ { + break + } + y := s.Args[1] + x := s.Args[0] + v.reset(OpAMD64BTQ) + v.AddArg2(y, x) + return true + } return false } func rewriteValueAMD64_OpAMD64BTRLconst(v *Value) bool { @@ -15886,6 +16036,25 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value) bool { } break } + // match: (ORL (SHLXL (MOVLconst [1]) y) x) + // result: (BTSL x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + y := v_0.Args[1] + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_0_0.AuxInt) != 1 { + continue + } + x := v_1 + v.reset(OpAMD64BTSL) + v.AddArg2(x, y) + return true + } + break + } // match: (ORL (MOVLconst [c]) x) // cond: isUint32PowerOfTwo(int64(c)) && uint64(c) >= 128 // result: (BTSLconst [int8(log32(c))] x) @@ -16196,6 +16365,206 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value) bool { } break } + // match: (ORL (SHLXL x y) (ANDL (SHRXL x (NEGQ y)) (SBBLcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [31]) [-32])) [32])))) + // result: (ROLL x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRXL { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGQ || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPQconst || auxIntToInt32(v_1_1_0.AuxInt) != 32 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGQ { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -32 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 31 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64ROLL) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORL (SHLXL x y) (ANDL (SHRXL x (NEGL y)) (SBBLcarrymask (CMPLconst (NEGL (ADDLconst (ANDLconst y [31]) [-32])) [32])))) + // result: (ROLL x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRXL { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGL || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPLconst || auxIntToInt32(v_1_1_0.AuxInt) != 32 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGL { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -32 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 31 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64ROLL) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORL (SHRXL x y) (ANDL (SHLXL x (NEGQ y)) (SBBLcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [31]) [-32])) [32])))) + // result: (RORL x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRXL { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXL { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGQ || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPQconst || auxIntToInt32(v_1_1_0.AuxInt) != 32 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGQ { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -32 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 31 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64RORL) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORL (SHRXL x y) (ANDL (SHLXL x (NEGL y)) (SBBLcarrymask (CMPLconst (NEGL (ADDLconst (ANDLconst y [31]) [-32])) [32])))) + // result: (RORL x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRXL { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXL { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGL || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPLconst || auxIntToInt32(v_1_1_0.AuxInt) != 32 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGL { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -32 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 31 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64RORL) + v.AddArg2(x, y) + return true + } + } + break + } // match: (ORL (SHLL x (ANDQconst y [15])) (ANDL (SHRW x (NEGQ (ADDQconst (ANDQconst y [15]) [-16]))) (SBBLcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [15]) [-16])) [16])))) // cond: v.Type.Size() == 2 // result: (ROLW x y) @@ -16404,6 +16773,214 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value) bool { } break } + // match: (ORL (SHLXL x (ANDQconst y [15])) (ANDL (SHRW x (NEGQ (ADDQconst (ANDQconst y [15]) [-16]))) (SBBLcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [15]) [-16])) [16])))) + // cond: v.Type.Size() == 2 + // result: (ROLW x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDQconst || auxIntToInt32(v_0_1.AuxInt) != 15 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRW { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGQ { + continue + } + v_1_0_1_0 := v_1_0_1.Args[0] + if v_1_0_1_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_0_1_0.AuxInt) != -16 { + continue + } + v_1_0_1_0_0 := v_1_0_1_0.Args[0] + if v_1_0_1_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_0_1_0_0.AuxInt) != 15 || y != v_1_0_1_0_0.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPQconst || auxIntToInt32(v_1_1_0.AuxInt) != 16 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGQ { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -16 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 15 || y != v_1_1_0_0_0_0.Args[0] || !(v.Type.Size() == 2) { + continue + } + v.reset(OpAMD64ROLW) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORL (SHLXL x (ANDLconst y [15])) (ANDL (SHRW x (NEGL (ADDLconst (ANDLconst y [15]) [-16]))) (SBBLcarrymask (CMPLconst (NEGL (ADDLconst (ANDLconst y [15]) [-16])) [16])))) + // cond: v.Type.Size() == 2 + // result: (ROLW x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDLconst || auxIntToInt32(v_0_1.AuxInt) != 15 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRW { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGL { + continue + } + v_1_0_1_0 := v_1_0_1.Args[0] + if v_1_0_1_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_0_1_0.AuxInt) != -16 { + continue + } + v_1_0_1_0_0 := v_1_0_1_0.Args[0] + if v_1_0_1_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_0_1_0_0.AuxInt) != 15 || y != v_1_0_1_0_0.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPLconst || auxIntToInt32(v_1_1_0.AuxInt) != 16 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGL { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -16 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 15 || y != v_1_1_0_0_0_0.Args[0] || !(v.Type.Size() == 2) { + continue + } + v.reset(OpAMD64ROLW) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORL (SHRW x (ANDQconst y [15])) (SHLXL x (NEGQ (ADDQconst (ANDQconst y [15]) [-16])))) + // cond: v.Type.Size() == 2 + // result: (RORW x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRW { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDQconst || auxIntToInt32(v_0_1.AuxInt) != 15 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64SHLXL { + continue + } + _ = v_1.Args[1] + if x != v_1.Args[0] { + continue + } + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGQ { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0.AuxInt) != -16 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0.AuxInt) != 15 || y != v_1_1_0_0.Args[0] || !(v.Type.Size() == 2) { + continue + } + v.reset(OpAMD64RORW) + v.AddArg2(x, y) + return true + } + break + } + // match: (ORL (SHRW x (ANDLconst y [15])) (SHLXL x (NEGL (ADDLconst (ANDLconst y [15]) [-16])))) + // cond: v.Type.Size() == 2 + // result: (RORW x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRW { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDLconst || auxIntToInt32(v_0_1.AuxInt) != 15 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64SHLXL { + continue + } + _ = v_1.Args[1] + if x != v_1.Args[0] { + continue + } + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGL { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0.AuxInt) != -16 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0.AuxInt) != 15 || y != v_1_1_0_0.Args[0] || !(v.Type.Size() == 2) { + continue + } + v.reset(OpAMD64RORW) + v.AddArg2(x, y) + return true + } + break + } // match: (ORL (SHLL x (ANDQconst y [ 7])) (ANDL (SHRB x (NEGQ (ADDQconst (ANDQconst y [ 7]) [ -8]))) (SBBLcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [ 7]) [ -8])) [ 8])))) // cond: v.Type.Size() == 1 // result: (ROLB x y) @@ -16612,6 +17189,214 @@ func rewriteValueAMD64_OpAMD64ORL(v *Value) bool { } break } + // match: (ORL (SHLXL x (ANDQconst y [ 7])) (ANDL (SHRB x (NEGQ (ADDQconst (ANDQconst y [ 7]) [ -8]))) (SBBLcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [ 7]) [ -8])) [ 8])))) + // cond: v.Type.Size() == 1 + // result: (ROLB x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDQconst || auxIntToInt32(v_0_1.AuxInt) != 7 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRB { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGQ { + continue + } + v_1_0_1_0 := v_1_0_1.Args[0] + if v_1_0_1_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_0_1_0.AuxInt) != -8 { + continue + } + v_1_0_1_0_0 := v_1_0_1_0.Args[0] + if v_1_0_1_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_0_1_0_0.AuxInt) != 7 || y != v_1_0_1_0_0.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPQconst || auxIntToInt32(v_1_1_0.AuxInt) != 8 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGQ { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -8 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 7 || y != v_1_1_0_0_0_0.Args[0] || !(v.Type.Size() == 1) { + continue + } + v.reset(OpAMD64ROLB) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORL (SHLXL x (ANDLconst y [ 7])) (ANDL (SHRB x (NEGL (ADDLconst (ANDLconst y [ 7]) [ -8]))) (SBBLcarrymask (CMPLconst (NEGL (ADDLconst (ANDLconst y [ 7]) [ -8])) [ 8])))) + // cond: v.Type.Size() == 1 + // result: (ROLB x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDLconst || auxIntToInt32(v_0_1.AuxInt) != 7 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64ANDL { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRB { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGL { + continue + } + v_1_0_1_0 := v_1_0_1.Args[0] + if v_1_0_1_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_0_1_0.AuxInt) != -8 { + continue + } + v_1_0_1_0_0 := v_1_0_1_0.Args[0] + if v_1_0_1_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_0_1_0_0.AuxInt) != 7 || y != v_1_0_1_0_0.Args[0] || v_1_1.Op != OpAMD64SBBLcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPLconst || auxIntToInt32(v_1_1_0.AuxInt) != 8 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGL { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -8 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 7 || y != v_1_1_0_0_0_0.Args[0] || !(v.Type.Size() == 1) { + continue + } + v.reset(OpAMD64ROLB) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORL (SHRB x (ANDQconst y [ 7])) (SHLXL x (NEGQ (ADDQconst (ANDQconst y [ 7]) [ -8])))) + // cond: v.Type.Size() == 1 + // result: (RORB x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRB { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDQconst || auxIntToInt32(v_0_1.AuxInt) != 7 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64SHLXL { + continue + } + _ = v_1.Args[1] + if x != v_1.Args[0] { + continue + } + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGQ { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0.AuxInt) != -8 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0.AuxInt) != 7 || y != v_1_1_0_0.Args[0] || !(v.Type.Size() == 1) { + continue + } + v.reset(OpAMD64RORB) + v.AddArg2(x, y) + return true + } + break + } + // match: (ORL (SHRB x (ANDLconst y [ 7])) (SHLXL x (NEGL (ADDLconst (ANDLconst y [ 7]) [ -8])))) + // cond: v.Type.Size() == 1 + // result: (RORB x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRB { + continue + } + _ = v_0.Args[1] + x := v_0.Args[0] + v_0_1 := v_0.Args[1] + if v_0_1.Op != OpAMD64ANDLconst || auxIntToInt32(v_0_1.AuxInt) != 7 { + continue + } + y := v_0_1.Args[0] + if v_1.Op != OpAMD64SHLXL { + continue + } + _ = v_1.Args[1] + if x != v_1.Args[0] { + continue + } + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGL { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0.AuxInt) != -8 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0.AuxInt) != 7 || y != v_1_1_0_0.Args[0] || !(v.Type.Size() == 1) { + continue + } + v.reset(OpAMD64RORB) + v.AddArg2(x, y) + return true + } + break + } // match: (ORL x x) // result: x for { @@ -17505,6 +18290,25 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value) bool { } break } + // match: (ORQ (SHLXQ (MOVQconst [1]) y) x) + // result: (BTSQ x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXQ { + continue + } + y := v_0.Args[1] + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_0_0.AuxInt) != 1 { + continue + } + x := v_1 + v.reset(OpAMD64BTSQ) + v.AddArg2(x, y) + return true + } + break + } // match: (ORQ (MOVQconst [c]) x) // cond: isUint64PowerOfTwo(c) && uint64(c) >= 128 // result: (BTSQconst [int8(log64(c))] x) @@ -17785,6 +18589,206 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value) bool { } break } + // match: (ORQ (SHLXQ x y) (ANDQ (SHRXQ x (NEGQ y)) (SBBQcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [63]) [-64])) [64])))) + // result: (ROLQ x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXQ { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDQ { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRXQ { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGQ || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBQcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPQconst || auxIntToInt32(v_1_1_0.AuxInt) != 64 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGQ { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -64 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 63 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64ROLQ) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORQ (SHLXQ x y) (ANDQ (SHRXQ x (NEGL y)) (SBBQcarrymask (CMPLconst (NEGL (ADDLconst (ANDLconst y [63]) [-64])) [64])))) + // result: (ROLQ x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXQ { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDQ { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHRXQ { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGL || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBQcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPLconst || auxIntToInt32(v_1_1_0.AuxInt) != 64 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGL { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -64 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 63 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64ROLQ) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORQ (SHRXQ x y) (ANDQ (SHLXQ x (NEGQ y)) (SBBQcarrymask (CMPQconst (NEGQ (ADDQconst (ANDQconst y [63]) [-64])) [64])))) + // result: (RORQ x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRXQ { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDQ { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXQ { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGQ || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBQcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPQconst || auxIntToInt32(v_1_1_0.AuxInt) != 64 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGQ { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDQconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -64 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDQconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 63 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64RORQ) + v.AddArg2(x, y) + return true + } + } + break + } + // match: (ORQ (SHRXQ x y) (ANDQ (SHLXQ x (NEGL y)) (SBBQcarrymask (CMPLconst (NEGL (ADDLconst (ANDLconst y [63]) [-64])) [64])))) + // result: (RORQ x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRXQ { + continue + } + y := v_0.Args[1] + x := v_0.Args[0] + if v_1.Op != OpAMD64ANDQ { + continue + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i1 := 0; _i1 <= 1; _i1, v_1_0, v_1_1 = _i1+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXQ { + continue + } + _ = v_1_0.Args[1] + if x != v_1_0.Args[0] { + continue + } + v_1_0_1 := v_1_0.Args[1] + if v_1_0_1.Op != OpAMD64NEGL || y != v_1_0_1.Args[0] || v_1_1.Op != OpAMD64SBBQcarrymask { + continue + } + v_1_1_0 := v_1_1.Args[0] + if v_1_1_0.Op != OpAMD64CMPLconst || auxIntToInt32(v_1_1_0.AuxInt) != 64 { + continue + } + v_1_1_0_0 := v_1_1_0.Args[0] + if v_1_1_0_0.Op != OpAMD64NEGL { + continue + } + v_1_1_0_0_0 := v_1_1_0_0.Args[0] + if v_1_1_0_0_0.Op != OpAMD64ADDLconst || auxIntToInt32(v_1_1_0_0_0.AuxInt) != -64 { + continue + } + v_1_1_0_0_0_0 := v_1_1_0_0_0.Args[0] + if v_1_1_0_0_0_0.Op != OpAMD64ANDLconst || auxIntToInt32(v_1_1_0_0_0_0.AuxInt) != 63 || y != v_1_1_0_0_0_0.Args[0] { + continue + } + v.reset(OpAMD64RORQ) + v.AddArg2(x, y) + return true + } + } + break + } // match: (ORQ (SHRQ lo bits) (SHLQ hi (NEGQ bits))) // result: (SHRDQ lo hi bits) for { @@ -17833,6 +18837,54 @@ func rewriteValueAMD64_OpAMD64ORQ(v *Value) bool { } break } + // match: (ORQ (SHRXQ lo bits) (SHLXQ hi (NEGQ bits))) + // result: (SHRDQ lo hi bits) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHRXQ { + continue + } + bits := v_0.Args[1] + lo := v_0.Args[0] + if v_1.Op != OpAMD64SHLXQ { + continue + } + _ = v_1.Args[1] + hi := v_1.Args[0] + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGQ || bits != v_1_1.Args[0] { + continue + } + v.reset(OpAMD64SHRDQ) + v.AddArg3(lo, hi, bits) + return true + } + break + } + // match: (ORQ (SHLXQ lo bits) (SHRXQ hi (NEGQ bits))) + // result: (SHLDQ lo hi bits) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXQ { + continue + } + bits := v_0.Args[1] + lo := v_0.Args[0] + if v_1.Op != OpAMD64SHRXQ { + continue + } + _ = v_1.Args[1] + hi := v_1.Args[0] + v_1_1 := v_1.Args[1] + if v_1_1.Op != OpAMD64NEGQ || bits != v_1_1.Args[0] { + continue + } + v.reset(OpAMD64SHLDQ) + v.AddArg3(lo, hi, bits) + return true + } + break + } // match: (ORQ (MOVQconst [c]) (MOVQconst [d])) // result: (MOVQconst [c|d]) for { @@ -19844,6 +20896,19 @@ func rewriteValueAMD64_OpAMD64SARL(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + // match: (SARL x y) + // cond: buildcfg.GOAMD64 >= 3 + // result: (SARXL x y) + for { + x := v_0 + y := v_1 + if !(buildcfg.GOAMD64 >= 3) { + break + } + v.reset(OpAMD64SARXL) + v.AddArg2(x, y) + return true + } // match: (SARL x (MOVQconst [c])) // result: (SARLconst [int8(c&31)] x) for { @@ -20066,6 +21131,19 @@ func rewriteValueAMD64_OpAMD64SARQ(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + // match: (SARQ x y) + // cond: buildcfg.GOAMD64 >= 3 + // result: (SARXQ x y) + for { + x := v_0 + y := v_1 + if !(buildcfg.GOAMD64 >= 3) { + break + } + v.reset(OpAMD64SARXQ) + v.AddArg2(x, y) + return true + } // match: (SARQ x (MOVQconst [c])) // result: (SARQconst [int8(c&63)] x) for { @@ -20341,6 +21419,518 @@ func rewriteValueAMD64_OpAMD64SARWconst(v *Value) bool { } return false } +func rewriteValueAMD64_OpAMD64SARXL(v *Value) bool { + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + // match: (SARXL x (MOVQconst [c])) + // result: (SARLconst [int8(c&31)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + v.reset(OpAMD64SARLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v.AddArg(x) + return true + } + // match: (SARXL x (MOVLconst [c])) + // result: (SARLconst [int8(c&31)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + v.reset(OpAMD64SARLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v.AddArg(x) + return true + } + // match: (SARXL x (ADDQconst [c] y)) + // cond: c & 31 == 0 + // result: (SARXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SARXL) + v.AddArg2(x, y) + return true + } + // match: (SARXL x (NEGQ (ADDQconst [c] y))) + // cond: c & 31 == 0 + // result: (SARXL x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SARXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXL x (ANDQconst [c] y)) + // cond: c & 31 == 31 + // result: (SARXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SARXL) + v.AddArg2(x, y) + return true + } + // match: (SARXL x (NEGQ (ANDQconst [c] y))) + // cond: c & 31 == 31 + // result: (SARXL x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SARXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXL x (ADDLconst [c] y)) + // cond: c & 31 == 0 + // result: (SARXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SARXL) + v.AddArg2(x, y) + return true + } + // match: (SARXL x (NEGL (ADDLconst [c] y))) + // cond: c & 31 == 0 + // result: (SARXL x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SARXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXL x (ANDLconst [c] y)) + // cond: c & 31 == 31 + // result: (SARXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SARXL) + v.AddArg2(x, y) + return true + } + // match: (SARXL x (NEGL (ANDLconst [c] y))) + // cond: c & 31 == 31 + // result: (SARXL x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SARXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXL l:(MOVLload [off] {sym} ptr mem) x) + // cond: canMergeLoad(v, l) && clobber(l) + // result: (SARXLload [off] {sym} ptr x mem) + for { + l := v_0 + if l.Op != OpAMD64MOVLload { + break + } + off := auxIntToInt32(l.AuxInt) + sym := auxToSym(l.Aux) + mem := l.Args[1] + ptr := l.Args[0] + x := v_1 + if !(canMergeLoad(v, l) && clobber(l)) { + break + } + v.reset(OpAMD64SARXLload) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v.AddArg3(ptr, x, mem) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SARXLload(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (SARXLload [off] {sym} ptr (MOVLconst [c]) mem) + // result: (SARLconst [int8(c&31)] (MOVLload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SARLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVLload, typ.UInt32) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SARXQ(v *Value) bool { + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + // match: (SARXQ x (MOVQconst [c])) + // result: (SARQconst [int8(c&63)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + v.reset(OpAMD64SARQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v.AddArg(x) + return true + } + // match: (SARXQ x (MOVLconst [c])) + // result: (SARQconst [int8(c&63)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + v.reset(OpAMD64SARQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v.AddArg(x) + return true + } + // match: (SARXQ x (ADDQconst [c] y)) + // cond: c & 63 == 0 + // result: (SARXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SARXQ) + v.AddArg2(x, y) + return true + } + // match: (SARXQ x (NEGQ (ADDQconst [c] y))) + // cond: c & 63 == 0 + // result: (SARXQ x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SARXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXQ x (ANDQconst [c] y)) + // cond: c & 63 == 63 + // result: (SARXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SARXQ) + v.AddArg2(x, y) + return true + } + // match: (SARXQ x (NEGQ (ANDQconst [c] y))) + // cond: c & 63 == 63 + // result: (SARXQ x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SARXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXQ x (ADDLconst [c] y)) + // cond: c & 63 == 0 + // result: (SARXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SARXQ) + v.AddArg2(x, y) + return true + } + // match: (SARXQ x (NEGL (ADDLconst [c] y))) + // cond: c & 63 == 0 + // result: (SARXQ x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SARXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXQ x (ANDLconst [c] y)) + // cond: c & 63 == 63 + // result: (SARXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SARXQ) + v.AddArg2(x, y) + return true + } + // match: (SARXQ x (NEGL (ANDLconst [c] y))) + // cond: c & 63 == 63 + // result: (SARXQ x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SARXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SARXQ l:(MOVQload [off] {sym} ptr mem) x) + // cond: canMergeLoad(v, l) && clobber(l) + // result: (SARXQload [off] {sym} ptr x mem) + for { + l := v_0 + if l.Op != OpAMD64MOVQload { + break + } + off := auxIntToInt32(l.AuxInt) + sym := auxToSym(l.Aux) + mem := l.Args[1] + ptr := l.Args[0] + x := v_1 + if !(canMergeLoad(v, l) && clobber(l)) { + break + } + v.reset(OpAMD64SARXQload) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v.AddArg3(ptr, x, mem) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SARXQload(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (SARXQload [off] {sym} ptr (MOVQconst [c]) mem) + // result: (SARQconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SARQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + // match: (SARXQload [off] {sym} ptr (MOVLconst [c]) mem) + // result: (SARQconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SARQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + return false +} func rewriteValueAMD64_OpAMD64SBBLcarrymask(v *Value) bool { v_0 := v.Args[0] // match: (SBBLcarrymask (FlagEQ)) @@ -21596,6 +23186,60 @@ func rewriteValueAMD64_OpAMD64SETEQ(v *Value) bool { } break } + // match: (SETEQ (TESTL (SHLXL (MOVLconst [1]) x) y)) + // result: (SETAE (BTL x y)) + for { + if v_0.Op != OpAMD64TESTL { + break + } + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXL { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v.reset(OpAMD64SETAE) + v0 := b.NewValue0(v.Pos, OpAMD64BTL, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg(v0) + return true + } + break + } + // match: (SETEQ (TESTQ (SHLXQ (MOVQconst [1]) x) y)) + // result: (SETAE (BTQ x y)) + for { + if v_0.Op != OpAMD64TESTQ { + break + } + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXQ { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v.reset(OpAMD64SETAE) + v0 := b.NewValue0(v.Pos, OpAMD64BTQ, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg(v0) + return true + } + break + } // match: (SETEQ (TESTLconst [c] x)) // cond: isUint32PowerOfTwo(int64(c)) // result: (SETAE (BTLconst [int8(log32(c))] x)) @@ -22021,6 +23665,72 @@ func rewriteValueAMD64_OpAMD64SETEQstore(v *Value) bool { } break } + // match: (SETEQstore [off] {sym} ptr (TESTL (SHLXL (MOVLconst [1]) x) y) mem) + // result: (SETAEstore [off] {sym} ptr (BTL x y) mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64TESTL { + break + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_1_0, v_1_1 = _i0+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXL { + continue + } + x := v_1_0.Args[1] + v_1_0_0 := v_1_0.Args[0] + if v_1_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_1_0_0.AuxInt) != 1 { + continue + } + y := v_1_1 + mem := v_2 + v.reset(OpAMD64SETAEstore) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v0 := b.NewValue0(v.Pos, OpAMD64BTL, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg3(ptr, v0, mem) + return true + } + break + } + // match: (SETEQstore [off] {sym} ptr (TESTQ (SHLXQ (MOVQconst [1]) x) y) mem) + // result: (SETAEstore [off] {sym} ptr (BTQ x y) mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64TESTQ { + break + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_1_0, v_1_1 = _i0+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXQ { + continue + } + x := v_1_0.Args[1] + v_1_0_0 := v_1_0.Args[0] + if v_1_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_1_0_0.AuxInt) != 1 { + continue + } + y := v_1_1 + mem := v_2 + v.reset(OpAMD64SETAEstore) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v0 := b.NewValue0(v.Pos, OpAMD64BTQ, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg3(ptr, v0, mem) + return true + } + break + } // match: (SETEQstore [off] {sym} ptr (TESTLconst [c] x) mem) // cond: isUint32PowerOfTwo(int64(c)) // result: (SETAEstore [off] {sym} ptr (BTLconst [int8(log32(c))] x) mem) @@ -23512,6 +25222,60 @@ func rewriteValueAMD64_OpAMD64SETNE(v *Value) bool { } break } + // match: (SETNE (TESTL (SHLXL (MOVLconst [1]) x) y)) + // result: (SETB (BTL x y)) + for { + if v_0.Op != OpAMD64TESTL { + break + } + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXL { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v.reset(OpAMD64SETB) + v0 := b.NewValue0(v.Pos, OpAMD64BTL, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg(v0) + return true + } + break + } + // match: (SETNE (TESTQ (SHLXQ (MOVQconst [1]) x) y)) + // result: (SETB (BTQ x y)) + for { + if v_0.Op != OpAMD64TESTQ { + break + } + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXQ { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v.reset(OpAMD64SETB) + v0 := b.NewValue0(v.Pos, OpAMD64BTQ, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg(v0) + return true + } + break + } // match: (SETNE (TESTLconst [c] x)) // cond: isUint32PowerOfTwo(int64(c)) // result: (SETB (BTLconst [int8(log32(c))] x)) @@ -23937,6 +25701,72 @@ func rewriteValueAMD64_OpAMD64SETNEstore(v *Value) bool { } break } + // match: (SETNEstore [off] {sym} ptr (TESTL (SHLXL (MOVLconst [1]) x) y) mem) + // result: (SETBstore [off] {sym} ptr (BTL x y) mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64TESTL { + break + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_1_0, v_1_1 = _i0+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXL { + continue + } + x := v_1_0.Args[1] + v_1_0_0 := v_1_0.Args[0] + if v_1_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_1_0_0.AuxInt) != 1 { + continue + } + y := v_1_1 + mem := v_2 + v.reset(OpAMD64SETBstore) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v0 := b.NewValue0(v.Pos, OpAMD64BTL, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg3(ptr, v0, mem) + return true + } + break + } + // match: (SETNEstore [off] {sym} ptr (TESTQ (SHLXQ (MOVQconst [1]) x) y) mem) + // result: (SETBstore [off] {sym} ptr (BTQ x y) mem) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64TESTQ { + break + } + _ = v_1.Args[1] + v_1_0 := v_1.Args[0] + v_1_1 := v_1.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_1_0, v_1_1 = _i0+1, v_1_1, v_1_0 { + if v_1_0.Op != OpAMD64SHLXQ { + continue + } + x := v_1_0.Args[1] + v_1_0_0 := v_1_0.Args[0] + if v_1_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_1_0_0.AuxInt) != 1 { + continue + } + y := v_1_1 + mem := v_2 + v.reset(OpAMD64SETBstore) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v0 := b.NewValue0(v.Pos, OpAMD64BTQ, types.TypeFlags) + v0.AddArg2(x, y) + v.AddArg3(ptr, v0, mem) + return true + } + break + } // match: (SETNEstore [off] {sym} ptr (TESTLconst [c] x) mem) // cond: isUint32PowerOfTwo(int64(c)) // result: (SETBstore [off] {sym} ptr (BTLconst [int8(log32(c))] x) mem) @@ -24451,6 +26281,19 @@ func rewriteValueAMD64_OpAMD64SHLL(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + // match: (SHLL x y) + // cond: buildcfg.GOAMD64 >= 3 + // result: (SHLXL x y) + for { + x := v_0 + y := v_1 + if !(buildcfg.GOAMD64 >= 3) { + break + } + v.reset(OpAMD64SHLXL) + v.AddArg2(x, y) + return true + } // match: (SHLL x (MOVQconst [c])) // result: (SHLLconst [int8(c&31)] x) for { @@ -24641,28 +26484,6 @@ func rewriteValueAMD64_OpAMD64SHLL(v *Value) bool { v.AddArg2(x, v0) return true } - // match: (SHLL l:(MOVLload [off] {sym} ptr mem) x) - // cond: buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l) - // result: (SHLXLload [off] {sym} ptr x mem) - for { - l := v_0 - if l.Op != OpAMD64MOVLload { - break - } - off := auxIntToInt32(l.AuxInt) - sym := auxToSym(l.Aux) - mem := l.Args[1] - ptr := l.Args[0] - x := v_1 - if !(buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l)) { - break - } - v.reset(OpAMD64SHLXLload) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v.AddArg3(ptr, x, mem) - return true - } return false } func rewriteValueAMD64_OpAMD64SHLLconst(v *Value) bool { @@ -24707,6 +26528,19 @@ func rewriteValueAMD64_OpAMD64SHLQ(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + // match: (SHLQ x y) + // cond: buildcfg.GOAMD64 >= 3 + // result: (SHLXQ x y) + for { + x := v_0 + y := v_1 + if !(buildcfg.GOAMD64 >= 3) { + break + } + v.reset(OpAMD64SHLXQ) + v.AddArg2(x, y) + return true + } // match: (SHLQ x (MOVQconst [c])) // result: (SHLQconst [int8(c&63)] x) for { @@ -24897,28 +26731,6 @@ func rewriteValueAMD64_OpAMD64SHLQ(v *Value) bool { v.AddArg2(x, v0) return true } - // match: (SHLQ l:(MOVQload [off] {sym} ptr mem) x) - // cond: buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l) - // result: (SHLXQload [off] {sym} ptr x mem) - for { - l := v_0 - if l.Op != OpAMD64MOVQload { - break - } - off := auxIntToInt32(l.AuxInt) - sym := auxToSym(l.Aux) - mem := l.Args[1] - ptr := l.Args[0] - x := v_1 - if !(buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l)) { - break - } - v.reset(OpAMD64SHLXQload) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v.AddArg3(ptr, x, mem) - return true - } return false } func rewriteValueAMD64_OpAMD64SHLQconst(v *Value) bool { @@ -24971,6 +26783,518 @@ func rewriteValueAMD64_OpAMD64SHLQconst(v *Value) bool { } return false } +func rewriteValueAMD64_OpAMD64SHLXL(v *Value) bool { + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + // match: (SHLXL x (MOVQconst [c])) + // result: (SHLLconst [int8(c&31)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + v.reset(OpAMD64SHLLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v.AddArg(x) + return true + } + // match: (SHLXL x (MOVLconst [c])) + // result: (SHLLconst [int8(c&31)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + v.reset(OpAMD64SHLLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v.AddArg(x) + return true + } + // match: (SHLXL x (ADDQconst [c] y)) + // cond: c & 31 == 0 + // result: (SHLXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHLXL) + v.AddArg2(x, y) + return true + } + // match: (SHLXL x (NEGQ (ADDQconst [c] y))) + // cond: c & 31 == 0 + // result: (SHLXL x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHLXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXL x (ANDQconst [c] y)) + // cond: c & 31 == 31 + // result: (SHLXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHLXL) + v.AddArg2(x, y) + return true + } + // match: (SHLXL x (NEGQ (ANDQconst [c] y))) + // cond: c & 31 == 31 + // result: (SHLXL x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHLXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXL x (ADDLconst [c] y)) + // cond: c & 31 == 0 + // result: (SHLXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHLXL) + v.AddArg2(x, y) + return true + } + // match: (SHLXL x (NEGL (ADDLconst [c] y))) + // cond: c & 31 == 0 + // result: (SHLXL x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHLXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXL x (ANDLconst [c] y)) + // cond: c & 31 == 31 + // result: (SHLXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHLXL) + v.AddArg2(x, y) + return true + } + // match: (SHLXL x (NEGL (ANDLconst [c] y))) + // cond: c & 31 == 31 + // result: (SHLXL x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHLXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXL l:(MOVLload [off] {sym} ptr mem) x) + // cond: canMergeLoad(v, l) && clobber(l) + // result: (SHLXLload [off] {sym} ptr x mem) + for { + l := v_0 + if l.Op != OpAMD64MOVLload { + break + } + off := auxIntToInt32(l.AuxInt) + sym := auxToSym(l.Aux) + mem := l.Args[1] + ptr := l.Args[0] + x := v_1 + if !(canMergeLoad(v, l) && clobber(l)) { + break + } + v.reset(OpAMD64SHLXLload) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v.AddArg3(ptr, x, mem) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SHLXLload(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (SHLXLload [off] {sym} ptr (MOVLconst [c]) mem) + // result: (SHLLconst [int8(c&31)] (MOVLload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SHLLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVLload, typ.UInt32) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SHLXQ(v *Value) bool { + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + // match: (SHLXQ x (MOVQconst [c])) + // result: (SHLQconst [int8(c&63)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + v.reset(OpAMD64SHLQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v.AddArg(x) + return true + } + // match: (SHLXQ x (MOVLconst [c])) + // result: (SHLQconst [int8(c&63)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + v.reset(OpAMD64SHLQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v.AddArg(x) + return true + } + // match: (SHLXQ x (ADDQconst [c] y)) + // cond: c & 63 == 0 + // result: (SHLXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHLXQ) + v.AddArg2(x, y) + return true + } + // match: (SHLXQ x (NEGQ (ADDQconst [c] y))) + // cond: c & 63 == 0 + // result: (SHLXQ x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHLXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXQ x (ANDQconst [c] y)) + // cond: c & 63 == 63 + // result: (SHLXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHLXQ) + v.AddArg2(x, y) + return true + } + // match: (SHLXQ x (NEGQ (ANDQconst [c] y))) + // cond: c & 63 == 63 + // result: (SHLXQ x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHLXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXQ x (ADDLconst [c] y)) + // cond: c & 63 == 0 + // result: (SHLXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHLXQ) + v.AddArg2(x, y) + return true + } + // match: (SHLXQ x (NEGL (ADDLconst [c] y))) + // cond: c & 63 == 0 + // result: (SHLXQ x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHLXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXQ x (ANDLconst [c] y)) + // cond: c & 63 == 63 + // result: (SHLXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHLXQ) + v.AddArg2(x, y) + return true + } + // match: (SHLXQ x (NEGL (ANDLconst [c] y))) + // cond: c & 63 == 63 + // result: (SHLXQ x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHLXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHLXQ l:(MOVQload [off] {sym} ptr mem) x) + // cond: canMergeLoad(v, l) && clobber(l) + // result: (SHLXQload [off] {sym} ptr x mem) + for { + l := v_0 + if l.Op != OpAMD64MOVQload { + break + } + off := auxIntToInt32(l.AuxInt) + sym := auxToSym(l.Aux) + mem := l.Args[1] + ptr := l.Args[0] + x := v_1 + if !(canMergeLoad(v, l) && clobber(l)) { + break + } + v.reset(OpAMD64SHLXQload) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v.AddArg3(ptr, x, mem) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SHLXQload(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (SHLXQload [off] {sym} ptr (MOVQconst [c]) mem) + // result: (SHLQconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SHLQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + // match: (SHLXQload [off] {sym} ptr (MOVLconst [c]) mem) + // result: (SHLQconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SHLQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + return false +} func rewriteValueAMD64_OpAMD64SHRB(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -25058,6 +27382,19 @@ func rewriteValueAMD64_OpAMD64SHRL(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + // match: (SHRL x y) + // cond: buildcfg.GOAMD64 >= 3 + // result: (SHRXL x y) + for { + x := v_0 + y := v_1 + if !(buildcfg.GOAMD64 >= 3) { + break + } + v.reset(OpAMD64SHRXL) + v.AddArg2(x, y) + return true + } // match: (SHRL x (MOVQconst [c])) // result: (SHRLconst [int8(c&31)] x) for { @@ -25248,28 +27585,6 @@ func rewriteValueAMD64_OpAMD64SHRL(v *Value) bool { v.AddArg2(x, v0) return true } - // match: (SHRL l:(MOVLload [off] {sym} ptr mem) x) - // cond: buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l) - // result: (SHRXLload [off] {sym} ptr x mem) - for { - l := v_0 - if l.Op != OpAMD64MOVLload { - break - } - off := auxIntToInt32(l.AuxInt) - sym := auxToSym(l.Aux) - mem := l.Args[1] - ptr := l.Args[0] - x := v_1 - if !(buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l)) { - break - } - v.reset(OpAMD64SHRXLload) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v.AddArg3(ptr, x, mem) - return true - } return false } func rewriteValueAMD64_OpAMD64SHRLconst(v *Value) bool { @@ -25302,6 +27617,19 @@ func rewriteValueAMD64_OpAMD64SHRQ(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] b := v.Block + // match: (SHRQ x y) + // cond: buildcfg.GOAMD64 >= 3 + // result: (SHRXQ x y) + for { + x := v_0 + y := v_1 + if !(buildcfg.GOAMD64 >= 3) { + break + } + v.reset(OpAMD64SHRXQ) + v.AddArg2(x, y) + return true + } // match: (SHRQ x (MOVQconst [c])) // result: (SHRQconst [int8(c&63)] x) for { @@ -25492,28 +27820,6 @@ func rewriteValueAMD64_OpAMD64SHRQ(v *Value) bool { v.AddArg2(x, v0) return true } - // match: (SHRQ l:(MOVQload [off] {sym} ptr mem) x) - // cond: buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l) - // result: (SHRXQload [off] {sym} ptr x mem) - for { - l := v_0 - if l.Op != OpAMD64MOVQload { - break - } - off := auxIntToInt32(l.AuxInt) - sym := auxToSym(l.Aux) - mem := l.Args[1] - ptr := l.Args[0] - x := v_1 - if !(buildcfg.GOAMD64 >= 3 && canMergeLoad(v, l) && clobber(l)) { - break - } - v.reset(OpAMD64SHRXQload) - v.AuxInt = int32ToAuxInt(off) - v.Aux = symToAux(sym) - v.AddArg3(ptr, x, mem) - return true - } return false } func rewriteValueAMD64_OpAMD64SHRQconst(v *Value) bool { @@ -25625,6 +27931,518 @@ func rewriteValueAMD64_OpAMD64SHRWconst(v *Value) bool { } return false } +func rewriteValueAMD64_OpAMD64SHRXL(v *Value) bool { + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + // match: (SHRXL x (MOVQconst [c])) + // result: (SHRLconst [int8(c&31)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + v.reset(OpAMD64SHRLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v.AddArg(x) + return true + } + // match: (SHRXL x (MOVLconst [c])) + // result: (SHRLconst [int8(c&31)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + v.reset(OpAMD64SHRLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v.AddArg(x) + return true + } + // match: (SHRXL x (ADDQconst [c] y)) + // cond: c & 31 == 0 + // result: (SHRXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHRXL) + v.AddArg2(x, y) + return true + } + // match: (SHRXL x (NEGQ (ADDQconst [c] y))) + // cond: c & 31 == 0 + // result: (SHRXL x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHRXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXL x (ANDQconst [c] y)) + // cond: c & 31 == 31 + // result: (SHRXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHRXL) + v.AddArg2(x, y) + return true + } + // match: (SHRXL x (NEGQ (ANDQconst [c] y))) + // cond: c & 31 == 31 + // result: (SHRXL x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHRXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXL x (ADDLconst [c] y)) + // cond: c & 31 == 0 + // result: (SHRXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHRXL) + v.AddArg2(x, y) + return true + } + // match: (SHRXL x (NEGL (ADDLconst [c] y))) + // cond: c & 31 == 0 + // result: (SHRXL x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 0) { + break + } + v.reset(OpAMD64SHRXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXL x (ANDLconst [c] y)) + // cond: c & 31 == 31 + // result: (SHRXL x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHRXL) + v.AddArg2(x, y) + return true + } + // match: (SHRXL x (NEGL (ANDLconst [c] y))) + // cond: c & 31 == 31 + // result: (SHRXL x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&31 == 31) { + break + } + v.reset(OpAMD64SHRXL) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXL l:(MOVLload [off] {sym} ptr mem) x) + // cond: canMergeLoad(v, l) && clobber(l) + // result: (SHRXLload [off] {sym} ptr x mem) + for { + l := v_0 + if l.Op != OpAMD64MOVLload { + break + } + off := auxIntToInt32(l.AuxInt) + sym := auxToSym(l.Aux) + mem := l.Args[1] + ptr := l.Args[0] + x := v_1 + if !(canMergeLoad(v, l) && clobber(l)) { + break + } + v.reset(OpAMD64SHRXLload) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v.AddArg3(ptr, x, mem) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SHRXLload(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (SHRXLload [off] {sym} ptr (MOVLconst [c]) mem) + // result: (SHRLconst [int8(c&31)] (MOVLload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SHRLconst) + v.AuxInt = int8ToAuxInt(int8(c & 31)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVLload, typ.UInt32) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SHRXQ(v *Value) bool { + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + // match: (SHRXQ x (MOVQconst [c])) + // result: (SHRQconst [int8(c&63)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + v.reset(OpAMD64SHRQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v.AddArg(x) + return true + } + // match: (SHRXQ x (MOVLconst [c])) + // result: (SHRQconst [int8(c&63)] x) + for { + x := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + v.reset(OpAMD64SHRQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v.AddArg(x) + return true + } + // match: (SHRXQ x (ADDQconst [c] y)) + // cond: c & 63 == 0 + // result: (SHRXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHRXQ) + v.AddArg2(x, y) + return true + } + // match: (SHRXQ x (NEGQ (ADDQconst [c] y))) + // cond: c & 63 == 0 + // result: (SHRXQ x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHRXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXQ x (ANDQconst [c] y)) + // cond: c & 63 == 63 + // result: (SHRXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHRXQ) + v.AddArg2(x, y) + return true + } + // match: (SHRXQ x (NEGQ (ANDQconst [c] y))) + // cond: c & 63 == 63 + // result: (SHRXQ x (NEGQ y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGQ { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDQconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHRXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGQ, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXQ x (ADDLconst [c] y)) + // cond: c & 63 == 0 + // result: (SHRXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHRXQ) + v.AddArg2(x, y) + return true + } + // match: (SHRXQ x (NEGL (ADDLconst [c] y))) + // cond: c & 63 == 0 + // result: (SHRXQ x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ADDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 0) { + break + } + v.reset(OpAMD64SHRXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXQ x (ANDLconst [c] y)) + // cond: c & 63 == 63 + // result: (SHRXQ x y) + for { + x := v_0 + if v_1.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + y := v_1.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHRXQ) + v.AddArg2(x, y) + return true + } + // match: (SHRXQ x (NEGL (ANDLconst [c] y))) + // cond: c & 63 == 63 + // result: (SHRXQ x (NEGL y)) + for { + x := v_0 + if v_1.Op != OpAMD64NEGL { + break + } + t := v_1.Type + v_1_0 := v_1.Args[0] + if v_1_0.Op != OpAMD64ANDLconst { + break + } + c := auxIntToInt32(v_1_0.AuxInt) + y := v_1_0.Args[0] + if !(c&63 == 63) { + break + } + v.reset(OpAMD64SHRXQ) + v0 := b.NewValue0(v.Pos, OpAMD64NEGL, t) + v0.AddArg(y) + v.AddArg2(x, v0) + return true + } + // match: (SHRXQ l:(MOVQload [off] {sym} ptr mem) x) + // cond: canMergeLoad(v, l) && clobber(l) + // result: (SHRXQload [off] {sym} ptr x mem) + for { + l := v_0 + if l.Op != OpAMD64MOVQload { + break + } + off := auxIntToInt32(l.AuxInt) + sym := auxToSym(l.Aux) + mem := l.Args[1] + ptr := l.Args[0] + x := v_1 + if !(canMergeLoad(v, l) && clobber(l)) { + break + } + v.reset(OpAMD64SHRXQload) + v.AuxInt = int32ToAuxInt(off) + v.Aux = symToAux(sym) + v.AddArg3(ptr, x, mem) + return true + } + return false +} +func rewriteValueAMD64_OpAMD64SHRXQload(v *Value) bool { + v_2 := v.Args[2] + v_1 := v.Args[1] + v_0 := v.Args[0] + b := v.Block + typ := &b.Func.Config.Types + // match: (SHRXQload [off] {sym} ptr (MOVQconst [c]) mem) + // result: (SHRQconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVQconst { + break + } + c := auxIntToInt64(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SHRQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + // match: (SHRXQload [off] {sym} ptr (MOVLconst [c]) mem) + // result: (SHRQconst [int8(c&63)] (MOVQload [off] {sym} ptr mem)) + for { + off := auxIntToInt32(v.AuxInt) + sym := auxToSym(v.Aux) + ptr := v_0 + if v_1.Op != OpAMD64MOVLconst { + break + } + c := auxIntToInt32(v_1.AuxInt) + mem := v_2 + v.reset(OpAMD64SHRQconst) + v.AuxInt = int8ToAuxInt(int8(c & 63)) + v0 := b.NewValue0(v.Pos, OpAMD64MOVQload, typ.UInt64) + v0.AuxInt = int32ToAuxInt(off) + v0.Aux = symToAux(sym) + v0.AddArg2(ptr, mem) + v.AddArg(v0) + return true + } + return false +} func rewriteValueAMD64_OpAMD64SUBL(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -26908,6 +29726,25 @@ func rewriteValueAMD64_OpAMD64XORL(v *Value) bool { } break } + // match: (XORL (SHLXL (MOVLconst [1]) y) x) + // result: (BTCL x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXL { + continue + } + y := v_0.Args[1] + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_0_0.AuxInt) != 1 { + continue + } + x := v_1 + v.reset(OpAMD64BTCL) + v.AddArg2(x, y) + return true + } + break + } // match: (XORL (MOVLconst [c]) x) // cond: isUint32PowerOfTwo(int64(c)) && uint64(c) >= 128 // result: (BTCLconst [int8(log32(c))] x) @@ -27445,6 +30282,25 @@ func rewriteValueAMD64_OpAMD64XORQ(v *Value) bool { } break } + // match: (XORQ (SHLXQ (MOVQconst [1]) y) x) + // result: (BTCQ x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpAMD64SHLXQ { + continue + } + y := v_0.Args[1] + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_0_0.AuxInt) != 1 { + continue + } + x := v_1 + v.reset(OpAMD64BTCQ) + v.AddArg2(x, y) + return true + } + break + } // match: (XORQ (MOVQconst [c]) x) // cond: isUint64PowerOfTwo(c) && uint64(c) >= 128 // result: (BTCQconst [int8(log64(c))] x) @@ -34017,6 +36873,7 @@ func rewriteValueAMD64_OpZero(v *Value) bool { return false } func rewriteBlockAMD64(b *Block) bool { + typ := &b.Func.Config.Types switch b.Kind { case BlockAMD64EQ: // match: (EQ (TESTL (SHLL (MOVLconst [1]) x) y)) @@ -34067,6 +36924,54 @@ func rewriteBlockAMD64(b *Block) bool { } break } + // match: (EQ (TESTL (SHLXL (MOVLconst [1]) x) y)) + // result: (UGE (BTL x y)) + for b.Controls[0].Op == OpAMD64TESTL { + v_0 := b.Controls[0] + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXL { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v0 := b.NewValue0(v_0.Pos, OpAMD64BTL, types.TypeFlags) + v0.AddArg2(x, y) + b.resetWithControl(BlockAMD64UGE, v0) + return true + } + break + } + // match: (EQ (TESTQ (SHLXQ (MOVQconst [1]) x) y)) + // result: (UGE (BTQ x y)) + for b.Controls[0].Op == OpAMD64TESTQ { + v_0 := b.Controls[0] + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXQ { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v0 := b.NewValue0(v_0.Pos, OpAMD64BTQ, types.TypeFlags) + v0.AddArg2(x, y) + b.resetWithControl(BlockAMD64UGE, v0) + return true + } + break + } // match: (EQ (TESTLconst [c] x)) // cond: isUint32PowerOfTwo(int64(c)) // result: (UGE (BTLconst [int8(log32(c))] x)) @@ -34551,6 +37456,19 @@ func rewriteBlockAMD64(b *Block) bool { b.resetWithControl(BlockAMD64NE, v0) return true } + case BlockJumpTable: + // match: (JumpTable idx) + // result: (JUMPTABLE {makeJumpTableSym(b)} idx (LEAQ {makeJumpTableSym(b)} (SB))) + for { + idx := b.Controls[0] + v0 := b.NewValue0(b.Pos, OpAMD64LEAQ, typ.Uintptr) + v0.Aux = symToAux(makeJumpTableSym(b)) + v1 := b.NewValue0(b.Pos, OpSB, typ.Uintptr) + v0.AddArg(v1) + b.resetWithControl2(BlockAMD64JUMPTABLE, idx, v0) + b.Aux = symToAux(makeJumpTableSym(b)) + return true + } case BlockAMD64LE: // match: (LE (InvertFlags cmp) yes no) // result: (GE cmp yes no) @@ -34870,6 +37788,54 @@ func rewriteBlockAMD64(b *Block) bool { } break } + // match: (NE (TESTL (SHLXL (MOVLconst [1]) x) y)) + // result: (ULT (BTL x y)) + for b.Controls[0].Op == OpAMD64TESTL { + v_0 := b.Controls[0] + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXL { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVLconst || auxIntToInt32(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v0 := b.NewValue0(v_0.Pos, OpAMD64BTL, types.TypeFlags) + v0.AddArg2(x, y) + b.resetWithControl(BlockAMD64ULT, v0) + return true + } + break + } + // match: (NE (TESTQ (SHLXQ (MOVQconst [1]) x) y)) + // result: (ULT (BTQ x y)) + for b.Controls[0].Op == OpAMD64TESTQ { + v_0 := b.Controls[0] + _ = v_0.Args[1] + v_0_0 := v_0.Args[0] + v_0_1 := v_0.Args[1] + for _i0 := 0; _i0 <= 1; _i0, v_0_0, v_0_1 = _i0+1, v_0_1, v_0_0 { + if v_0_0.Op != OpAMD64SHLXQ { + continue + } + x := v_0_0.Args[1] + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpAMD64MOVQconst || auxIntToInt64(v_0_0_0.AuxInt) != 1 { + continue + } + y := v_0_1 + v0 := b.NewValue0(v_0.Pos, OpAMD64BTQ, types.TypeFlags) + v0.AddArg2(x, y) + b.resetWithControl(BlockAMD64ULT, v0) + return true + } + break + } // match: (NE (TESTLconst [c] x)) // cond: isUint32PowerOfTwo(int64(c)) // result: (ULT (BTLconst [int8(log32(c))] x)) diff --git a/src/cmd/compile/internal/ssa/rewriteCond_test.go b/src/cmd/compile/internal/ssa/rewriteCond_test.go index 2c26fdf142..ca74ed5947 100644 --- a/src/cmd/compile/internal/ssa/rewriteCond_test.go +++ b/src/cmd/compile/internal/ssa/rewriteCond_test.go @@ -68,8 +68,10 @@ func TestCondRewrite(t *testing.T) { } // Profile the aforementioned optimization from two angles: -// SoloJump: generated branching code has one 'jump', for '<' and '>=' -// CombJump: generated branching code has two consecutive 'jump', for '<=' and '>' +// +// SoloJump: generated branching code has one 'jump', for '<' and '>=' +// CombJump: generated branching code has two consecutive 'jump', for '<=' and '>' +// // We expect that 'CombJump' is generally on par with the non-optimized code, and // 'SoloJump' demonstrates some improvement. // It's for arm64 initially, please see https://github.com/golang/go/issues/38740 diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index fbf227562a..f61b6ca3ec 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -519,6 +519,38 @@ func rewriteValuegeneric_OpAdd16(v *Value) bool { } break } + // match: (Add16 x (Neg16 y)) + // result: (Sub16 x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + x := v_0 + if v_1.Op != OpNeg16 { + continue + } + y := v_1.Args[0] + v.reset(OpSub16) + v.AddArg2(x, y) + return true + } + break + } + // match: (Add16 (Com16 x) x) + // result: (Const16 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom16 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst16) + v.AuxInt = int16ToAuxInt(-1) + return true + } + break + } // match: (Add16 (Const16 [1]) (Com16 x)) // result: (Neg16 x) for { @@ -764,6 +796,38 @@ func rewriteValuegeneric_OpAdd32(v *Value) bool { } break } + // match: (Add32 x (Neg32 y)) + // result: (Sub32 x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + x := v_0 + if v_1.Op != OpNeg32 { + continue + } + y := v_1.Args[0] + v.reset(OpSub32) + v.AddArg2(x, y) + return true + } + break + } + // match: (Add32 (Com32 x) x) + // result: (Const32 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom32 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst32) + v.AuxInt = int32ToAuxInt(-1) + return true + } + break + } // match: (Add32 (Const32 [1]) (Com32 x)) // result: (Neg32 x) for { @@ -1036,6 +1100,38 @@ func rewriteValuegeneric_OpAdd64(v *Value) bool { } break } + // match: (Add64 x (Neg64 y)) + // result: (Sub64 x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + x := v_0 + if v_1.Op != OpNeg64 { + continue + } + y := v_1.Args[0] + v.reset(OpSub64) + v.AddArg2(x, y) + return true + } + break + } + // match: (Add64 (Com64 x) x) + // result: (Const64 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom64 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst64) + v.AuxInt = int64ToAuxInt(-1) + return true + } + break + } // match: (Add64 (Const64 [1]) (Com64 x)) // result: (Neg64 x) for { @@ -1308,6 +1404,38 @@ func rewriteValuegeneric_OpAdd8(v *Value) bool { } break } + // match: (Add8 x (Neg8 y)) + // result: (Sub8 x y) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + x := v_0 + if v_1.Op != OpNeg8 { + continue + } + y := v_1.Args[0] + v.reset(OpSub8) + v.AddArg2(x, y) + return true + } + break + } + // match: (Add8 (Com8 x) x) + // result: (Const8 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom8 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst8) + v.AuxInt = int8ToAuxInt(-1) + return true + } + break + } // match: (Add8 (Const8 [1]) (Com8 x)) // result: (Neg8 x) for { @@ -1630,6 +1758,23 @@ func rewriteValuegeneric_OpAnd16(v *Value) bool { } break } + // match: (And16 (Com16 x) x) + // result: (Const16 [0]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom16 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst16) + v.AuxInt = int16ToAuxInt(0) + return true + } + break + } // match: (And16 x (And16 x y)) // result: (And16 x y) for { @@ -1828,6 +1973,23 @@ func rewriteValuegeneric_OpAnd32(v *Value) bool { } break } + // match: (And32 (Com32 x) x) + // result: (Const32 [0]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom32 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst32) + v.AuxInt = int32ToAuxInt(0) + return true + } + break + } // match: (And32 x (And32 x y)) // result: (And32 x y) for { @@ -2026,6 +2188,23 @@ func rewriteValuegeneric_OpAnd64(v *Value) bool { } break } + // match: (And64 (Com64 x) x) + // result: (Const64 [0]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom64 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst64) + v.AuxInt = int64ToAuxInt(0) + return true + } + break + } // match: (And64 x (And64 x y)) // result: (And64 x y) for { @@ -2224,6 +2403,23 @@ func rewriteValuegeneric_OpAnd8(v *Value) bool { } break } + // match: (And8 (Com8 x) x) + // result: (Const8 [0]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom8 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst8) + v.AuxInt = int8ToAuxInt(0) + return true + } + break + } // match: (And8 x (And8 x y)) // result: (And8 x y) for { @@ -16964,6 +17160,23 @@ func rewriteValuegeneric_OpOr16(v *Value) bool { } break } + // match: (Or16 (Com16 x) x) + // result: (Const16 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom16 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst16) + v.AuxInt = int16ToAuxInt(-1) + return true + } + break + } // match: (Or16 x (Or16 x y)) // result: (Or16 x y) for { @@ -17142,6 +17355,23 @@ func rewriteValuegeneric_OpOr32(v *Value) bool { } break } + // match: (Or32 (Com32 x) x) + // result: (Const32 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom32 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst32) + v.AuxInt = int32ToAuxInt(-1) + return true + } + break + } // match: (Or32 x (Or32 x y)) // result: (Or32 x y) for { @@ -17320,6 +17550,23 @@ func rewriteValuegeneric_OpOr64(v *Value) bool { } break } + // match: (Or64 (Com64 x) x) + // result: (Const64 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom64 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst64) + v.AuxInt = int64ToAuxInt(-1) + return true + } + break + } // match: (Or64 x (Or64 x y)) // result: (Or64 x y) for { @@ -17498,6 +17745,23 @@ func rewriteValuegeneric_OpOr8(v *Value) bool { } break } + // match: (Or8 (Com8 x) x) + // result: (Const8 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom8 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst8) + v.AuxInt = int8ToAuxInt(-1) + return true + } + break + } // match: (Or8 x (Or8 x y)) // result: (Or8 x y) for { @@ -22994,6 +23258,34 @@ func rewriteValuegeneric_OpSub16(v *Value) bool { v.AuxInt = int16ToAuxInt(0) return true } + // match: (Sub16 (Neg16 x) (Com16 x)) + // result: (Const16 [1]) + for { + if v_0.Op != OpNeg16 { + break + } + x := v_0.Args[0] + if v_1.Op != OpCom16 || x != v_1.Args[0] { + break + } + v.reset(OpConst16) + v.AuxInt = int16ToAuxInt(1) + return true + } + // match: (Sub16 (Com16 x) (Neg16 x)) + // result: (Const16 [-1]) + for { + if v_0.Op != OpCom16 { + break + } + x := v_0.Args[0] + if v_1.Op != OpNeg16 || x != v_1.Args[0] { + break + } + v.reset(OpConst16) + v.AuxInt = int16ToAuxInt(-1) + return true + } // match: (Sub16 (Add16 x y) x) // result: y for { @@ -23309,6 +23601,34 @@ func rewriteValuegeneric_OpSub32(v *Value) bool { v.AuxInt = int32ToAuxInt(0) return true } + // match: (Sub32 (Neg32 x) (Com32 x)) + // result: (Const32 [1]) + for { + if v_0.Op != OpNeg32 { + break + } + x := v_0.Args[0] + if v_1.Op != OpCom32 || x != v_1.Args[0] { + break + } + v.reset(OpConst32) + v.AuxInt = int32ToAuxInt(1) + return true + } + // match: (Sub32 (Com32 x) (Neg32 x)) + // result: (Const32 [-1]) + for { + if v_0.Op != OpCom32 { + break + } + x := v_0.Args[0] + if v_1.Op != OpNeg32 || x != v_1.Args[0] { + break + } + v.reset(OpConst32) + v.AuxInt = int32ToAuxInt(-1) + return true + } // match: (Sub32 (Add32 x y) x) // result: y for { @@ -23648,6 +23968,34 @@ func rewriteValuegeneric_OpSub64(v *Value) bool { v.AuxInt = int64ToAuxInt(0) return true } + // match: (Sub64 (Neg64 x) (Com64 x)) + // result: (Const64 [1]) + for { + if v_0.Op != OpNeg64 { + break + } + x := v_0.Args[0] + if v_1.Op != OpCom64 || x != v_1.Args[0] { + break + } + v.reset(OpConst64) + v.AuxInt = int64ToAuxInt(1) + return true + } + // match: (Sub64 (Com64 x) (Neg64 x)) + // result: (Const64 [-1]) + for { + if v_0.Op != OpCom64 { + break + } + x := v_0.Args[0] + if v_1.Op != OpNeg64 || x != v_1.Args[0] { + break + } + v.reset(OpConst64) + v.AuxInt = int64ToAuxInt(-1) + return true + } // match: (Sub64 (Add64 x y) x) // result: y for { @@ -23987,6 +24335,34 @@ func rewriteValuegeneric_OpSub8(v *Value) bool { v.AuxInt = int8ToAuxInt(0) return true } + // match: (Sub8 (Neg8 x) (Com8 x)) + // result: (Const8 [1]) + for { + if v_0.Op != OpNeg8 { + break + } + x := v_0.Args[0] + if v_1.Op != OpCom8 || x != v_1.Args[0] { + break + } + v.reset(OpConst8) + v.AuxInt = int8ToAuxInt(1) + return true + } + // match: (Sub8 (Com8 x) (Neg8 x)) + // result: (Const8 [-1]) + for { + if v_0.Op != OpCom8 { + break + } + x := v_0.Args[0] + if v_1.Op != OpNeg8 || x != v_1.Args[0] { + break + } + v.reset(OpConst8) + v.AuxInt = int8ToAuxInt(-1) + return true + } // match: (Sub8 (Add8 x y) x) // result: y for { @@ -24714,6 +25090,37 @@ func rewriteValuegeneric_OpXor16(v *Value) bool { } break } + // match: (Xor16 (Com16 x) x) + // result: (Const16 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom16 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst16) + v.AuxInt = int16ToAuxInt(-1) + return true + } + break + } + // match: (Xor16 (Const16 [-1]) x) + // result: (Com16 x) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpConst16 || auxIntToInt16(v_0.AuxInt) != -1 { + continue + } + x := v_1 + v.reset(OpCom16) + v.AddArg(x) + return true + } + break + } // match: (Xor16 x (Xor16 x y)) // result: y for { @@ -24845,6 +25252,37 @@ func rewriteValuegeneric_OpXor32(v *Value) bool { } break } + // match: (Xor32 (Com32 x) x) + // result: (Const32 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom32 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst32) + v.AuxInt = int32ToAuxInt(-1) + return true + } + break + } + // match: (Xor32 (Const32 [-1]) x) + // result: (Com32 x) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpConst32 || auxIntToInt32(v_0.AuxInt) != -1 { + continue + } + x := v_1 + v.reset(OpCom32) + v.AddArg(x) + return true + } + break + } // match: (Xor32 x (Xor32 x y)) // result: y for { @@ -24976,6 +25414,37 @@ func rewriteValuegeneric_OpXor64(v *Value) bool { } break } + // match: (Xor64 (Com64 x) x) + // result: (Const64 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom64 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst64) + v.AuxInt = int64ToAuxInt(-1) + return true + } + break + } + // match: (Xor64 (Const64 [-1]) x) + // result: (Com64 x) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpConst64 || auxIntToInt64(v_0.AuxInt) != -1 { + continue + } + x := v_1 + v.reset(OpCom64) + v.AddArg(x) + return true + } + break + } // match: (Xor64 x (Xor64 x y)) // result: y for { @@ -25107,6 +25576,37 @@ func rewriteValuegeneric_OpXor8(v *Value) bool { } break } + // match: (Xor8 (Com8 x) x) + // result: (Const8 [-1]) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpCom8 { + continue + } + x := v_0.Args[0] + if x != v_1 { + continue + } + v.reset(OpConst8) + v.AuxInt = int8ToAuxInt(-1) + return true + } + break + } + // match: (Xor8 (Const8 [-1]) x) + // result: (Com8 x) + for { + for _i0 := 0; _i0 <= 1; _i0, v_0, v_1 = _i0+1, v_1, v_0 { + if v_0.Op != OpConst8 || auxIntToInt8(v_0.AuxInt) != -1 { + continue + } + x := v_1 + v.reset(OpCom8) + v.AddArg(x) + return true + } + break + } // match: (Xor8 x (Xor8 x y)) // result: y for { diff --git a/src/cmd/compile/internal/ssa/schedule.go b/src/cmd/compile/internal/ssa/schedule.go index c5130b2ee5..170d8b7095 100644 --- a/src/cmd/compile/internal/ssa/schedule.go +++ b/src/cmd/compile/internal/ssa/schedule.go @@ -338,13 +338,15 @@ func schedule(f *Func) { // if v transitively depends on store s, v is ordered after s, // otherwise v is ordered before s. // Specifically, values are ordered like -// store1 -// NilCheck that depends on store1 -// other values that depends on store1 -// store2 -// NilCheck that depends on store2 -// other values that depends on store2 -// ... +// +// store1 +// NilCheck that depends on store1 +// other values that depends on store1 +// store2 +// NilCheck that depends on store2 +// other values that depends on store2 +// ... +// // The order of non-store and non-NilCheck values are undefined // (not necessarily dependency order). This should be cheaper // than a full scheduling as done above. diff --git a/src/cmd/compile/internal/ssa/shift_test.go b/src/cmd/compile/internal/ssa/shift_test.go index 3876d8df12..06c2f6720f 100644 --- a/src/cmd/compile/internal/ssa/shift_test.go +++ b/src/cmd/compile/internal/ssa/shift_test.go @@ -85,7 +85,7 @@ func TestShiftToExtensionAMD64(t *testing.T) { // makeShiftExtensionFunc generates a function containing: // -// (rshift (lshift (Const64 [amount])) (Const64 [amount])) +// (rshift (lshift (Const64 [amount])) (Const64 [amount])) // // This may be equivalent to a sign or zero extension. func makeShiftExtensionFunc(c *Conf, amount int64, lshift, rshift Op, typ *types.Type) fun { diff --git a/src/cmd/compile/internal/ssa/shortcircuit.go b/src/cmd/compile/internal/ssa/shortcircuit.go index c0b9eacf41..5f1f892120 100644 --- a/src/cmd/compile/internal/ssa/shortcircuit.go +++ b/src/cmd/compile/internal/ssa/shortcircuit.go @@ -67,11 +67,11 @@ func shortcircuit(f *Func) { // // (1) Look for a CFG of the form // -// p other pred(s) -// \ / -// b -// / \ -// t other succ +// p other pred(s) +// \ / +// b +// / \ +// t other succ // // in which b is an If block containing a single phi value with a single use (b's Control), // which has a ConstBool arg. @@ -80,21 +80,21 @@ func shortcircuit(f *Func) { // // Rewrite this into // -// p other pred(s) -// | / -// | b -// |/ \ -// t u +// p other pred(s) +// | / +// | b +// |/ \ +// t u // // and remove the appropriate phi arg(s). // // (2) Look for a CFG of the form // -// p q -// \ / -// b -// / \ -// t u +// p q +// \ / +// b +// / \ +// t u // // in which b is as described in (1). // However, b may also contain other phi values. diff --git a/src/cmd/compile/internal/ssa/sparsetree.go b/src/cmd/compile/internal/ssa/sparsetree.go index 732bb8e321..9f4e0007d3 100644 --- a/src/cmd/compile/internal/ssa/sparsetree.go +++ b/src/cmd/compile/internal/ssa/sparsetree.go @@ -210,6 +210,7 @@ func (t SparseTree) isAncestor(x, y *Block) bool { // 1. If domorder(x) > domorder(y) then x does not dominate y. // 2. If domorder(x) < domorder(y) and domorder(y) < domorder(z) and x does not dominate y, // then x does not dominate z. +// // Property (1) means that blocks sorted by domorder always have a maximal dominant block first. // Property (2) allows searches for dominated blocks to exit early. func (t SparseTree) domorder(x *Block) int32 { diff --git a/src/cmd/compile/internal/ssa/trim.go b/src/cmd/compile/internal/ssa/trim.go index c930a205c1..1fd7b33d5f 100644 --- a/src/cmd/compile/internal/ssa/trim.go +++ b/src/cmd/compile/internal/ssa/trim.go @@ -130,11 +130,11 @@ func emptyBlock(b *Block) bool { // trimmableBlock reports whether the block can be trimmed from the CFG, // subject to the following criteria: -// - it should not be the first block -// - it should be BlockPlain -// - it should not loop back to itself -// - it either is the single predecessor of the successor block or -// contains no actual instructions +// - it should not be the first block +// - it should be BlockPlain +// - it should not loop back to itself +// - it either is the single predecessor of the successor block or +// contains no actual instructions func trimmableBlock(b *Block) bool { if b.Kind != BlockPlain || b == b.Func.Entry { return false diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index 7b411a4612..8f125cef99 100644 --- a/src/cmd/compile/internal/ssa/value.go +++ b/src/cmd/compile/internal/ssa/value.go @@ -228,6 +228,7 @@ func (v *Value) auxString() string { // If/when midstack inlining is enabled (-l=4), the compiler gets both larger and slower. // Not-inlining this method is a help (*Value.reset and *Block.NewValue0 are similar). +// //go:noinline func (v *Value) AddArg(w *Value) { if v.Args == nil { @@ -331,6 +332,7 @@ func (v *Value) resetArgs() { // reset is called from most rewrite rules. // Allowing it to be inlined increases the size // of cmd/compile by almost 10%, and slows it down. +// //go:noinline func (v *Value) reset(op Op) { if v.InCache { @@ -377,6 +379,7 @@ func (v *Value) invalidateRecursively() bool { // copyOf is called from rewrite rules. // It modifies v to be (Copy a). +// //go:noinline func (v *Value) copyOf(a *Value) { if v == a { diff --git a/src/cmd/compile/internal/ssa/writebarrier.go b/src/cmd/compile/internal/ssa/writebarrier.go index 21eee12c85..65ff960c84 100644 --- a/src/cmd/compile/internal/ssa/writebarrier.go +++ b/src/cmd/compile/internal/ssa/writebarrier.go @@ -486,7 +486,7 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va inRegs := b.Func.ABIDefault == b.Func.ABI1 && len(config.intParamRegs) >= 3 // put arguments on stack - off := config.ctxt.FixedFrameSize() + off := config.ctxt.Arch.FixedFrameSize var argTypes []*types.Type if typ != nil { // for typedmemmove @@ -529,7 +529,7 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va // issue call call := b.NewValue0A(pos, OpStaticCall, types.TypeResultMem, StaticAuxCall(fn, b.Func.ABIDefault.ABIAnalyzeTypes(nil, argTypes, nil))) call.AddArgs(wbargs...) - call.AuxInt = off - config.ctxt.FixedFrameSize() + call.AuxInt = off - config.ctxt.Arch.FixedFrameSize return b.NewValue1I(pos, OpSelectN, types.TypeMem, 0, call) } @@ -629,7 +629,7 @@ func IsNewObject(v *Value) (mem *Value, ok bool) { if v.Args[0].Args[0].Op != OpSP { return nil, false } - if v.Args[0].AuxInt != c.ctxt.FixedFrameSize()+c.RegSize { // offset of return value + if v.Args[0].AuxInt != c.ctxt.Arch.FixedFrameSize+c.RegSize { // offset of return value return nil, false } return mem, true diff --git a/src/cmd/compile/internal/ssagen/pgen.go b/src/cmd/compile/internal/ssagen/pgen.go index 86d40e239d..825b32aa80 100644 --- a/src/cmd/compile/internal/ssagen/pgen.go +++ b/src/cmd/compile/internal/ssagen/pgen.go @@ -225,13 +225,13 @@ func StackOffset(slot ssa.LocalSlot) int32 { switch n.Class { case ir.PPARAM, ir.PPARAMOUT: if !n.IsOutputParamInRegisters() { - off = n.FrameOffset() + base.Ctxt.FixedFrameSize() + off = n.FrameOffset() + base.Ctxt.Arch.FixedFrameSize break } fallthrough // PPARAMOUT in registers allocates like an AUTO case ir.PAUTO: off = n.FrameOffset() - if base.Ctxt.FixedFrameSize() == 0 { + if base.Ctxt.Arch.FixedFrameSize == 0 { off -= int64(types.PtrSize) } if buildcfg.FramePointerEnabled { diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 883772b341..adb95445c4 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -286,10 +286,10 @@ func dvarint(x *obj.LSym, off int, v int64) int { // for stack variables are specified as the number of bytes below varp (pointer to the // top of the local variables) for their starting address. The format is: // -// - Offset of the deferBits variable -// - Number of defers in the function -// - Information about each defer call, in reverse order of appearance in the function: -// - Offset of the closure value to call +// - Offset of the deferBits variable +// - Number of defers in the function +// - Information about each defer call, in reverse order of appearance in the function: +// - Offset of the closure value to call func (s *state) emitOpenDeferInfo() { x := base.Ctxt.Lookup(s.curfn.LSym.Name + ".opendefer") x.Set(obj.AttrContentAddressable, true) @@ -1861,6 +1861,84 @@ func (s *state) stmt(n ir.Node) { } s.startBlock(bEnd) + case ir.OJUMPTABLE: + n := n.(*ir.JumpTableStmt) + + // Make blocks we'll need. + jt := s.f.NewBlock(ssa.BlockJumpTable) + bEnd := s.f.NewBlock(ssa.BlockPlain) + + // The only thing that needs evaluating is the index we're looking up. + idx := s.expr(n.Idx) + unsigned := idx.Type.IsUnsigned() + + // Extend so we can do everything in uintptr arithmetic. + t := types.Types[types.TUINTPTR] + idx = s.conv(nil, idx, idx.Type, t) + + // The ending condition for the current block decides whether we'll use + // the jump table at all. + // We check that min <= idx <= max and jump around the jump table + // if that test fails. + // We implement min <= idx <= max with 0 <= idx-min <= max-min, because + // we'll need idx-min anyway as the control value for the jump table. + var min, max uint64 + if unsigned { + min, _ = constant.Uint64Val(n.Cases[0]) + max, _ = constant.Uint64Val(n.Cases[len(n.Cases)-1]) + } else { + mn, _ := constant.Int64Val(n.Cases[0]) + mx, _ := constant.Int64Val(n.Cases[len(n.Cases)-1]) + min = uint64(mn) + max = uint64(mx) + } + // Compare idx-min with max-min, to see if we can use the jump table. + idx = s.newValue2(s.ssaOp(ir.OSUB, t), t, idx, s.uintptrConstant(min)) + width := s.uintptrConstant(max - min) + cmp := s.newValue2(s.ssaOp(ir.OLE, t), types.Types[types.TBOOL], idx, width) + b := s.endBlock() + b.Kind = ssa.BlockIf + b.SetControl(cmp) + b.AddEdgeTo(jt) // in range - use jump table + b.AddEdgeTo(bEnd) // out of range - no case in the jump table will trigger + b.Likely = ssa.BranchLikely // TODO: assumes missing the table entirely is unlikely. True? + + // Build jump table block. + s.startBlock(jt) + jt.Pos = n.Pos() + if base.Flag.Cfg.SpectreIndex { + idx = s.newValue2(ssa.OpSpectreSliceIndex, t, idx, width) + } + jt.SetControl(idx) + + // Figure out where we should go for each index in the table. + table := make([]*ssa.Block, max-min+1) + for i := range table { + table[i] = bEnd // default target + } + for i := range n.Targets { + c := n.Cases[i] + lab := s.label(n.Targets[i]) + if lab.target == nil { + lab.target = s.f.NewBlock(ssa.BlockPlain) + } + var val uint64 + if unsigned { + val, _ = constant.Uint64Val(c) + } else { + vl, _ := constant.Int64Val(c) + val = uint64(vl) + } + // Overwrite the default target. + table[val-min] = lab.target + } + for _, t := range table { + jt.AddEdgeTo(t) + } + s.endBlock() + + s.startBlock(bEnd) + case ir.OVARDEF: n := n.(*ir.UnaryExpr) if !s.canSSA(n.X) { @@ -2351,6 +2429,13 @@ func (s *state) ssaShiftOp(op ir.Op, t *types.Type, u *types.Type) ssa.Op { return x } +func (s *state) uintptrConstant(v uint64) *ssa.Value { + if s.config.PtrSize == 4 { + return s.newValue0I(ssa.OpConst32, types.Types[types.TUINTPTR], int64(v)) + } + return s.newValue0I(ssa.OpConst64, types.Types[types.TUINTPTR], int64(v)) +} + func (s *state) conv(n ir.Node, v *ssa.Value, ft, tt *types.Type) *ssa.Value { if ft.IsBoolean() && tt.IsKind(types.TUINT8) { // Bool -> uint8 is generated internally when indexing into runtime.staticbyte. @@ -5017,7 +5102,7 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val } else { // Store arguments to stack, including defer/go arguments and receiver for method calls. // These are written in SP-offset order. - argStart := base.Ctxt.FixedFrameSize() + argStart := base.Ctxt.Arch.FixedFrameSize // Defer/go args. if k != callNormal && k != callTail { // Write closure (arg to newproc/deferproc). @@ -5521,7 +5606,7 @@ func (s *state) intDivide(n ir.Node, a, b *ssa.Value) *ssa.Value { func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args ...*ssa.Value) []*ssa.Value { s.prevCall = nil // Write args to the stack - off := base.Ctxt.FixedFrameSize() + off := base.Ctxt.Arch.FixedFrameSize var callArgs []*ssa.Value var callArgTypes []*types.Type @@ -5535,13 +5620,6 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args . } off = types.Rnd(off, int64(types.RegSize)) - // Accumulate results types and offsets - offR := off - for _, t := range results { - offR = types.Rnd(offR, t.Alignment()) - offR += t.Size() - } - // Issue call var call *ssa.Value aux := ssa.StaticAuxCall(fn, s.f.ABIDefault.ABIAnalyzeTypes(nil, callArgTypes, results)) @@ -5555,7 +5633,7 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args . b := s.endBlock() b.Kind = ssa.BlockExit b.SetControl(call) - call.AuxInt = off - base.Ctxt.FixedFrameSize() + call.AuxInt = off - base.Ctxt.Arch.FixedFrameSize if len(results) > 0 { s.Fatalf("panic call can't have results") } @@ -6447,6 +6525,9 @@ type State struct { // and where they would like to go. Branches []Branch + // JumpTables remembers all the jump tables we've seen. + JumpTables []*ssa.Block + // bstart remembers where each block starts (indexed by block ID) bstart []*obj.Prog @@ -7059,6 +7140,20 @@ func genssa(f *ssa.Func, pp *objw.Progs) { } + // Resolve jump table destinations. + for _, jt := range s.JumpTables { + // Convert from *Block targets to *Prog targets. + targets := make([]*obj.Prog, len(jt.Succs)) + for i, e := range jt.Succs { + targets[i] = s.bstart[e.Block().ID] + } + // Add to list of jump tables to be resolved at assembly time. + // The assembler converts from *Prog entries to absolute addresses + // once it knows instruction byte offsets. + fi := pp.CurFunc.LSym.Func() + fi.JumpTables = append(fi.JumpTables, obj.JumpTable{Sym: jt.Aux.(*obj.LSym), Targets: targets}) + } + if e.log { // spew to stdout filename := "" for p := pp.Text; p != nil; p = p.Link { @@ -7712,6 +7807,10 @@ func (e *ssafn) MyImportPath() string { return base.Ctxt.Pkgpath } +func (e *ssafn) LSym() string { + return e.curfn.LSym.Name +} + func clobberBase(n ir.Node) ir.Node { if n.Op() == ir.ODOT { n := n.(*ir.SelectorExpr) diff --git a/src/cmd/compile/internal/syntax/branches.go b/src/cmd/compile/internal/syntax/branches.go index 56e97c71d8..6079097426 100644 --- a/src/cmd/compile/internal/syntax/branches.go +++ b/src/cmd/compile/internal/syntax/branches.go @@ -11,10 +11,10 @@ import "fmt" // checkBranches checks correct use of labels and branch // statements (break, continue, goto) in a function body. // It catches: -// - misplaced breaks and continues -// - bad labeled breaks and continues -// - invalid, unused, duplicate, and missing labels -// - gotos jumping over variable declarations and into blocks +// - misplaced breaks and continues +// - bad labeled breaks and continues +// - invalid, unused, duplicate, and missing labels +// - gotos jumping over variable declarations and into blocks func checkBranches(body *BlockStmt, errh ErrorHandler) { if body == nil { return diff --git a/src/cmd/compile/internal/test/float_test.go b/src/cmd/compile/internal/test/float_test.go index 884a983bdd..c736f970f9 100644 --- a/src/cmd/compile/internal/test/float_test.go +++ b/src/cmd/compile/internal/test/float_test.go @@ -170,6 +170,7 @@ func cvt8(a float32) int32 { } // make sure to cover int, uint cases (issue #16738) +// //go:noinline func cvt9(a float64) int { return int(a) diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go index b10d37a17c..211068e1dc 100644 --- a/src/cmd/compile/internal/test/inl_test.go +++ b/src/cmd/compile/internal/test/inl_test.go @@ -136,6 +136,7 @@ func TestIntendedInlining(t *testing.T) { "Value.CanSet", "Value.CanInterface", "Value.IsValid", + "Value.MapRange", "Value.pointer", "add", "align", diff --git a/src/cmd/compile/internal/test/shift_test.go b/src/cmd/compile/internal/test/shift_test.go index ea88f0a70a..58c8dde1a0 100644 --- a/src/cmd/compile/internal/test/shift_test.go +++ b/src/cmd/compile/internal/test/shift_test.go @@ -1029,3 +1029,13 @@ func TestShiftGeneric(t *testing.T) { } } } + +var shiftSink64 int64 + +func BenchmarkShiftArithmeticRight(b *testing.B) { + x := shiftSink64 + for i := 0; i < b.N; i++ { + x = x >> (i & 63) + } + shiftSink64 = x +} diff --git a/src/cmd/compile/internal/test/switch_test.go b/src/cmd/compile/internal/test/switch_test.go new file mode 100644 index 0000000000..30dee6257e --- /dev/null +++ b/src/cmd/compile/internal/test/switch_test.go @@ -0,0 +1,137 @@ +// 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 test + +import ( + "math/bits" + "testing" +) + +func BenchmarkSwitch8Predictable(b *testing.B) { + benchmarkSwitch8(b, true) +} +func BenchmarkSwitch8Unpredictable(b *testing.B) { + benchmarkSwitch8(b, false) +} +func benchmarkSwitch8(b *testing.B, predictable bool) { + n := 0 + rng := newRNG() + for i := 0; i < b.N; i++ { + rng = rng.next(predictable) + switch rng.value() & 7 { + case 0: + n += 1 + case 1: + n += 2 + case 2: + n += 3 + case 3: + n += 4 + case 4: + n += 5 + case 5: + n += 6 + case 6: + n += 7 + case 7: + n += 8 + } + } + sink = n +} + +func BenchmarkSwitch32Predictable(b *testing.B) { + benchmarkSwitch32(b, true) +} +func BenchmarkSwitch32Unpredictable(b *testing.B) { + benchmarkSwitch32(b, false) +} +func benchmarkSwitch32(b *testing.B, predictable bool) { + n := 0 + rng := newRNG() + for i := 0; i < b.N; i++ { + rng = rng.next(predictable) + switch rng.value() & 31 { + case 0, 1, 2: + n += 1 + case 4, 5, 6: + n += 2 + case 8, 9, 10: + n += 3 + case 12, 13, 14: + n += 4 + case 16, 17, 18: + n += 5 + case 20, 21, 22: + n += 6 + case 24, 25, 26: + n += 7 + case 28, 29, 30: + n += 8 + default: + n += 9 + } + } + sink = n +} + +func BenchmarkSwitchStringPredictable(b *testing.B) { + benchmarkSwitchString(b, true) +} +func BenchmarkSwitchStringUnpredictable(b *testing.B) { + benchmarkSwitchString(b, false) +} +func benchmarkSwitchString(b *testing.B, predictable bool) { + a := []string{ + "foo", + "foo1", + "foo22", + "foo333", + "foo4444", + "foo55555", + "foo666666", + "foo7777777", + } + n := 0 + rng := newRNG() + for i := 0; i < b.N; i++ { + rng = rng.next(predictable) + switch a[rng.value()&7] { + case "foo": + n += 1 + case "foo1": + n += 2 + case "foo22": + n += 3 + case "foo333": + n += 4 + case "foo4444": + n += 5 + case "foo55555": + n += 6 + case "foo666666": + n += 7 + case "foo7777777": + n += 8 + } + } + sink = n +} + +// A simple random number generator used to make switches conditionally predictable. +type rng uint64 + +func newRNG() rng { + return 1 +} +func (r rng) next(predictable bool) rng { + if predictable { + return r + 1 + } + return rng(bits.RotateLeft64(uint64(r), 13) * 0x3c374d) +} +func (r rng) value() uint64 { + return uint64(r) +} diff --git a/src/cmd/compile/internal/test/testdata/addressed_test.go b/src/cmd/compile/internal/test/testdata/addressed_test.go index cdabf978f0..4cc9ac4d5b 100644 --- a/src/cmd/compile/internal/test/testdata/addressed_test.go +++ b/src/cmd/compile/internal/test/testdata/addressed_test.go @@ -145,6 +145,7 @@ func (v V) val() int64 { // and y.val() should be equal to which and y.p.val() should // be equal to z.val(). Also, x(.p)**8 == x; that is, the // autos are all linked into a ring. +// //go:noinline func (v V) autos_ssa(which, w1, x1, w2, x2 int64) (y, z V) { fill_ssa(v.w, v.x, &v, v.p) // gratuitous no-op to force addressing @@ -191,6 +192,7 @@ func (v V) autos_ssa(which, w1, x1, w2, x2 int64) (y, z V) { // gets is an address-mentioning way of implementing // structure assignment. +// //go:noinline func (to *V) gets(from *V) { *to = *from @@ -198,12 +200,14 @@ func (to *V) gets(from *V) { // gets is an address-and-interface-mentioning way of // implementing structure assignment. +// //go:noinline func (to *V) getsI(from interface{}) { *to = *from.(*V) } // fill_ssa initializes r with V{w:w, x:x, p:p} +// //go:noinline func fill_ssa(w, x int64, r, p *V) { *r = V{w: w, x: x, p: p} diff --git a/src/cmd/compile/internal/test/testdata/arith_test.go b/src/cmd/compile/internal/test/testdata/arith_test.go index 7d54a9181d..253142a0fb 100644 --- a/src/cmd/compile/internal/test/testdata/arith_test.go +++ b/src/cmd/compile/internal/test/testdata/arith_test.go @@ -225,6 +225,7 @@ func testArithConstShift(t *testing.T) { // overflowConstShift_ssa verifes that constant folding for shift // doesn't wrap (i.e. x << MAX_INT << 1 doesn't get folded to x << 0). +// //go:noinline func overflowConstShift64_ssa(x int64) int64 { return x << uint64(0xffffffffffffffff) << uint64(1) diff --git a/src/cmd/compile/internal/test/testdata/ctl_test.go b/src/cmd/compile/internal/test/testdata/ctl_test.go index 16d571ce2c..ff3a1609c5 100644 --- a/src/cmd/compile/internal/test/testdata/ctl_test.go +++ b/src/cmd/compile/internal/test/testdata/ctl_test.go @@ -117,6 +117,7 @@ type junk struct { // flagOverwrite_ssa is intended to reproduce an issue seen where a XOR // was scheduled between a compare and branch, clearing flags. +// //go:noinline func flagOverwrite_ssa(s *junk, c int) int { if '0' <= c && c <= '9' { diff --git a/src/cmd/compile/internal/test/testdata/fp_test.go b/src/cmd/compile/internal/test/testdata/fp_test.go index 7d61a8063e..b96ce84a6c 100644 --- a/src/cmd/compile/internal/test/testdata/fp_test.go +++ b/src/cmd/compile/internal/test/testdata/fp_test.go @@ -14,6 +14,7 @@ import ( // manysub_ssa is designed to tickle bugs that depend on register // pressure or unfriendly operand ordering in registers (and at // least once it succeeded in this). +// //go:noinline func manysub_ssa(a, b, c, d float64) (aa, ab, ac, ad, ba, bb, bc, bd, ca, cb, cc, cd, da, db, dc, dd float64) { aa = a + 11.0 - a @@ -37,6 +38,7 @@ func manysub_ssa(a, b, c, d float64) (aa, ab, ac, ad, ba, bb, bc, bd, ca, cb, cc // fpspill_ssa attempts to trigger a bug where phis with floating point values // were stored in non-fp registers causing an error in doasm. +// //go:noinline func fpspill_ssa(a int) float64 { diff --git a/src/cmd/compile/internal/test/testdata/loadstore_test.go b/src/cmd/compile/internal/test/testdata/loadstore_test.go index 57571f5d17..052172819a 100644 --- a/src/cmd/compile/internal/test/testdata/loadstore_test.go +++ b/src/cmd/compile/internal/test/testdata/loadstore_test.go @@ -73,6 +73,7 @@ var b int // testDeadStorePanic_ssa ensures that we don't optimize away stores // that could be read by after recover(). Modeled after fixedbugs/issue1304. +// //go:noinline func testDeadStorePanic_ssa(a int) (r int) { defer func() { diff --git a/src/cmd/compile/internal/typecheck/builtin.go b/src/cmd/compile/internal/typecheck/builtin.go index 67597cebb4..581928c005 100644 --- a/src/cmd/compile/internal/typecheck/builtin.go +++ b/src/cmd/compile/internal/typecheck/builtin.go @@ -212,6 +212,7 @@ var runtimeDecls = [...]struct { } // Not inlining this function removes a significant chunk of init code. +// //go:noinline func newSig(params, results []*types.Field) *types.Type { return types.NewSignature(types.NoPkg, nil, nil, params, results) diff --git a/src/cmd/compile/internal/typecheck/const.go b/src/cmd/compile/internal/typecheck/const.go index 1422ab0031..a626c000be 100644 --- a/src/cmd/compile/internal/typecheck/const.go +++ b/src/cmd/compile/internal/typecheck/const.go @@ -620,7 +620,8 @@ func OrigInt(n ir.Node, v int64) ir.Node { // get the same type going out. // force means must assign concrete (non-ideal) type. // The results of defaultlit2 MUST be assigned back to l and r, e.g. -// n.Left, n.Right = defaultlit2(n.Left, n.Right, force) +// +// n.Left, n.Right = defaultlit2(n.Left, n.Right, force) func defaultlit2(l ir.Node, r ir.Node, force bool) (ir.Node, ir.Node) { if l.Type() == nil || r.Type() == nil { return l, r diff --git a/src/cmd/compile/internal/typecheck/expr.go b/src/cmd/compile/internal/typecheck/expr.go index e6adc05a65..f0b7b74aed 100644 --- a/src/cmd/compile/internal/typecheck/expr.go +++ b/src/cmd/compile/internal/typecheck/expr.go @@ -76,8 +76,9 @@ func tcShift(n, l, r ir.Node) (ir.Node, ir.Node, *types.Type) { // tcArith typechecks operands of a binary arithmetic expression. // The result of tcArith MUST be assigned back to original operands, // t is the type of the expression, and should be set by the caller. e.g: -// n.X, n.Y, t = tcArith(n, op, n.X, n.Y) -// n.SetType(t) +// +// n.X, n.Y, t = tcArith(n, op, n.X, n.Y) +// n.SetType(t) func tcArith(n ir.Node, op ir.Op, l, r ir.Node) (ir.Node, ir.Node, *types.Type) { l, r = defaultlit2(l, r, false) if l.Type() == nil || r.Type() == nil { @@ -194,7 +195,8 @@ func tcArith(n ir.Node, op ir.Op, l, r ir.Node) (ir.Node, ir.Node, *types.Type) } // The result of tcCompLit MUST be assigned back to n, e.g. -// n.Left = tcCompLit(n.Left) +// +// n.Left = tcCompLit(n.Left) func tcCompLit(n *ir.CompLitExpr) (res ir.Node) { if base.EnableTrace && base.Flag.LowerT { defer tracePrint("tcCompLit", n)(&res) diff --git a/src/cmd/compile/internal/typecheck/iexport.go b/src/cmd/compile/internal/typecheck/iexport.go index 5d319eaca3..12159b71e1 100644 --- a/src/cmd/compile/internal/typecheck/iexport.go +++ b/src/cmd/compile/internal/typecheck/iexport.go @@ -258,7 +258,7 @@ import ( // 1: added column details to Pos // 2: added information for generic function/types. The export of non-generic // functions/types remains largely backward-compatible. Breaking changes include: -// - a 'kind' byte is added to constant values +// - a 'kind' byte is added to constant values const ( iexportVersionGo1_11 = 0 iexportVersionPosCol = 1 diff --git a/src/cmd/compile/internal/typecheck/mkbuiltin.go b/src/cmd/compile/internal/typecheck/mkbuiltin.go index 6dbd1869b3..9b27557956 100644 --- a/src/cmd/compile/internal/typecheck/mkbuiltin.go +++ b/src/cmd/compile/internal/typecheck/mkbuiltin.go @@ -105,6 +105,7 @@ func mkbuiltin(w io.Writer, name string) { fmt.Fprintln(w, ` // Not inlining this function removes a significant chunk of init code. +// //go:noinline func newSig(params, results []*types.Field) *types.Type { return types.NewSignature(types.NoPkg, nil, nil, params, results) diff --git a/src/cmd/compile/internal/typecheck/syms.go b/src/cmd/compile/internal/typecheck/syms.go index 6c2e84680b..1f60f31851 100644 --- a/src/cmd/compile/internal/typecheck/syms.go +++ b/src/cmd/compile/internal/typecheck/syms.go @@ -24,7 +24,8 @@ func LookupRuntime(name string) *ir.Name { // successive occurrences of the "any" placeholder in the // type syntax expression n.Type. // The result of SubstArgTypes MUST be assigned back to old, e.g. -// n.Left = SubstArgTypes(n.Left, t1, t2) +// +// n.Left = SubstArgTypes(n.Left, t1, t2) func SubstArgTypes(old *ir.Name, types_ ...*types.Type) *ir.Name { for _, t := range types_ { types.CalcSize(t) diff --git a/src/cmd/compile/internal/typecheck/typecheck.go b/src/cmd/compile/internal/typecheck/typecheck.go index 85de653a82..2eb9e6d718 100644 --- a/src/cmd/compile/internal/typecheck/typecheck.go +++ b/src/cmd/compile/internal/typecheck/typecheck.go @@ -240,7 +240,8 @@ func typecheckNtype(n ir.Ntype) ir.Ntype { // typecheck type checks node n. // The result of typecheck MUST be assigned back to n, e.g. -// n.Left = typecheck(n.Left, top) +// +// n.Left = typecheck(n.Left, top) func typecheck(n ir.Node, top int) (res ir.Node) { // cannot type check until all the source has been parsed if !TypecheckAllowed { @@ -414,7 +415,8 @@ func typecheck(n ir.Node, top int) (res ir.Node) { // but also accepts untyped numeric values representable as // value of type int (see also checkmake for comparison). // The result of indexlit MUST be assigned back to n, e.g. -// n.Left = indexlit(n.Left) +// +// n.Left = indexlit(n.Left) func indexlit(n ir.Node) ir.Node { if n != nil && n.Type() != nil && n.Type().Kind() == types.TIDEAL { return DefaultLit(n, types.Types[types.TINT]) @@ -961,7 +963,8 @@ func checksliceconst(lo ir.Node, hi ir.Node) bool { } // The result of implicitstar MUST be assigned back to n, e.g. -// n.Left = implicitstar(n.Left) +// +// n.Left = implicitstar(n.Left) func implicitstar(n ir.Node) ir.Node { // insert implicit * if needed for fixed array t := n.Type() @@ -1607,7 +1610,8 @@ func checkassignto(src *types.Type, dst ir.Node) { } // The result of stringtoruneslit MUST be assigned back to n, e.g. -// n.Left = stringtoruneslit(n.Left) +// +// n.Left = stringtoruneslit(n.Left) func stringtoruneslit(n *ir.ConvExpr) ir.Node { if n.X.Op() != ir.OLITERAL || n.X.Val().Kind() != constant.String { base.Fatalf("stringtoarraylit %v", n) diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index 147194c369..987352babc 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -492,9 +492,9 @@ type Slice struct { // A Field is a (Sym, Type) pairing along with some other information, and, // depending on the context, is used to represent: -// - a field in a struct -// - a method in an interface or associated with a named type -// - a function parameter +// - a field in a struct +// - a method in an interface or associated with a named type +// - a function parameter type Field struct { flags bitset8 @@ -1121,9 +1121,10 @@ func (t *Type) SimpleString() string { } // Cmp is a comparison between values a and b. -// -1 if a < b -// 0 if a == b -// 1 if a > b +// +// -1 if a < b +// 0 if a == b +// 1 if a > b type Cmp int8 const ( diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go index 34bb29cadc..54cddaee28 100644 --- a/src/cmd/compile/internal/types2/api.go +++ b/src/cmd/compile/internal/types2/api.go @@ -21,7 +21,6 @@ // Type inference computes the type (Type) of every expression (syntax.Expr) // and checks for compliance with the language specification. // Use Info.Types[expr].Type for the results of type inference. -// package types2 import ( diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index 528beaacea..fde7291b03 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go @@ -311,6 +311,18 @@ func TestTypesInfo(t *testing.T) { `[][]struct{}`, }, + // issue 47243 + {`package issue47243_a; var x int32; var _ = x << 3`, `3`, `untyped int`}, + {`package issue47243_b; var x int32; var _ = x << 3.`, `3.`, `untyped float`}, + {`package issue47243_c; var x int32; var _ = 1 << x`, `1 << x`, `int`}, + {`package issue47243_d; var x int32; var _ = 1 << x`, `1`, `int`}, + {`package issue47243_e; var x int32; var _ = 1 << 2`, `1`, `untyped int`}, + {`package issue47243_f; var x int32; var _ = 1 << 2`, `2`, `untyped int`}, + {`package issue47243_g; var x int32; var _ = int(1) << 2`, `2`, `untyped int`}, + {`package issue47243_h; var x int32; var _ = 1 << (2 << x)`, `1`, `int`}, + {`package issue47243_i; var x int32; var _ = 1 << (2 << x)`, `(2 << x)`, `untyped int`}, + {`package issue47243_j; var x int32; var _ = 1 << (2 << x)`, `2`, `untyped int`}, + // tests for broken code that doesn't parse or type-check {brokenPkg + `x0; func _() { var x struct {f string}; x.f := 0 }`, `x.f`, `string`}, {brokenPkg + `x1; func _() { var z string; type x struct {f string}; y := &x{q: z}}`, `z`, `string`}, diff --git a/src/cmd/compile/internal/types2/check_test.go b/src/cmd/compile/internal/types2/check_test.go index ec242c5e22..2e1ae0d2be 100644 --- a/src/cmd/compile/internal/types2/check_test.go +++ b/src/cmd/compile/internal/types2/check_test.go @@ -263,7 +263,7 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) { // (and a separating "--"). For instance, to test the package made // of the files foo.go and bar.go, use: // -// go test -run Manual -- foo.go bar.go +// go test -run Manual -- foo.go bar.go // // If no source arguments are provided, the file testdata/manual.go // is used instead. diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 1ecb4ff54b..e0c22f5b03 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -954,32 +954,48 @@ func (check *Checker) shift(x, y *operand, e syntax.Expr, op syntax.Operator) { // spec: "The right operand in a shift expression must have integer type // or be an untyped constant representable by a value of type uint." - // Provide a good error message for negative shift counts. + // Check that constants are representable by uint, but do not convert them + // (see also issue #47243). if y.mode == constant_ { + // Provide a good error message for negative shift counts. yval := constant.ToInt(y.val) // consider -1, 1.0, but not -1.1 if yval.Kind() == constant.Int && constant.Sign(yval) < 0 { check.errorf(y, invalidOp+"negative shift count %s", y) x.mode = invalid return } - } - // Caution: Check for isUntyped first because isInteger includes untyped - // integers (was bug #43697). - if isUntyped(y.typ) { - check.convertUntyped(y, Typ[Uint]) - if y.mode == invalid { + if isUntyped(y.typ) { + // Caution: Check for representability here, rather than in the switch + // below, because isInteger includes untyped integers (was bug #43697). + check.representable(y, Typ[Uint]) + if y.mode == invalid { + x.mode = invalid + return + } + } + } else { + // Check that RHS is otherwise at least of integer type. + switch { + case allInteger(y.typ): + if !allUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) { + check.errorf(y, invalidOp+"signed shift count %s requires go1.13 or later", y) + x.mode = invalid + return + } + case isUntyped(y.typ): + // This is incorrect, but preserves pre-existing behavior. + // See also bug #47410. + check.convertUntyped(y, Typ[Uint]) + if y.mode == invalid { + x.mode = invalid + return + } + default: + check.errorf(y, invalidOp+"shift count %s must be integer", y) x.mode = invalid return } - } else if !allInteger(y.typ) { - check.errorf(y, invalidOp+"shift count %s must be integer", y) - x.mode = invalid - return - } else if !allUnsigned(y.typ) && !check.allowVersion(check.pkg, 1, 13) { - check.versionErrorf(y, "go1.13", invalidOp+"signed shift count %s", y) - x.mode = invalid - return } if x.mode == constant_ { diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 9f7e593eeb..9e77d67a7d 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -23,10 +23,10 @@ const useConstraintTypeInference = true // // Inference proceeds as follows. Starting with given type arguments: // -// 1) apply FTI (function type inference) with typed arguments, -// 2) apply CTI (constraint type inference), -// 3) apply FTI with untyped function arguments, -// 4) apply CTI. +// 1. apply FTI (function type inference) with typed arguments, +// 2. apply CTI (constraint type inference), +// 3. apply FTI with untyped function arguments, +// 4. apply CTI. // // The process stops as soon as all type arguments are known or an error occurs. func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type, params *Tuple, args []*operand) (result []Type) { diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go index 93defd6618..684bbf7a8b 100644 --- a/src/cmd/compile/internal/types2/lookup.go +++ b/src/cmd/compile/internal/types2/lookup.go @@ -25,9 +25,9 @@ import ( // The last index entry is the field or method index in the (possibly embedded) // type where the entry was found, either: // -// 1) the list of declared methods of a named type; or -// 2) the list of all methods (method set) of an interface type; or -// 3) the list of fields of a struct type. +// 1. the list of declared methods of a named type; or +// 2. the list of all methods (method set) of an interface type; or +// 3. the list of fields of a struct type. // // The earlier index entries are the indices of the embedded struct fields // traversed to get to the found entry, starting at depth 0. @@ -35,12 +35,12 @@ import ( // If no entry is found, a nil object is returned. In this case, the returned // index and indirect values have the following meaning: // -// - If index != nil, the index sequence points to an ambiguous entry -// (the same name appeared more than once at the same embedding level). +// - If index != nil, the index sequence points to an ambiguous entry +// (the same name appeared more than once at the same embedding level). // -// - If indirect is set, a method with a pointer receiver type was found -// but there was no pointer on the path from the actual receiver type to -// the method's formal receiver base type, nor was the receiver addressable. +// - If indirect is set, a method with a pointer receiver type was found +// but there was no pointer on the path from the actual receiver type to +// the method's formal receiver base type, nor was the receiver addressable. func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { if T == nil { panic("LookupFieldOrMethod on nil type") diff --git a/src/cmd/compile/internal/types2/selection.go b/src/cmd/compile/internal/types2/selection.go index ee63214407..c820a29fad 100644 --- a/src/cmd/compile/internal/types2/selection.go +++ b/src/cmd/compile/internal/types2/selection.go @@ -92,9 +92,9 @@ func (s *Selection) Type() Type { // The last index entry is the field or method index of the type declaring f; // either: // -// 1) the list of declared methods of a named type; or -// 2) the list of methods of an interface type; or -// 3) the list of fields of a struct type. +// 1. the list of declared methods of a named type; or +// 2. the list of methods of an interface type; or +// 3. the list of fields of a struct type. // // The earlier index entries are the indices of the embedded fields implicitly // traversed to get from (the type of) x to f, starting at embedding depth 0. @@ -111,6 +111,7 @@ func (s *Selection) String() string { return SelectionString(s, nil) } // package-level objects, and may be nil. // // Examples: +// // "field (T) f int" // "method (T) f(X) Y" // "method expr (T) f(X) Y" diff --git a/src/cmd/compile/internal/types2/sizes.go b/src/cmd/compile/internal/types2/sizes.go index 7a34b6474c..f530849a9d 100644 --- a/src/cmd/compile/internal/types2/sizes.go +++ b/src/cmd/compile/internal/types2/sizes.go @@ -24,19 +24,19 @@ type Sizes interface { // StdSizes is a convenience type for creating commonly used Sizes. // It makes the following simplifying assumptions: // -// - The size of explicitly sized basic types (int16, etc.) is the -// specified size. -// - The size of strings and interfaces is 2*WordSize. -// - The size of slices is 3*WordSize. -// - The size of an array of n elements corresponds to the size of -// a struct of n consecutive fields of the array's element type. -// - The size of a struct is the offset of the last field plus that -// field's size. As with all element types, if the struct is used -// in an array its size must first be aligned to a multiple of the -// struct's alignment. -// - All other types have size WordSize. -// - Arrays and structs are aligned per spec definition; all other -// types are naturally aligned with a maximum alignment MaxAlign. +// - The size of explicitly sized basic types (int16, etc.) is the +// specified size. +// - The size of strings and interfaces is 2*WordSize. +// - The size of slices is 3*WordSize. +// - The size of an array of n elements corresponds to the size of +// a struct of n consecutive fields of the array's element type. +// - The size of a struct is the offset of the last field plus that +// field's size. As with all element types, if the struct is used +// in an array its size must first be aligned to a multiple of the +// struct's alignment. +// - All other types have size WordSize. +// - Arrays and structs are aligned per spec definition; all other +// types are naturally aligned with a maximum alignment MaxAlign. // // *StdSizes implements Sizes. type StdSizes struct { diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52031.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52031.go new file mode 100644 index 0000000000..448a550b25 --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52031.go @@ -0,0 +1,33 @@ +// -lang=go1.12 + +// Copyright 2022 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 resultFlags uint + +// Example from #52031. +// +// The following shifts should not produce errors on Go < 1.13, as their +// untyped constant operands are representable by type uint. +const ( + _ resultFlags = (1 << iota) / 2 + + reportEqual + reportUnequal + reportByIgnore + reportByMethod + reportByFunc + reportByCycle +) + +// Invalid cases. +var x int = 1 +var _ = (8 << x /* ERROR "signed shift count .* requires go1.13 or later" */) + +const _ = (1 << 1.2 /* ERROR "truncated to uint" */) + +var y float64 +var _ = (1 << y /* ERROR "must be integer" */) diff --git a/src/cmd/compile/internal/types2/typeterm.go b/src/cmd/compile/internal/types2/typeterm.go index 3d82a37ab8..97791324e1 100644 --- a/src/cmd/compile/internal/types2/typeterm.go +++ b/src/cmd/compile/internal/types2/typeterm.go @@ -6,10 +6,10 @@ package types2 // A term describes elementary type sets: // -// ∅: (*term)(nil) == ∅ // set of no types (empty set) -// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) -// T: &term{false, T} == {T} // set of type T -// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t type term struct { tilde bool // valid if typ != nil typ Type diff --git a/src/cmd/compile/internal/walk/assign.go b/src/cmd/compile/internal/walk/assign.go index 9b09e097fa..c44d934f21 100644 --- a/src/cmd/compile/internal/walk/assign.go +++ b/src/cmd/compile/internal/walk/assign.go @@ -242,6 +242,7 @@ func walkReturn(n *ir.ReturnStmt) ir.Node { // check assign type list to // an expression list. called in +// // expr-list = func() func ascompatet(nl ir.Nodes, nr *types.Type) []ir.Node { if len(nl) != nr.NumFields() { @@ -273,6 +274,7 @@ func ascompatet(nl ir.Nodes, nr *types.Type) []ir.Node { // check assign expression list to // an expression list. called in +// // expr-list = expr-list func ascompatee(op ir.Op, nl, nr []ir.Node) []ir.Node { // cannot happen: should have been rejected during type checking @@ -455,17 +457,18 @@ func readsMemory(n ir.Node) bool { } // expand append(l1, l2...) to -// init { -// s := l1 -// n := len(s) + len(l2) -// // Compare as uint so growslice can panic on overflow. -// if uint(n) > uint(cap(s)) { -// s = growslice(s, n) -// } -// s = s[:n] -// memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T)) -// } -// s +// +// init { +// s := l1 +// n := len(s) + len(l2) +// // Compare as uint so growslice can panic on overflow. +// if uint(n) > uint(cap(s)) { +// s = growslice(s, n) +// } +// s = s[:n] +// memmove(&s[len(l1)], &l2[0], len(l2)*sizeof(T)) +// } +// s // // l2 is allowed to be a string. func appendSlice(n *ir.CallExpr, init *ir.Nodes) ir.Node { @@ -597,32 +600,33 @@ func isAppendOfMake(n ir.Node) bool { } // extendSlice rewrites append(l1, make([]T, l2)...) to -// init { -// if l2 >= 0 { // Empty if block here for more meaningful node.SetLikely(true) -// } else { -// panicmakeslicelen() -// } -// s := l1 -// n := len(s) + l2 -// // Compare n and s as uint so growslice can panic on overflow of len(s) + l2. -// // cap is a positive int and n can become negative when len(s) + l2 -// // overflows int. Interpreting n when negative as uint makes it larger -// // than cap(s). growslice will check the int n arg and panic if n is -// // negative. This prevents the overflow from being undetected. -// if uint(n) > uint(cap(s)) { -// s = growslice(T, s, n) -// } -// s = s[:n] -// lptr := &l1[0] -// sptr := &s[0] -// if lptr == sptr || !T.HasPointers() { -// // growslice did not clear the whole underlying array (or did not get called) -// hp := &s[len(l1)] -// hn := l2 * sizeof(T) -// memclr(hp, hn) -// } -// } -// s +// +// init { +// if l2 >= 0 { // Empty if block here for more meaningful node.SetLikely(true) +// } else { +// panicmakeslicelen() +// } +// s := l1 +// n := len(s) + l2 +// // Compare n and s as uint so growslice can panic on overflow of len(s) + l2. +// // cap is a positive int and n can become negative when len(s) + l2 +// // overflows int. Interpreting n when negative as uint makes it larger +// // than cap(s). growslice will check the int n arg and panic if n is +// // negative. This prevents the overflow from being undetected. +// if uint(n) > uint(cap(s)) { +// s = growslice(T, s, n) +// } +// s = s[:n] +// lptr := &l1[0] +// sptr := &s[0] +// if lptr == sptr || !T.HasPointers() { +// // growslice did not clear the whole underlying array (or did not get called) +// hp := &s[len(l1)] +// hn := l2 * sizeof(T) +// memclr(hp, hn) +// } +// } +// s func extendSlice(n *ir.CallExpr, init *ir.Nodes) ir.Node { // isAppendOfMake made sure all possible positive values of l2 fit into an uint. // The case of l2 overflow when converting from e.g. uint to int is handled by an explicit diff --git a/src/cmd/compile/internal/walk/builtin.go b/src/cmd/compile/internal/walk/builtin.go index 7ec5494d99..d7b553ed0c 100644 --- a/src/cmd/compile/internal/walk/builtin.go +++ b/src/cmd/compile/internal/walk/builtin.go @@ -26,19 +26,19 @@ import ( // // For race detector, expand append(src, a [, b]* ) to // -// init { -// s := src -// const argc = len(args) - 1 -// if cap(s) - len(s) < argc { -// s = growslice(s, len(s)+argc) -// } -// n := len(s) -// s = s[:n+argc] -// s[n] = a -// s[n+1] = b -// ... -// } -// s +// init { +// s := src +// const argc = len(args) - 1 +// if cap(s) - len(s) < argc { +// s = growslice(s, len(s)+argc) +// } +// n := len(s) +// s = s[:n+argc] +// s[n] = a +// s[n+1] = b +// ... +// } +// s func walkAppend(n *ir.CallExpr, init *ir.Nodes, dst ir.Node) ir.Node { if !ir.SameSafeExpr(dst, n.Args[0]) { n.Args[0] = safeExpr(n.Args[0], init) diff --git a/src/cmd/compile/internal/walk/compare.go b/src/cmd/compile/internal/walk/compare.go index 625e216050..993f1392aa 100644 --- a/src/cmd/compile/internal/walk/compare.go +++ b/src/cmd/compile/internal/walk/compare.go @@ -16,7 +16,8 @@ import ( ) // The result of walkCompare MUST be assigned back to n, e.g. -// n.Left = walkCompare(n.Left, init) +// +// n.Left = walkCompare(n.Left, init) func walkCompare(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { if n.X.Type().IsInterface() && n.Y.Type().IsInterface() && n.X.Op() != ir.ONIL && n.Y.Op() != ir.ONIL { return walkCompareInterface(n, init) @@ -404,7 +405,8 @@ func walkCompareString(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { } // The result of finishCompare MUST be assigned back to n, e.g. -// n.Left = finishCompare(n.Left, x, r, init) +// +// n.Left = finishCompare(n.Left, x, r, init) func finishCompare(n *ir.BinaryExpr, r ir.Node, init *ir.Nodes) ir.Node { r = typecheck.Expr(r) r = typecheck.Conv(r, n.Type()) diff --git a/src/cmd/compile/internal/walk/expr.go b/src/cmd/compile/internal/walk/expr.go index 4c1e7adddd..26a23c4d09 100644 --- a/src/cmd/compile/internal/walk/expr.go +++ b/src/cmd/compile/internal/walk/expr.go @@ -20,7 +20,8 @@ import ( ) // The result of walkExpr MUST be assigned back to n, e.g. -// n.Left = walkExpr(n.Left, init) +// +// n.Left = walkExpr(n.Left, init) func walkExpr(n ir.Node, init *ir.Nodes) ir.Node { if n == nil { return n diff --git a/src/cmd/compile/internal/walk/order.go b/src/cmd/compile/internal/walk/order.go index cc37f95764..80806478be 100644 --- a/src/cmd/compile/internal/walk/order.go +++ b/src/cmd/compile/internal/walk/order.go @@ -237,7 +237,8 @@ func isaddrokay(n ir.Node) bool { // If the original argument n is not okay, addrTemp creates a tmp, emits // tmp = n, and then returns tmp. // The result of addrTemp MUST be assigned back to n, e.g. -// n.Left = o.addrTemp(n.Left) +// +// n.Left = o.addrTemp(n.Left) func (o *orderState) addrTemp(n ir.Node) ir.Node { if n.Op() == ir.OLITERAL || n.Op() == ir.ONIL { // TODO: expand this to all static composite literal nodes? @@ -316,8 +317,10 @@ func (o *orderState) mapKeyTemp(t *types.Type, n ir.Node) ir.Node { // Returns a bool that signals if a modification was made. // // For: -// x = m[string(k)] -// x = m[T1{... Tn{..., string(k), ...}] +// +// x = m[string(k)] +// x = m[T1{... Tn{..., string(k), ...}] +// // where k is []byte, T1 to Tn is a nesting of struct and array literals, // the allocation of backing bytes for the string can be avoided // by reusing the []byte backing array. These are special cases @@ -400,9 +403,12 @@ func (o *orderState) stmtList(l ir.Nodes) { } // orderMakeSliceCopy matches the pattern: -// m = OMAKESLICE([]T, x); OCOPY(m, s) +// +// m = OMAKESLICE([]T, x); OCOPY(m, s) +// // and rewrites it to: -// m = OMAKESLICECOPY([]T, x, s); nil +// +// m = OMAKESLICECOPY([]T, x, s); nil func orderMakeSliceCopy(s []ir.Node) { if base.Flag.N != 0 || base.Flag.Cfg.Instrumenting { return @@ -473,7 +479,8 @@ func orderBlock(n *ir.Nodes, free map[string][]*ir.Name) { // exprInPlace orders the side effects in *np and // leaves them as the init list of the final *np. // The result of exprInPlace MUST be assigned back to n, e.g. -// n.Left = o.exprInPlace(n.Left) +// +// n.Left = o.exprInPlace(n.Left) func (o *orderState) exprInPlace(n ir.Node) ir.Node { var order orderState order.free = o.free @@ -489,7 +496,9 @@ func (o *orderState) exprInPlace(n ir.Node) ir.Node { // orderStmtInPlace orders the side effects of the single statement *np // and replaces it with the resulting statement list. // The result of orderStmtInPlace MUST be assigned back to n, e.g. -// n.Left = orderStmtInPlace(n.Left) +// +// n.Left = orderStmtInPlace(n.Left) +// // free is a map that can be used to obtain temporary variables by type. func orderStmtInPlace(n ir.Node, free map[string][]*ir.Name) ir.Node { var order orderState @@ -1087,7 +1096,8 @@ func (o *orderState) exprNoLHS(n ir.Node) ir.Node { // Otherwise lhs == nil. (When lhs != nil it may be possible // to avoid copying the result of the expression to a temporary.) // The result of expr MUST be assigned back to n, e.g. -// n.Left = o.expr(n.Left, lhs) +// +// n.Left = o.expr(n.Left, lhs) func (o *orderState) expr(n, lhs ir.Node) ir.Node { if n == nil { return n @@ -1451,10 +1461,14 @@ func (o *orderState) expr1(n, lhs ir.Node) ir.Node { // as2func orders OAS2FUNC nodes. It creates temporaries to ensure left-to-right assignment. // The caller should order the right-hand side of the assignment before calling order.as2func. // It rewrites, +// // a, b, a = ... +// // as +// // tmp1, tmp2, tmp3 = ... // a, b, a = tmp1, tmp2, tmp3 +// // This is necessary to ensure left to right assignment order. func (o *orderState) as2func(n *ir.AssignListStmt) { results := n.Rhs[0].Type() diff --git a/src/cmd/compile/internal/walk/stmt.go b/src/cmd/compile/internal/walk/stmt.go index f09e916546..8a42dbf777 100644 --- a/src/cmd/compile/internal/walk/stmt.go +++ b/src/cmd/compile/internal/walk/stmt.go @@ -10,7 +10,8 @@ import ( ) // The result of walkStmt MUST be assigned back to n, e.g. -// n.Left = walkStmt(n.Left) +// +// n.Left = walkStmt(n.Left) func walkStmt(n ir.Node) ir.Node { if n == nil { return n @@ -84,6 +85,7 @@ func walkStmt(n ir.Node) ir.Node { ir.OFALL, ir.OGOTO, ir.OLABEL, + ir.OJUMPTABLE, ir.ODCL, ir.ODCLCONST, ir.ODCLTYPE, diff --git a/src/cmd/compile/internal/walk/switch.go b/src/cmd/compile/internal/walk/switch.go index 3705c5b192..5067d5eb49 100644 --- a/src/cmd/compile/internal/walk/switch.go +++ b/src/cmd/compile/internal/walk/switch.go @@ -11,6 +11,7 @@ import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" + "cmd/compile/internal/ssagen" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/internal/src" @@ -66,6 +67,7 @@ func walkSwitchExpr(sw *ir.SwitchStmt) { base.Pos = lno s := exprSwitch{ + pos: lno, exprname: cond, } @@ -112,6 +114,7 @@ func walkSwitchExpr(sw *ir.SwitchStmt) { // An exprSwitch walks an expression switch. type exprSwitch struct { + pos src.XPos exprname ir.Node // value being switched on done ir.Nodes @@ -182,17 +185,59 @@ func (s *exprSwitch) flush() { } runs = append(runs, cc[start:]) - // Perform two-level binary search. - binarySearch(len(runs), &s.done, - func(i int) ir.Node { - return ir.NewBinaryExpr(base.Pos, ir.OLE, ir.NewUnaryExpr(base.Pos, ir.OLEN, s.exprname), ir.NewInt(runLen(runs[i-1]))) - }, - func(i int, nif *ir.IfStmt) { - run := runs[i] - nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, ir.NewUnaryExpr(base.Pos, ir.OLEN, s.exprname), ir.NewInt(runLen(run))) - s.search(run, &nif.Body) - }, - ) + if len(runs) == 1 { + s.search(runs[0], &s.done) + return + } + // We have strings of more than one length. Generate an + // outer switch which switches on the length of the string + // and an inner switch in each case which resolves all the + // strings of the same length. The code looks something like this: + + // goto outerLabel + // len5: + // ... search among length 5 strings ... + // goto endLabel + // len8: + // ... search among length 8 strings ... + // goto endLabel + // ... other lengths ... + // outerLabel: + // switch len(s) { + // case 5: goto len5 + // case 8: goto len8 + // ... other lengths ... + // } + // endLabel: + + outerLabel := typecheck.AutoLabel(".s") + endLabel := typecheck.AutoLabel(".s") + + // Jump around all the individual switches for each length. + s.done.Append(ir.NewBranchStmt(s.pos, ir.OGOTO, outerLabel)) + + var outer exprSwitch + outer.exprname = ir.NewUnaryExpr(s.pos, ir.OLEN, s.exprname) + outer.exprname.SetType(types.Types[types.TINT]) + + for _, run := range runs { + // Target label to jump to when we match this length. + label := typecheck.AutoLabel(".s") + + // Search within this run of same-length strings. + pos := run[0].pos + s.done.Append(ir.NewLabelStmt(pos, label)) + s.search(run, &s.done) + s.done.Append(ir.NewBranchStmt(pos, ir.OGOTO, endLabel)) + + // Add length case to outer switch. + cas := ir.NewBasicLit(pos, constant.MakeInt64(runLen(run))) + jmp := ir.NewBranchStmt(pos, ir.OGOTO, label) + outer.Add(pos, cas, jmp) + } + s.done.Append(ir.NewLabelStmt(s.pos, outerLabel)) + outer.Emit(&s.done) + s.done.Append(ir.NewLabelStmt(s.pos, endLabel)) return } @@ -223,6 +268,9 @@ func (s *exprSwitch) flush() { } func (s *exprSwitch) search(cc []exprClause, out *ir.Nodes) { + if s.tryJumpTable(cc, out) { + return + } binarySearch(len(cc), out, func(i int) ir.Node { return ir.NewBinaryExpr(base.Pos, ir.OLE, s.exprname, cc[i-1].hi) @@ -235,6 +283,48 @@ func (s *exprSwitch) search(cc []exprClause, out *ir.Nodes) { ) } +// Try to implement the clauses with a jump table. Returns true if successful. +func (s *exprSwitch) tryJumpTable(cc []exprClause, out *ir.Nodes) bool { + const go119UseJumpTables = true + const minCases = 8 // have at least minCases cases in the switch + const minDensity = 4 // use at least 1 out of every minDensity entries + + if !go119UseJumpTables || base.Flag.N != 0 || !ssagen.Arch.LinkArch.CanJumpTable { + return false + } + if len(cc) < minCases { + return false // not enough cases for it to be worth it + } + if cc[0].lo.Val().Kind() != constant.Int { + return false // e.g. float + } + if s.exprname.Type().Size() > int64(types.PtrSize) { + return false // 64-bit switches on 32-bit archs + } + min := cc[0].lo.Val() + max := cc[len(cc)-1].hi.Val() + width := constant.BinaryOp(constant.BinaryOp(max, token.SUB, min), token.ADD, constant.MakeInt64(1)) + limit := constant.MakeInt64(int64(len(cc)) * minDensity) + if constant.Compare(width, token.GTR, limit) { + // We disable jump tables if we use less than a minimum fraction of the entries. + // i.e. for switch x {case 0: case 1000: case 2000:} we don't want to use a jump table. + return false + } + jt := ir.NewJumpTableStmt(base.Pos, s.exprname) + for _, c := range cc { + jmp := c.jmp.(*ir.BranchStmt) + if jmp.Op() != ir.OGOTO || jmp.Label == nil { + panic("bad switch case body") + } + for i := c.lo.Val(); constant.Compare(i, token.LEQ, c.hi.Val()); i = constant.BinaryOp(i, token.ADD, constant.MakeInt64(1)) { + jt.Cases = append(jt.Cases, i) + jt.Targets = append(jt.Targets, jmp.Label) + } + } + out.Append(jt) + return true +} + func (c *exprClause) test(exprname ir.Node) ir.Node { // Integer range. if c.hi != c.lo { @@ -540,6 +630,7 @@ func (s *typeSwitch) flush() { } cc = merged + // TODO: figure out if we could use a jump table using some low bits of the type hashes. binarySearch(len(cc), &s.done, func(i int) ir.Node { return ir.NewBinaryExpr(base.Pos, ir.OLE, s.hashname, ir.NewInt(int64(cc[i-1].hash))) @@ -562,7 +653,7 @@ func (s *typeSwitch) flush() { // then cases before i will be tested; otherwise, cases i and later. // // leaf(i, nif) should setup nif (an OIF node) to test case i. In -// particular, it should set nif.Left and nif.Nbody. +// particular, it should set nif.Cond and nif.Body. func binarySearch(n int, out *ir.Nodes, less func(i int) ir.Node, leaf func(i int, nif *ir.IfStmt)) { const binarySearchMin = 4 // minimum number of cases for binary search diff --git a/src/cmd/compile/internal/x86/ssa.go b/src/cmd/compile/internal/x86/ssa.go index 32e29f347b..378100b162 100644 --- a/src/cmd/compile/internal/x86/ssa.go +++ b/src/cmd/compile/internal/x86/ssa.go @@ -106,7 +106,9 @@ func moveByType(t *types.Type) obj.As { } // opregreg emits instructions for -// dest := dest(To) op src(From) +// +// dest := dest(To) op src(From) +// // and also returns the created obj.Prog so it // may be further adjusted (offset, scale, etc). func opregreg(s *ssagen.State, op obj.As, dest, src int16) *obj.Prog { @@ -725,7 +727,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { // caller's SP is the address of the first arg p := s.Prog(x86.AMOVL) p.From.Type = obj.TYPE_ADDR - p.From.Offset = -base.Ctxt.FixedFrameSize() // 0 on 386, just to be consistent with other architectures + p.From.Offset = -base.Ctxt.Arch.FixedFrameSize // 0 on 386, just to be consistent with other architectures p.From.Name = obj.NAME_PARAM p.To.Type = obj.TYPE_REG p.To.Reg = v.Reg() diff --git a/src/cmd/cover/cover.go b/src/cmd/cover/cover.go index 9c8529f7eb..86ef128f2c 100644 --- a/src/cmd/cover/cover.go +++ b/src/cmd/cover/cover.go @@ -377,7 +377,7 @@ func (f *File) newCounter(start, end token.Pos, numStmt int) string { // S1 // if cond { // S2 -// } +// } // S3 // // counters will be added before S1 and before S3. The block containing S2 diff --git a/src/cmd/cover/cover_test.go b/src/cmd/cover/cover_test.go index 8bd31514a0..28be231121 100644 --- a/src/cmd/cover/cover_test.go +++ b/src/cmd/cover/cover_test.go @@ -162,8 +162,8 @@ func buildCover(t *testing.T) { // Run this shell script, but do it in Go so it can be run by "go test". // // replace the word LINE with the line number < testdata/test.go > testdata/test_line.go -// go build -o testcover -// testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go +// go build -o testcover +// testcover -mode=count -var=CoverTest -o ./testdata/test_cover.go testdata/test_line.go // go run ./testdata/main.go ./testdata/test.go func TestCover(t *testing.T) { t.Parallel() diff --git a/src/cmd/cover/doc.go b/src/cmd/cover/doc.go index e2c849419a..e091ce9e30 100644 --- a/src/cmd/cover/doc.go +++ b/src/cmd/cover/doc.go @@ -19,6 +19,7 @@ must be applied to the output of cgo preprocessing, not the input, because cover deletes comments that are significant to cgo. For usage information, please see: + go help testflag go tool cover -help */ diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index db2ac1f2a6..bbaf595421 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -26,7 +26,7 @@ import ( // The usual variables. var ( goarch string - gobin string + gorootBin string gohostarch string gohostos string goos string @@ -112,6 +112,7 @@ func xinit() { fatalf("$GOROOT must be set") } goroot = filepath.Clean(b) + gorootBin = pathf("%s/bin", goroot) b = os.Getenv("GOROOT_FINAL") if b == "" { @@ -119,12 +120,6 @@ func xinit() { } goroot_final = b - b = os.Getenv("GOBIN") - if b == "" { - b = pathf("%s/bin", goroot) - } - gobin = b - b = os.Getenv("GOOS") if b == "" { b = gohostos @@ -241,9 +236,19 @@ func xinit() { // make.bash really does start from a clean slate. os.Setenv("GOCACHE", pathf("%s/pkg/obj/go-build", goroot)) + // Set GOBIN to GOROOT/bin. The meaning of GOBIN has drifted over time + // (see https://go.dev/issue/3269, https://go.dev/cl/183058, + // https://go.dev/issue/31576). Since we want binaries installed by 'dist' to + // always go to GOROOT/bin anyway. + os.Setenv("GOBIN", gorootBin) + // Make the environment more predictable. os.Setenv("LANG", "C") os.Setenv("LANGUAGE", "en_US.UTF8") + os.Unsetenv("GO111MODULE") + os.Setenv("GOENV", "off") + os.Unsetenv("GOFLAGS") + os.Setenv("GOWORK", "off") workdir = xworkdir() if err := ioutil.WriteFile(pathf("%s/go.mod", workdir), []byte("module bootstrap"), 0666); err != nil { @@ -490,16 +495,6 @@ func setup() { xremove(pathf("%s/bin/%s", goroot, old)) } - // If $GOBIN is set and has a Go compiler, it must be cleaned. - for _, char := range "56789" { - if isfile(pathf("%s/%c%s", gobin, char, "g")) { - for _, old := range oldtool { - xremove(pathf("%s/%s", gobin, old)) - } - break - } - } - // For release, make sure excluded things are excluded. goversion := findgoversion() if strings.HasPrefix(goversion, "release.") || (strings.HasPrefix(goversion, "go") && !strings.Contains(goversion, "beta")) { @@ -1126,8 +1121,8 @@ func clean() { // The env command prints the default environment. func cmdenv() { path := flag.Bool("p", false, "emit updated PATH") - plan9 := flag.Bool("9", false, "emit plan 9 syntax") - windows := flag.Bool("w", false, "emit windows syntax") + plan9 := flag.Bool("9", gohostos == "plan9", "emit plan 9 syntax") + windows := flag.Bool("w", gohostos == "windows", "emit windows syntax") xflagparse(0) format := "%s=\"%s\"\n" @@ -1138,10 +1133,13 @@ func cmdenv() { format = "set %s=%s\r\n" } + xprintf(format, "GO111MODULE", "") xprintf(format, "GOARCH", goarch) - xprintf(format, "GOBIN", gobin) + xprintf(format, "GOBIN", gorootBin) xprintf(format, "GOCACHE", os.Getenv("GOCACHE")) xprintf(format, "GODEBUG", os.Getenv("GODEBUG")) + xprintf(format, "GOENV", "off") + xprintf(format, "GOFLAGS", "") xprintf(format, "GOHOSTARCH", gohostarch) xprintf(format, "GOHOSTOS", gohostos) xprintf(format, "GOOS", goos) @@ -1167,13 +1165,14 @@ func cmdenv() { if goarch == "ppc64" || goarch == "ppc64le" { xprintf(format, "GOPPC64", goppc64) } + xprintf(format, "GOWORK", "off") if *path { sep := ":" if gohostos == "windows" { sep = ";" } - xprintf(format, "PATH", fmt.Sprintf("%s%s%s", gobin, sep, os.Getenv("PATH"))) + xprintf(format, "PATH", fmt.Sprintf("%s%s%s", gorootBin, sep, os.Getenv("PATH"))) } } @@ -1226,7 +1225,9 @@ var toolchain = []string{"cmd/asm", "cmd/cgo", "cmd/compile", "cmd/link"} // commands (like "go tool dist test" in run.bash) can rely on bug fixes // made since Go 1.4, but this function cannot. In particular, the uses // of os/exec in this function cannot assume that +// // cmd.Env = append(os.Environ(), "X=Y") +// // sets $X to Y in the command's environment. That guarantee was // added after Go 1.4, and in fact in Go 1.4 it was typically the opposite: // if $X was already present in os.Environ(), most systems preferred @@ -1318,7 +1319,7 @@ func cmdbootstrap() { gogcflags = os.Getenv("GO_GCFLAGS") // we were using $BOOT_GO_GCFLAGS until now goldflags = os.Getenv("GO_LDFLAGS") // we were using $BOOT_GO_LDFLAGS until now goBootstrap := pathf("%s/go_bootstrap", tooldir) - cmdGo := pathf("%s/go", gobin) + cmdGo := pathf("%s/go", gorootBin) if debug { run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full") copyfile(pathf("%s/compile1", tooldir), pathf("%s/compile", tooldir), writeExec) @@ -1457,7 +1458,7 @@ func cmdbootstrap() { os.Setenv("GOOS", gohostos) os.Setenv("GOARCH", gohostarch) os.Setenv("CC", compilerEnvLookup(defaultcc, gohostos, gohostarch)) - goCmd(cmdGo, "build", "-o", pathf("%s/go_%s_%s_exec%s", gobin, goos, goarch, exe), wrapperPath) + goCmd(cmdGo, "build", "-o", pathf("%s/go_%s_%s_exec%s", gorootBin, goos, goarch, exe), wrapperPath) // Restore environment. // TODO(elias.naur): support environment variables in goCmd? os.Setenv("GOOS", goos) @@ -1681,26 +1682,26 @@ func banner() { } xprintf("---\n") xprintf("Installed Go for %s/%s in %s\n", goos, goarch, goroot) - xprintf("Installed commands in %s\n", gobin) + xprintf("Installed commands in %s\n", gorootBin) if !xsamefile(goroot_final, goroot) { // If the files are to be moved, don't check that gobin // is on PATH; assume they know what they are doing. } else if gohostos == "plan9" { - // Check that gobin is bound before /bin. + // Check that GOROOT/bin is bound before /bin. pid := strings.Replace(readfile("#c/pid"), " ", "", -1) ns := fmt.Sprintf("/proc/%s/ns", pid) - if !strings.Contains(readfile(ns), fmt.Sprintf("bind -b %s /bin", gobin)) { - xprintf("*** You need to bind %s before /bin.\n", gobin) + if !strings.Contains(readfile(ns), fmt.Sprintf("bind -b %s /bin", gorootBin)) { + xprintf("*** You need to bind %s before /bin.\n", gorootBin) } } else { - // Check that gobin appears in $PATH. + // Check that GOROOT/bin appears in $PATH. pathsep := ":" if gohostos == "windows" { pathsep = ";" } - if !strings.Contains(pathsep+os.Getenv("PATH")+pathsep, pathsep+gobin+pathsep) { - xprintf("*** You need to add %s to your PATH.\n", gobin) + if !strings.Contains(pathsep+os.Getenv("PATH")+pathsep, pathsep+gorootBin+pathsep) { + xprintf("*** You need to add %s to your PATH.\n", gorootBin) } } diff --git a/src/cmd/dist/doc.go b/src/cmd/dist/doc.go index a4e6aa5cbf..ad26aa2dc0 100644 --- a/src/cmd/dist/doc.go +++ b/src/cmd/dist/doc.go @@ -5,15 +5,17 @@ // Dist helps bootstrap, build, and test the Go distribution. // // Usage: -// go tool dist [command] +// +// go tool dist [command] // // The commands are: -// banner print installation banner -// bootstrap rebuild everything -// clean deletes all built files -// env [-p] print environment (-p: include $PATH) -// install [dir] install individual directory -// list [-json] list all supported platforms -// test [-h] run Go test(s) -// version print Go version +// +// banner print installation banner +// bootstrap rebuild everything +// clean deletes all built files +// env [-p] print environment (-p: include $PATH) +// install [dir] install individual directory +// list [-json] list all supported platforms +// test [-h] run Go test(s) +// version print Go version package main diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go index 9118c133e5..ee521f81ba 100644 --- a/src/cmd/dist/test.go +++ b/src/cmd/dist/test.go @@ -100,8 +100,8 @@ func (t *tester) run() { if goos == "windows" { exeSuffix = ".exe" } - if _, err := os.Stat(filepath.Join(gobin, "go"+exeSuffix)); err == nil { - os.Setenv("PATH", fmt.Sprintf("%s%c%s", gobin, os.PathListSeparator, os.Getenv("PATH"))) + if _, err := os.Stat(filepath.Join(gorootBin, "go"+exeSuffix)); err == nil { + os.Setenv("PATH", fmt.Sprintf("%s%c%s", gorootBin, os.PathListSeparator, os.Getenv("PATH"))) } cmd := exec.Command("go", "env", "CGO_ENABLED") diff --git a/src/cmd/doc/doc_test.go b/src/cmd/doc/doc_test.go index ead4f722f6..5887ad3395 100644 --- a/src/cmd/doc/doc_test.go +++ b/src/cmd/doc/doc_test.go @@ -882,7 +882,9 @@ func TestDoc(t *testing.T) { } // Test the code to try multiple packages. Our test case is +// // go doc rand.Float64 +// // This needs to find math/rand.Float64; however crypto/rand, which doesn't // have the symbol, usually appears first in the directory listing. func TestMultiplePackages(t *testing.T) { @@ -939,11 +941,15 @@ func TestMultiplePackages(t *testing.T) { } // Test the code to look up packages when given two args. First test case is +// // go doc binary BigEndian +// // This needs to find encoding/binary.BigEndian, which means // finding the package encoding/binary given only "binary". // Second case is +// // go doc rand Float64 +// // which again needs to find math/rand and not give up after crypto/rand, // which has no such function. func TestTwoArgLookup(t *testing.T) { diff --git a/src/cmd/doc/main.go b/src/cmd/doc/main.go index dee5d7bbcd..3c45dd76df 100644 --- a/src/cmd/doc/main.go +++ b/src/cmd/doc/main.go @@ -5,20 +5,25 @@ // Doc (usually run as go doc) accepts zero, one or two arguments. // // Zero arguments: +// // go doc +// // Show the documentation for the package in the current directory. // // One argument: +// // go doc // go doc [.] // go doc [.][.] // go doc [.][.] +// // The first item in this list that succeeds is the one whose documentation // is printed. If there is a symbol but no package, the package in the current // directory is chosen. However, if the argument begins with a capital // letter it is always assumed to be a symbol in the current directory. // // Two arguments: +// // go doc [.] // // Show the documentation for the package, symbol, and method or field. The diff --git a/src/cmd/doc/pkg.go b/src/cmd/doc/pkg.go index 49b68873b6..35f2eb24bf 100644 --- a/src/cmd/doc/pkg.go +++ b/src/cmd/doc/pkg.go @@ -25,8 +25,7 @@ import ( ) const ( - punchedCardWidth = 80 // These things just won't leave us alone. - indentedWidth = punchedCardWidth - len(indent) + punchedCardWidth = 80 indent = " " ) @@ -44,6 +43,14 @@ type Package struct { buf pkgBuffer } +func (p *Package) ToText(w io.Writer, text, prefix, codePrefix string) { + d := p.doc.Parser().Parse(text) + pr := p.doc.Printer() + pr.TextPrefix = prefix + pr.TextCodePrefix = codePrefix + w.Write(pr.Text(d)) +} + // pkgBuffer is a wrapper for bytes.Buffer that prints a package clause the // first time Write is called. type pkgBuffer struct { @@ -251,7 +258,7 @@ func (pkg *Package) emit(comment string, node ast.Node) { } if comment != "" && !showSrc { pkg.newlines(1) - doc.ToText(&pkg.buf, comment, indent, indent+indent, indentedWidth) + pkg.ToText(&pkg.buf, comment, indent, indent+indent) pkg.newlines(2) // Blank line after comment to separate from next item. } else { pkg.newlines(1) @@ -463,7 +470,7 @@ func joinStrings(ss []string) string { // allDoc prints all the docs for the package. func (pkg *Package) allDoc() { pkg.Printf("") // Trigger the package clause; we know the package exists. - doc.ToText(&pkg.buf, pkg.doc.Doc, "", indent, indentedWidth) + pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent) pkg.newlines(1) printed := make(map[*ast.GenDecl]bool) @@ -523,7 +530,7 @@ func (pkg *Package) allDoc() { func (pkg *Package) packageDoc() { pkg.Printf("") // Trigger the package clause; we know the package exists. if !short { - doc.ToText(&pkg.buf, pkg.doc.Doc, "", indent, indentedWidth) + pkg.ToText(&pkg.buf, pkg.doc.Doc, "", indent) pkg.newlines(1) } @@ -1033,9 +1040,9 @@ func (pkg *Package) printFieldDoc(symbol, fieldName string) bool { if field.Doc != nil { // To present indented blocks in comments correctly, process the comment as // a unit before adding the leading // to each line. - docBuf := bytes.Buffer{} - doc.ToText(&docBuf, field.Doc.Text(), "", indent, indentedWidth) - scanner := bufio.NewScanner(&docBuf) + docBuf := new(bytes.Buffer) + pkg.ToText(docBuf, field.Doc.Text(), "", indent) + scanner := bufio.NewScanner(docBuf) for scanner.Scan() { fmt.Fprintf(&pkg.buf, "%s// %s\n", indent, scanner.Bytes()) } diff --git a/src/cmd/fix/cftype.go b/src/cmd/fix/cftype.go index 27e4088aa9..e4988b1c62 100644 --- a/src/cmd/fix/cftype.go +++ b/src/cmd/fix/cftype.go @@ -24,9 +24,13 @@ var cftypeFix = fix{ } // Old state: -// type CFTypeRef unsafe.Pointer +// +// type CFTypeRef unsafe.Pointer +// // New state: -// type CFTypeRef uintptr +// +// type CFTypeRef uintptr +// // and similar for other *Ref types. // This fix finds nils initializing these types and replaces the nils with 0s. func cftypefix(f *ast.File) bool { diff --git a/src/cmd/fix/doc.go b/src/cmd/fix/doc.go index 0570169576..062eb79285 100644 --- a/src/cmd/fix/doc.go +++ b/src/cmd/fix/doc.go @@ -8,6 +8,7 @@ newer ones. After you update to a new Go release, fix helps make the necessary changes to your programs. Usage: + go tool fix [-r name,...] [path ...] Without an explicit path, fix reads standard input and writes the @@ -30,7 +31,7 @@ Fix prints the full list of fixes it can apply in its help output; to see them, run go tool fix -help. Fix does not make backup copies of the files that it edits. -Instead, use a version control system's ``diff'' functionality to inspect +Instead, use a version control system's “diff” functionality to inspect the changes that fix makes before committing them. */ package main diff --git a/src/cmd/fix/egltype.go b/src/cmd/fix/egltype.go index cb0f7a73de..a096db6665 100644 --- a/src/cmd/fix/egltype.go +++ b/src/cmd/fix/egltype.go @@ -22,9 +22,13 @@ var eglFixDisplay = fix{ } // Old state: -// type EGLDisplay unsafe.Pointer +// +// type EGLDisplay unsafe.Pointer +// // New state: -// type EGLDisplay uintptr +// +// type EGLDisplay uintptr +// // This fix finds nils initializing these types and replaces the nils with 0s. func eglfixDisp(f *ast.File) bool { return typefix(f, func(s string) bool { @@ -41,9 +45,13 @@ var eglFixConfig = fix{ } // Old state: -// type EGLConfig unsafe.Pointer +// +// type EGLConfig unsafe.Pointer +// // New state: -// type EGLConfig uintptr +// +// type EGLConfig uintptr +// // This fix finds nils initializing these types and replaces the nils with 0s. func eglfixConfig(f *ast.File) bool { return typefix(f, func(s string) bool { diff --git a/src/cmd/fix/jnitype.go b/src/cmd/fix/jnitype.go index 29abe0f007..111be8e70c 100644 --- a/src/cmd/fix/jnitype.go +++ b/src/cmd/fix/jnitype.go @@ -21,9 +21,13 @@ var jniFix = fix{ } // Old state: -// type jobject *_jobject +// +// type jobject *_jobject +// // New state: -// type jobject uintptr +// +// type jobject uintptr +// // and similar for subtypes of jobject. // This fix finds nils initializing these types and replaces the nils with 0s. func jnifix(f *ast.File) bool { diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index f9d78b59e3..6fdb4f93a3 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -9,71 +9,69 @@ // // Usage: // -// go [arguments] +// go [arguments] // // The commands are: // -// bug start a bug report -// build compile packages and dependencies -// clean remove object files and cached files -// doc show documentation for package or symbol -// env print Go environment information -// fix update packages to use new APIs -// fmt gofmt (reformat) package sources -// generate generate Go files by processing source -// get add dependencies to current module and install them -// install compile and install packages and dependencies -// list list packages or modules -// mod module maintenance -// work workspace maintenance -// run compile and run Go program -// test test packages -// tool run specified go tool -// version print Go version -// vet report likely mistakes in packages +// bug start a bug report +// build compile packages and dependencies +// clean remove object files and cached files +// doc show documentation for package or symbol +// env print Go environment information +// fix update packages to use new APIs +// fmt gofmt (reformat) package sources +// generate generate Go files by processing source +// get add dependencies to current module and install them +// install compile and install packages and dependencies +// list list packages or modules +// mod module maintenance +// work workspace maintenance +// run compile and run Go program +// test test packages +// tool run specified go tool +// version print Go version +// vet report likely mistakes in packages // // Use "go help " for more information about a command. // // Additional help topics: // -// buildconstraint build constraints -// buildmode build modes -// c calling between Go and C -// cache build and test caching -// environment environment variables -// filetype file types -// go.mod the go.mod file -// gopath GOPATH environment variable -// gopath-get legacy GOPATH go get -// goproxy module proxy protocol -// importpath import path syntax -// modules modules, module versions, and more -// module-get module-aware go get -// module-auth module authentication using go.sum -// packages package lists and patterns -// private configuration for downloading non-public code -// testflag testing flags -// testfunc testing functions -// vcs controlling version control with GOVCS +// buildconstraint build constraints +// buildmode build modes +// c calling between Go and C +// cache build and test caching +// environment environment variables +// filetype file types +// go.mod the go.mod file +// gopath GOPATH environment variable +// gopath-get legacy GOPATH go get +// goproxy module proxy protocol +// importpath import path syntax +// modules modules, module versions, and more +// module-get module-aware go get +// module-auth module authentication using go.sum +// packages package lists and patterns +// private configuration for downloading non-public code +// testflag testing flags +// testfunc testing functions +// vcs controlling version control with GOVCS // // Use "go help " for more information about that topic. // -// -// Start a bug report +// # Start a bug report // // Usage: // -// go bug +// go bug // // Bug opens the default browser and starts a new bug report. // The report includes useful system information. // -// -// Compile packages and dependencies +// # Compile packages and dependencies // // Usage: // -// go build [-o output] [build flags] [packages] +// go build [-o output] [build flags] [packages] // // Build compiles the packages named by the import paths, // along with their dependencies, but it does not install the results. @@ -105,110 +103,112 @@ // The build flags are shared by the build, clean, get, install, list, run, // and test commands: // -// -a -// force rebuilding of packages that are already up-to-date. -// -n -// print the commands but do not run them. -// -p n -// the number of programs, such as build commands or -// test binaries, that can be run in parallel. -// The default is GOMAXPROCS, normally the number of CPUs available. -// -race -// enable data race detection. -// Supported only on linux/amd64, freebsd/amd64, darwin/amd64, darwin/arm64, windows/amd64, -// linux/ppc64le and linux/arm64 (only for 48-bit VMA). -// -msan -// enable interoperation with memory sanitizer. -// Supported only on linux/amd64, linux/arm64 -// and only with Clang/LLVM as the host C compiler. -// On linux/arm64, pie build mode will be used. -// -asan -// enable interoperation with address sanitizer. -// Supported only on linux/arm64, linux/amd64. -// -v -// print the names of packages as they are compiled. -// -work -// print the name of the temporary work directory and -// do not delete it when exiting. -// -x -// print the commands. +// -a +// force rebuilding of packages that are already up-to-date. +// -n +// print the commands but do not run them. +// -p n +// the number of programs, such as build commands or +// test binaries, that can be run in parallel. +// The default is GOMAXPROCS, normally the number of CPUs available. +// -race +// enable data race detection. +// Supported only on linux/amd64, freebsd/amd64, darwin/amd64, darwin/arm64, windows/amd64, +// linux/ppc64le and linux/arm64 (only for 48-bit VMA). +// -msan +// enable interoperation with memory sanitizer. +// Supported only on linux/amd64, linux/arm64 +// and only with Clang/LLVM as the host C compiler. +// On linux/arm64, pie build mode will be used. +// -asan +// enable interoperation with address sanitizer. +// Supported only on linux/arm64, linux/amd64. +// -v +// print the names of packages as they are compiled. +// -work +// print the name of the temporary work directory and +// do not delete it when exiting. +// -x +// print the commands. // -// -asmflags '[pattern=]arg list' -// arguments to pass on each go tool asm invocation. -// -buildmode mode -// build mode to use. See 'go help buildmode' for more. -// -buildvcs -// Whether to stamp binaries with version control information. By default, -// version control information is stamped into a binary if the main package -// and the main module containing it are in the repository containing the -// current directory (if there is a repository). Use -buildvcs=false to -// omit version control information. -// -compiler name -// name of compiler to use, as in runtime.Compiler (gccgo or gc). -// -gccgoflags '[pattern=]arg list' -// arguments to pass on each gccgo compiler/linker invocation. -// -gcflags '[pattern=]arg list' -// arguments to pass on each go tool compile invocation. -// -installsuffix suffix -// a suffix to use in the name of the package installation directory, -// in order to keep output separate from default builds. -// If using the -race flag, the install suffix is automatically set to race -// or, if set explicitly, has _race appended to it. Likewise for the -msan -// and -asan flags. Using a -buildmode option that requires non-default compile -// flags has a similar effect. -// -ldflags '[pattern=]arg list' -// arguments to pass on each go tool link invocation. -// -linkshared -// build code that will be linked against shared libraries previously -// created with -buildmode=shared. -// -mod mode -// module download mode to use: readonly, vendor, or mod. -// By default, if a vendor directory is present and the go version in go.mod -// is 1.14 or higher, the go command acts as if -mod=vendor were set. -// Otherwise, the go command acts as if -mod=readonly were set. -// See https://golang.org/ref/mod#build-commands for details. -// -modcacherw -// leave newly-created directories in the module cache read-write -// instead of making them read-only. -// -modfile file -// in module aware mode, read (and possibly write) an alternate go.mod -// file instead of the one in the module root directory. A file named -// "go.mod" must still be present in order to determine the module root -// directory, but it is not accessed. When -modfile is specified, an -// alternate go.sum file is also used: its path is derived from the -// -modfile flag by trimming the ".mod" extension and appending ".sum". -// -overlay file -// read a JSON config file that provides an overlay for build operations. -// The file is a JSON struct with a single field, named 'Replace', that -// maps each disk file path (a string) to its backing file path, so that -// a build will run as if the disk file path exists with the contents -// given by the backing file paths, or as if the disk file path does not -// exist if its backing file path is empty. Support for the -overlay flag -// has some limitations: importantly, cgo files included from outside the -// include path must be in the same directory as the Go package they are -// included from, and overlays will not appear when binaries and tests are -// run through go run and go test respectively. -// -pkgdir dir -// install and load all packages from dir instead of the usual locations. -// For example, when building with a non-standard configuration, -// use -pkgdir to keep generated packages in a separate location. -// -tags tag,list -// a comma-separated list of build tags to consider satisfied during the -// build. For more information about build tags, see the description of -// build constraints in the documentation for the go/build package. -// (Earlier versions of Go used a space-separated list, and that form -// is deprecated but still recognized.) -// -trimpath -// remove all file system paths from the resulting executable. -// Instead of absolute file system paths, the recorded file names -// will begin either a module path@version (when using modules), -// or a plain import path (when using the standard library, or GOPATH). -// -toolexec 'cmd args' -// a program to use to invoke toolchain programs like vet and asm. -// For example, instead of running asm, the go command will run -// 'cmd args /path/to/asm '. -// The TOOLEXEC_IMPORTPATH environment variable will be set, -// matching 'go list -f {{.ImportPath}}' for the package being built. +// -asmflags '[pattern=]arg list' +// arguments to pass on each go tool asm invocation. +// -buildmode mode +// build mode to use. See 'go help buildmode' for more. +// -buildvcs +// Whether to stamp binaries with version control information +// ("true", "false", or "auto"). By default ("auto"), version control +// information is stamped into a binary if the main package, the main module +// containing it, and the current directory are all in the same repository. +// Use -buildvcs=false to always omit version control information, or +// -buildvcs=true to error out if version control information is available but +// cannot be included due to a missing tool or ambiguous directory structure. +// -compiler name +// name of compiler to use, as in runtime.Compiler (gccgo or gc). +// -gccgoflags '[pattern=]arg list' +// arguments to pass on each gccgo compiler/linker invocation. +// -gcflags '[pattern=]arg list' +// arguments to pass on each go tool compile invocation. +// -installsuffix suffix +// a suffix to use in the name of the package installation directory, +// in order to keep output separate from default builds. +// If using the -race flag, the install suffix is automatically set to race +// or, if set explicitly, has _race appended to it. Likewise for the -msan +// and -asan flags. Using a -buildmode option that requires non-default compile +// flags has a similar effect. +// -ldflags '[pattern=]arg list' +// arguments to pass on each go tool link invocation. +// -linkshared +// build code that will be linked against shared libraries previously +// created with -buildmode=shared. +// -mod mode +// module download mode to use: readonly, vendor, or mod. +// By default, if a vendor directory is present and the go version in go.mod +// is 1.14 or higher, the go command acts as if -mod=vendor were set. +// Otherwise, the go command acts as if -mod=readonly were set. +// See https://golang.org/ref/mod#build-commands for details. +// -modcacherw +// leave newly-created directories in the module cache read-write +// instead of making them read-only. +// -modfile file +// in module aware mode, read (and possibly write) an alternate go.mod +// file instead of the one in the module root directory. A file named +// "go.mod" must still be present in order to determine the module root +// directory, but it is not accessed. When -modfile is specified, an +// alternate go.sum file is also used: its path is derived from the +// -modfile flag by trimming the ".mod" extension and appending ".sum". +// -overlay file +// read a JSON config file that provides an overlay for build operations. +// The file is a JSON struct with a single field, named 'Replace', that +// maps each disk file path (a string) to its backing file path, so that +// a build will run as if the disk file path exists with the contents +// given by the backing file paths, or as if the disk file path does not +// exist if its backing file path is empty. Support for the -overlay flag +// has some limitations: importantly, cgo files included from outside the +// include path must be in the same directory as the Go package they are +// included from, and overlays will not appear when binaries and tests are +// run through go run and go test respectively. +// -pkgdir dir +// install and load all packages from dir instead of the usual locations. +// For example, when building with a non-standard configuration, +// use -pkgdir to keep generated packages in a separate location. +// -tags tag,list +// a comma-separated list of build tags to consider satisfied during the +// build. For more information about build tags, see the description of +// build constraints in the documentation for the go/build package. +// (Earlier versions of Go used a space-separated list, and that form +// is deprecated but still recognized.) +// -trimpath +// remove all file system paths from the resulting executable. +// Instead of absolute file system paths, the recorded file names +// will begin either a module path@version (when using modules), +// or a plain import path (when using the standard library, or GOPATH). +// -toolexec 'cmd args' +// a program to use to invoke toolchain programs like vet and asm. +// For example, instead of running asm, the go command will run +// 'cmd args /path/to/asm '. +// The TOOLEXEC_IMPORTPATH environment variable will be set, +// matching 'go list -f {{.ImportPath}}' for the package being built. // // The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a // space-separated list of arguments to pass to an underlying tool @@ -240,12 +240,11 @@ // // See also: go install, go get, go clean. // -// -// Remove object files and cached files +// # Remove object files and cached files // // Usage: // -// go clean [clean flags] [build flags] [packages] +// go clean [clean flags] [build flags] [packages] // // Clean removes object files from package source directories. // The go command builds most objects in a temporary directory, @@ -256,17 +255,17 @@ // clean removes the following files from each of the // source directories corresponding to the import paths: // -// _obj/ old object directory, left from Makefiles -// _test/ old test directory, left from Makefiles -// _testmain.go old gotest file, left from Makefiles -// test.out old test log, left from Makefiles -// build.out old test log, left from Makefiles -// *.[568ao] object files, left from Makefiles +// _obj/ old object directory, left from Makefiles +// _test/ old test directory, left from Makefiles +// _testmain.go old gotest file, left from Makefiles +// test.out old test log, left from Makefiles +// build.out old test log, left from Makefiles +// *.[568ao] object files, left from Makefiles // -// DIR(.exe) from go build -// DIR.test(.exe) from go test -c -// MAINFILE(.exe) from go build MAINFILE.go -// *.so from SWIG +// DIR(.exe) from go build +// DIR.test(.exe) from go test -c +// MAINFILE(.exe) from go build MAINFILE.go +// *.so from SWIG // // In the list, DIR represents the final path element of the // directory, and MAINFILE is the base name of any Go source @@ -304,12 +303,11 @@ // // For more about specifying packages, see 'go help packages'. // -// -// Show documentation for package or symbol +// # Show documentation for package or symbol // // Usage: // -// go doc [doc flags] [package|[package.]symbol[.methodOrField]] +// go doc [doc flags] [package|[package.]symbol[.methodOrField]] // // Doc prints the documentation comments associated with the item identified by its // arguments (a package, const, func, type, var, method, or struct field) @@ -321,7 +319,7 @@ // // Given no arguments, that is, when run as // -// go doc +// go doc // // it prints the package documentation for the package in the current directory. // If the package is a command (package main), the exported symbols of the package @@ -332,10 +330,10 @@ // on what is installed in GOROOT and GOPATH, as well as the form of the argument, // which is schematically one of these: // -// go doc -// go doc [.] -// go doc [.][.] -// go doc [.][.] +// go doc +// go doc [.] +// go doc [.][.] +// go doc [.][.] // // The first item in this list matched by the argument is the one whose documentation // is printed. (See the examples below.) However, if the argument starts with a capital @@ -357,7 +355,7 @@ // When run with two arguments, the first is a package path (full path or suffix), // and the second is a symbol, or symbol with method or struct field: // -// go doc [.] +// go doc [.] // // In all forms, when matching symbols, lower-case letters in the argument match // either case but upper-case letters match exactly. This means that there may be @@ -365,68 +363,69 @@ // different cases. If this occurs, documentation for all matches is printed. // // Examples: -// go doc -// Show documentation for current package. -// go doc Foo -// Show documentation for Foo in the current package. -// (Foo starts with a capital letter so it cannot match -// a package path.) -// go doc encoding/json -// Show documentation for the encoding/json package. -// go doc json -// Shorthand for encoding/json. -// go doc json.Number (or go doc json.number) -// Show documentation and method summary for json.Number. -// go doc json.Number.Int64 (or go doc json.number.int64) -// Show documentation for json.Number's Int64 method. -// go doc cmd/doc -// Show package docs for the doc command. -// go doc -cmd cmd/doc -// Show package docs and exported symbols within the doc command. -// go doc template.new -// Show documentation for html/template's New function. -// (html/template is lexically before text/template) -// go doc text/template.new # One argument -// Show documentation for text/template's New function. -// go doc text/template new # Two arguments -// Show documentation for text/template's New function. // -// At least in the current tree, these invocations all print the -// documentation for json.Decoder's Decode method: +// go doc +// Show documentation for current package. +// go doc Foo +// Show documentation for Foo in the current package. +// (Foo starts with a capital letter so it cannot match +// a package path.) +// go doc encoding/json +// Show documentation for the encoding/json package. +// go doc json +// Shorthand for encoding/json. +// go doc json.Number (or go doc json.number) +// Show documentation and method summary for json.Number. +// go doc json.Number.Int64 (or go doc json.number.int64) +// Show documentation for json.Number's Int64 method. +// go doc cmd/doc +// Show package docs for the doc command. +// go doc -cmd cmd/doc +// Show package docs and exported symbols within the doc command. +// go doc template.new +// Show documentation for html/template's New function. +// (html/template is lexically before text/template) +// go doc text/template.new # One argument +// Show documentation for text/template's New function. +// go doc text/template new # Two arguments +// Show documentation for text/template's New function. // -// go doc json.Decoder.Decode -// go doc json.decoder.decode -// go doc json.decode -// cd go/src/encoding/json; go doc decode +// At least in the current tree, these invocations all print the +// documentation for json.Decoder's Decode method: +// +// go doc json.Decoder.Decode +// go doc json.decoder.decode +// go doc json.decode +// cd go/src/encoding/json; go doc decode // // Flags: -// -all -// Show all the documentation for the package. -// -c -// Respect case when matching symbols. -// -cmd -// Treat a command (package main) like a regular package. -// Otherwise package main's exported symbols are hidden -// when showing the package's top-level documentation. -// -short -// One-line representation for each symbol. -// -src -// Show the full source code for the symbol. This will -// display the full Go source of its declaration and -// definition, such as a function definition (including -// the body), type declaration or enclosing const -// block. The output may therefore include unexported -// details. -// -u -// Show documentation for unexported as well as exported -// symbols, methods, and fields. // +// -all +// Show all the documentation for the package. +// -c +// Respect case when matching symbols. +// -cmd +// Treat a command (package main) like a regular package. +// Otherwise package main's exported symbols are hidden +// when showing the package's top-level documentation. +// -short +// One-line representation for each symbol. +// -src +// Show the full source code for the symbol. This will +// display the full Go source of its declaration and +// definition, such as a function definition (including +// the body), type declaration or enclosing const +// block. The output may therefore include unexported +// details. +// -u +// Show documentation for unexported as well as exported +// symbols, methods, and fields. // -// Print Go environment information +// # Print Go environment information // // Usage: // -// go env [-json] [-u] [-w] [var ...] +// go env [-json] [-u] [-w] [var ...] // // Env prints Go environment information. // @@ -448,12 +447,11 @@ // // For more about environment variables, see 'go help environment'. // -// -// Update packages to use new APIs +// # Update packages to use new APIs // // Usage: // -// go fix [-fix list] [packages] +// go fix [-fix list] [packages] // // Fix runs the Go fix command on the packages named by the import paths. // @@ -468,12 +466,11 @@ // // See also: go fmt, go vet. // -// -// Gofmt (reformat) package sources +// # Gofmt (reformat) package sources // // Usage: // -// go fmt [-n] [-x] [packages] +// go fmt [-n] [-x] [packages] // // Fmt runs the command 'gofmt -l -w' on the packages named // by the import paths. It prints the names of the files that are modified. @@ -491,12 +488,11 @@ // // See also: go fix, go vet. // -// -// Generate Go files by processing source +// # Generate Go files by processing source // // Usage: // -// go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages] +// go generate [-run regexp] [-n] [-v] [-x] [build flags] [file.go... | packages] // // Generate runs commands described by directives within existing // files. Those commands can run any process but the intent is to @@ -508,7 +504,7 @@ // Go generate scans the file for directives, which are lines of // the form, // -// //go:generate command argument... +// //go:generate command argument... // // (note: no leading spaces and no space in "//go") where command // is the generator to be run, corresponding to an executable file @@ -531,25 +527,28 @@ // generated source should have a line that matches the following // regular expression (in Go syntax): // -// ^// Code generated .* DO NOT EDIT\.$ +// ^// Code generated .* DO NOT EDIT\.$ // // This line must appear before the first non-comment, non-blank // text in the file. // // Go generate sets several variables when it runs the generator: // -// $GOARCH -// The execution architecture (arm, amd64, etc.) -// $GOOS -// The execution operating system (linux, windows, etc.) -// $GOFILE -// The base name of the file. -// $GOLINE -// The line number of the directive in the source file. -// $GOPACKAGE -// The name of the package of the file containing the directive. -// $DOLLAR -// A dollar sign. +// $GOARCH +// The execution architecture (arm, amd64, etc.) +// $GOOS +// The execution operating system (linux, windows, etc.) +// $GOFILE +// The base name of the file. +// $GOLINE +// The line number of the directive in the source file. +// $GOPACKAGE +// The name of the package of the file containing the directive. +// $GOROOT +// The GOROOT directory for the 'go' command that invoked the +// generator, containing the Go toolchain and standard library. +// $DOLLAR +// A dollar sign. // // Other than variable substitution and quoted-string evaluation, no // special processing such as "globbing" is performed on the command @@ -565,14 +564,14 @@ // // A directive of the form, // -// //go:generate -command xxx args... +// //go:generate -command xxx args... // // specifies, for the remainder of this source file only, that the // string xxx represents the command identified by the arguments. This // can be used to create aliases or to handle multiword generators. // For example, // -// //go:generate -command foo go tool foo +// //go:generate -command foo go tool foo // // specifies that the command "foo" represents the generator // "go tool foo". @@ -596,11 +595,11 @@ // // Go generate accepts one specific flag: // -// -run="" -// if non-empty, specifies a regular expression to select -// directives whose full original source text (excluding -// any trailing spaces and final newline) matches the -// expression. +// -run="" +// if non-empty, specifies a regular expression to select +// directives whose full original source text (excluding +// any trailing spaces and final newline) matches the +// expression. // // It also accepts the standard build flags including -v, -n, and -x. // The -v flag prints the names of packages and files as they are @@ -612,12 +611,11 @@ // // For more about specifying packages, see 'go help packages'. // -// -// Add dependencies to current module and install them +// # Add dependencies to current module and install them // // Usage: // -// go get [-t] [-u] [-v] [build flags] [packages] +// go get [-t] [-u] [-v] [build flags] [packages] // // Get resolves its command-line arguments to packages at specific module versions, // updates go.mod to require those versions, and downloads source code into the @@ -625,15 +623,15 @@ // // To add a dependency for a package or upgrade it to its latest version: // -// go get example.com/pkg +// go get example.com/pkg // // To upgrade or downgrade a package to a specific version: // -// go get example.com/pkg@v1.2.3 +// go get example.com/pkg@v1.2.3 // // To remove a dependency on a module and downgrade modules that require it: // -// go get example.com/mod@none +// go get example.com/mod@none // // See https://golang.org/ref/mod#go-get for details. // @@ -643,8 +641,8 @@ // 'go install' runs in module-aware mode and ignores the go.mod file in the // current directory. For example: // -// go install example.com/pkg@v1.2.3 -// go install example.com/pkg@latest +// go install example.com/pkg@v1.2.3 +// go install example.com/pkg@latest // // See 'go help install' or https://golang.org/ref/mod#go-install for details. // @@ -678,12 +676,11 @@ // // See also: go build, go install, go clean, go mod. // -// -// Compile and install packages and dependencies +// # Compile and install packages and dependencies // // Usage: // -// go install [build flags] [packages] +// go install [build flags] [packages] // // Install compiles and installs the packages named by the import paths. // @@ -738,12 +735,11 @@ // // See also: go build, go get, go clean. // -// -// List packages or modules +// # List packages or modules // // Usage: // -// go list [-f format] [-json] [-m] [list flags] [build flags] [packages] +// go list [-f format] [-json] [-m] [list flags] [build flags] [packages] // // List lists the named packages, one per line. // The most commonly-used flags are -f and -json, which control the form @@ -752,83 +748,83 @@ // // The default output shows the package import path: // -// bytes -// encoding/json -// github.com/gorilla/mux -// golang.org/x/net/html +// bytes +// encoding/json +// github.com/gorilla/mux +// golang.org/x/net/html // // The -f flag specifies an alternate format for the list, using the // syntax of package template. The default output is equivalent // to -f '{{.ImportPath}}'. The struct being passed to the template is: // -// type Package struct { -// Dir string // directory containing package sources -// ImportPath string // import path of package in dir -// ImportComment string // path in import comment on package statement -// Name string // package name -// Doc string // package documentation string -// Target string // install path -// Shlib string // the shared library that contains this package (only set when -linkshared) -// Goroot bool // is this package in the Go root? -// Standard bool // is this package part of the standard Go library? -// Stale bool // would 'go install' do anything for this package? -// StaleReason string // explanation for Stale==true -// Root string // Go root or Go path dir containing this package -// ConflictDir string // this directory shadows Dir in $GOPATH -// BinaryOnly bool // binary-only package (no longer supported) -// ForTest string // package is only for use in named test -// Export string // file containing export data (when using -export) -// BuildID string // build ID of the compiled package (when using -export) -// Module *Module // info about package's containing module, if any (can be nil) -// Match []string // command-line patterns matching this package -// DepOnly bool // package is only a dependency, not explicitly listed +// type Package struct { +// Dir string // directory containing package sources +// ImportPath string // import path of package in dir +// ImportComment string // path in import comment on package statement +// Name string // package name +// Doc string // package documentation string +// Target string // install path +// Shlib string // the shared library that contains this package (only set when -linkshared) +// Goroot bool // is this package in the Go root? +// Standard bool // is this package part of the standard Go library? +// Stale bool // would 'go install' do anything for this package? +// StaleReason string // explanation for Stale==true +// Root string // Go root or Go path dir containing this package +// ConflictDir string // this directory shadows Dir in $GOPATH +// BinaryOnly bool // binary-only package (no longer supported) +// ForTest string // package is only for use in named test +// Export string // file containing export data (when using -export) +// BuildID string // build ID of the compiled package (when using -export) +// Module *Module // info about package's containing module, if any (can be nil) +// Match []string // command-line patterns matching this package +// DepOnly bool // package is only a dependency, not explicitly listed // -// // Source files -// GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) -// CgoFiles []string // .go source files that import "C" -// CompiledGoFiles []string // .go files presented to compiler (when using -compiled) -// IgnoredGoFiles []string // .go source files ignored due to build constraints -// IgnoredOtherFiles []string // non-.go source files ignored due to build constraints -// CFiles []string // .c source files -// CXXFiles []string // .cc, .cxx and .cpp source files -// MFiles []string // .m source files -// HFiles []string // .h, .hh, .hpp and .hxx source files -// FFiles []string // .f, .F, .for and .f90 Fortran source files -// SFiles []string // .s source files -// SwigFiles []string // .swig files -// SwigCXXFiles []string // .swigcxx files -// SysoFiles []string // .syso object files to add to archive -// TestGoFiles []string // _test.go files in package -// XTestGoFiles []string // _test.go files outside package +// // Source files +// GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles) +// CgoFiles []string // .go source files that import "C" +// CompiledGoFiles []string // .go files presented to compiler (when using -compiled) +// IgnoredGoFiles []string // .go source files ignored due to build constraints +// IgnoredOtherFiles []string // non-.go source files ignored due to build constraints +// CFiles []string // .c source files +// CXXFiles []string // .cc, .cxx and .cpp source files +// MFiles []string // .m source files +// HFiles []string // .h, .hh, .hpp and .hxx source files +// FFiles []string // .f, .F, .for and .f90 Fortran source files +// SFiles []string // .s source files +// SwigFiles []string // .swig files +// SwigCXXFiles []string // .swigcxx files +// SysoFiles []string // .syso object files to add to archive +// TestGoFiles []string // _test.go files in package +// XTestGoFiles []string // _test.go files outside package // -// // Embedded files -// EmbedPatterns []string // //go:embed patterns -// EmbedFiles []string // files matched by EmbedPatterns -// TestEmbedPatterns []string // //go:embed patterns in TestGoFiles -// TestEmbedFiles []string // files matched by TestEmbedPatterns -// XTestEmbedPatterns []string // //go:embed patterns in XTestGoFiles -// XTestEmbedFiles []string // files matched by XTestEmbedPatterns +// // Embedded files +// EmbedPatterns []string // //go:embed patterns +// EmbedFiles []string // files matched by EmbedPatterns +// TestEmbedPatterns []string // //go:embed patterns in TestGoFiles +// TestEmbedFiles []string // files matched by TestEmbedPatterns +// XTestEmbedPatterns []string // //go:embed patterns in XTestGoFiles +// XTestEmbedFiles []string // files matched by XTestEmbedPatterns // -// // Cgo directives -// CgoCFLAGS []string // cgo: flags for C compiler -// CgoCPPFLAGS []string // cgo: flags for C preprocessor -// CgoCXXFLAGS []string // cgo: flags for C++ compiler -// CgoFFLAGS []string // cgo: flags for Fortran compiler -// CgoLDFLAGS []string // cgo: flags for linker -// CgoPkgConfig []string // cgo: pkg-config names +// // Cgo directives +// CgoCFLAGS []string // cgo: flags for C compiler +// CgoCPPFLAGS []string // cgo: flags for C preprocessor +// CgoCXXFLAGS []string // cgo: flags for C++ compiler +// CgoFFLAGS []string // cgo: flags for Fortran compiler +// CgoLDFLAGS []string // cgo: flags for linker +// CgoPkgConfig []string // cgo: pkg-config names // -// // Dependency information -// Imports []string // import paths used by this package -// ImportMap map[string]string // map from source import to ImportPath (identity entries omitted) -// Deps []string // all (recursively) imported dependencies -// TestImports []string // imports from TestGoFiles -// XTestImports []string // imports from XTestGoFiles +// // Dependency information +// Imports []string // import paths used by this package +// ImportMap map[string]string // map from source import to ImportPath (identity entries omitted) +// Deps []string // all (recursively) imported dependencies +// TestImports []string // imports from TestGoFiles +// XTestImports []string // imports from XTestGoFiles // -// // Error information -// Incomplete bool // this package or a dependency has an error -// Error *PackageError // error loading package -// DepsErrors []*PackageError // errors loading dependencies -// } +// // Error information +// Incomplete bool // this package or a dependency has an error +// Error *PackageError // error loading package +// DepsErrors []*PackageError // errors loading dependencies +// } // // Packages stored in vendor directories report an ImportPath that includes the // path to the vendor directory (for example, "d/vendor/p" instead of "p"), @@ -838,11 +834,11 @@ // // The error information, if any, is // -// type PackageError struct { -// ImportStack []string // shortest path from package named on command line to this one -// Pos string // position of error (if present, file:line:col) -// Err string // the error itself -// } +// type PackageError struct { +// ImportStack []string // shortest path from package named on command line to this one +// Pos string // position of error (if present, file:line:col) +// Err string // the error itself +// } // // The module information is a Module struct, defined in the discussion // of list -m below. @@ -851,19 +847,19 @@ // // The template function "context" returns the build context, defined as: // -// type Context struct { -// GOARCH string // target architecture -// GOOS string // target operating system -// GOROOT string // Go root -// GOPATH string // Go path -// CgoEnabled bool // whether cgo can be used -// UseAllFiles bool // use files regardless of +build lines, file names -// Compiler string // compiler to assume when computing target paths -// BuildTags []string // build constraints to match in +build lines -// ToolTags []string // toolchain-specific build constraints -// ReleaseTags []string // releases the current release is compatible with -// InstallSuffix string // suffix to use in the name of the install dir -// } +// type Context struct { +// GOARCH string // target architecture +// GOOS string // target operating system +// GOROOT string // Go root +// GOPATH string // Go path +// CgoEnabled bool // whether cgo can be used +// UseAllFiles bool // use files regardless of +build lines, file names +// Compiler string // compiler to assume when computing target paths +// BuildTags []string // build constraints to match in +build lines +// ToolTags []string // toolchain-specific build constraints +// ReleaseTags []string // releases the current release is compatible with +// InstallSuffix string // suffix to use in the name of the install dir +// } // // For more information about the meaning of these fields see the documentation // for the go/build package's Context type. @@ -930,25 +926,25 @@ // When listing modules, the -f flag still specifies a format template // applied to a Go struct, but now a Module struct: // -// type Module struct { -// Path string // module path -// Version string // module version -// Versions []string // available module versions (with -versions) -// Replace *Module // replaced by this module -// Time *time.Time // time version was created -// Update *Module // available update, if any (with -u) -// Main bool // is this the main module? -// Indirect bool // is this module only an indirect dependency of main module? -// Dir string // directory holding files for this module, if any -// GoMod string // path to go.mod file used when loading this module, if any -// GoVersion string // go version used in module -// Retracted string // retraction information, if any (with -retracted or -u) -// Error *ModuleError // error loading module -// } +// type Module struct { +// Path string // module path +// Version string // module version +// Versions []string // available module versions (with -versions) +// Replace *Module // replaced by this module +// Time *time.Time // time version was created +// Update *Module // available update, if any (with -u) +// Main bool // is this the main module? +// Indirect bool // is this module only an indirect dependency of main module? +// Dir string // directory holding files for this module, if any +// GoMod string // path to go.mod file used when loading this module, if any +// GoVersion string // go version used in module +// Retracted string // retraction information, if any (with -retracted or -u) +// Error *ModuleError // error loading module +// } // -// type ModuleError struct { -// Err string // the error itself -// } +// type ModuleError struct { +// Err string // the error itself +// } // // The file GoMod refers to may be outside the module directory if the // module is in the module cache or if the -modfile flag is used. @@ -957,9 +953,9 @@ // information about the version and replacement if any. // For example, 'go list -m all' might print: // -// my/main/module -// golang.org/x/text v0.3.0 => /tmp/text -// rsc.io/pdf v0.1.1 +// my/main/module +// golang.org/x/text v0.3.0 => /tmp/text +// rsc.io/pdf v0.1.1 // // The Module struct has a String method that formats this // line of output, so that the default format is equivalent @@ -981,9 +977,9 @@ // If a version is retracted, the string "(retracted)" will follow it. // For example, 'go list -m -u all' might print: // -// my/main/module -// golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text -// rsc.io/pdf v0.1.1 (retracted) [v0.1.2] +// my/main/module +// golang.org/x/text v0.3.0 [v0.4.0] => /tmp/text +// rsc.io/pdf v0.1.1 (retracted) [v0.1.2] // // (For tools, 'go list -m -u -json all' may be more convenient to parse.) // @@ -1026,8 +1022,7 @@ // // For more about modules, see https://golang.org/ref/mod. // -// -// Module maintenance +// # Module maintenance // // Go mod provides access to operations on modules. // @@ -1038,26 +1033,26 @@ // // Usage: // -// go mod [arguments] +// go mod [arguments] // // The commands are: // -// download download modules to local cache -// edit edit go.mod from tools or scripts -// graph print module requirement graph -// init initialize new module in current directory -// tidy add missing and remove unused modules -// vendor make vendored copy of dependencies -// verify verify dependencies have expected content -// why explain why packages or modules are needed +// download download modules to local cache +// edit edit go.mod from tools or scripts +// graph print module requirement graph +// init initialize new module in current directory +// tidy add missing and remove unused modules +// vendor make vendored copy of dependencies +// verify verify dependencies have expected content +// why explain why packages or modules are needed // // Use "go help mod " for more information about a command. // -// Download modules to local cache +// # Download modules to local cache // // Usage: // -// go mod download [-x] [-json] [modules] +// go mod download [-x] [-json] [modules] // // Download downloads the named modules, which can be module patterns selecting // dependencies of the main module or module queries of the form path@version. @@ -1078,17 +1073,17 @@ // to standard output, describing each downloaded module (or failure), // corresponding to this Go struct: // -// type Module struct { -// Path string // module path -// Version string // module version -// Error string // error loading module -// Info string // absolute path to cached .info file -// GoMod string // absolute path to cached .mod file -// Zip string // absolute path to cached .zip file -// Dir string // absolute path to cached source root directory -// Sum string // checksum for path, version (as in go.sum) -// GoModSum string // checksum for go.mod (as in go.sum) -// } +// type Module struct { +// Path string // module path +// Version string // module version +// Error string // error loading module +// Info string // absolute path to cached .info file +// GoMod string // absolute path to cached .mod file +// Zip string // absolute path to cached .zip file +// Dir string // absolute path to cached source root directory +// Sum string // checksum for path, version (as in go.sum) +// GoModSum string // checksum for go.mod (as in go.sum) +// } // // The -x flag causes download to print the commands download executes. // @@ -1096,12 +1091,11 @@ // // See https://golang.org/ref/mod#version-queries for more about version queries. // -// -// Edit go.mod from tools or scripts +// # Edit go.mod from tools or scripts // // Usage: // -// go mod edit [editing flags] [-fmt|-print|-json] [go.mod] +// go mod edit [editing flags] [-fmt|-print|-json] [go.mod] // // Edit provides a command-line interface for editing go.mod, // for use primarily by tools or scripts. It reads only go.mod; @@ -1159,41 +1153,41 @@ // The -json flag prints the final go.mod file in JSON format instead of // writing it back to go.mod. The JSON output corresponds to these Go types: // -// type Module struct { -// Path string -// Version string -// } +// type Module struct { +// Path string +// Version string +// } // -// type GoMod struct { -// Module ModPath -// Go string -// Require []Require -// Exclude []Module -// Replace []Replace -// Retract []Retract -// } +// type GoMod struct { +// Module ModPath +// Go string +// Require []Require +// Exclude []Module +// Replace []Replace +// Retract []Retract +// } // -// type ModPath struct { -// Path string -// Deprecated string -// } +// type ModPath struct { +// Path string +// Deprecated string +// } // -// type Require struct { -// Path string -// Version string -// Indirect bool -// } +// type Require struct { +// Path string +// Version string +// Indirect bool +// } // -// type Replace struct { -// Old Module -// New Module -// } +// type Replace struct { +// Old Module +// New Module +// } // -// type Retract struct { -// Low string -// High string -// Rationale string -// } +// type Retract struct { +// Low string +// High string +// Rationale string +// } // // Retract entries representing a single version (not an interval) will have // the "Low" and "High" fields set to the same value. @@ -1204,12 +1198,11 @@ // // See https://golang.org/ref/mod#go-mod-edit for more about 'go mod edit'. // -// -// Print module requirement graph +// # Print module requirement graph // // Usage: // -// go mod graph [-go=version] +// go mod graph [-go=version] // // Graph prints the module requirement graph (with replacements applied) // in text form. Each line in the output has two space-separated fields: a module @@ -1222,12 +1215,11 @@ // // See https://golang.org/ref/mod#go-mod-graph for more about 'go mod graph'. // -// -// Initialize new module in current directory +// # Initialize new module in current directory // // Usage: // -// go mod init [module-path] +// go mod init [module-path] // // Init initializes and writes a new go.mod file in the current directory, in // effect creating a new module rooted at the current directory. The go.mod file @@ -1243,12 +1235,11 @@ // // See https://golang.org/ref/mod#go-mod-init for more about 'go mod init'. // -// -// Add missing and remove unused modules +// # Add missing and remove unused modules // // Usage: // -// go mod tidy [-e] [-v] [-go=version] [-compat=version] +// go mod tidy [-e] [-v] [-go=version] [-compat=version] // // Tidy makes sure go.mod matches the source code in the module. // It adds any missing modules necessary to build the current module's @@ -1278,12 +1269,11 @@ // // See https://golang.org/ref/mod#go-mod-tidy for more about 'go mod tidy'. // -// -// Make vendored copy of dependencies +// # Make vendored copy of dependencies // // Usage: // -// go mod vendor [-e] [-v] [-o outdir] +// go mod vendor [-e] [-v] [-o outdir] // // Vendor resets the main module's vendor directory to include all packages // needed to build and test all the main module's packages. @@ -1302,12 +1292,11 @@ // // See https://golang.org/ref/mod#go-mod-vendor for more about 'go mod vendor'. // -// -// Verify dependencies have expected content +// # Verify dependencies have expected content // // Usage: // -// go mod verify +// go mod verify // // Verify checks that the dependencies of the current module, // which are stored in a local downloaded source cache, have not been @@ -1318,12 +1307,11 @@ // // See https://golang.org/ref/mod#go-mod-verify for more about 'go mod verify'. // -// -// Explain why packages or modules are needed +// # Explain why packages or modules are needed // // Usage: // -// go mod why [-m] [-vendor] packages... +// go mod why [-m] [-vendor] packages... // // Why shows a shortest path in the import graph from the main module to // each of the listed packages. If the -m flag is given, why treats the @@ -1344,20 +1332,19 @@ // // For example: // -// $ go mod why golang.org/x/text/language golang.org/x/text/encoding -// # golang.org/x/text/language -// rsc.io/quote -// rsc.io/sampler -// golang.org/x/text/language +// $ go mod why golang.org/x/text/language golang.org/x/text/encoding +// # golang.org/x/text/language +// rsc.io/quote +// rsc.io/sampler +// golang.org/x/text/language // -// # golang.org/x/text/encoding -// (main module does not need package golang.org/x/text/encoding) -// $ +// # golang.org/x/text/encoding +// (main module does not need package golang.org/x/text/encoding) +// $ // // See https://golang.org/ref/mod#go-mod-why for more about 'go mod why'. // -// -// Workspace maintenance +// # Workspace maintenance // // Work provides access to operations on workspaces. // @@ -1382,20 +1369,20 @@ // go.work files are line-oriented. Each line holds a single directive, // made up of a keyword followed by arguments. For example: // -// go 1.18 +// go 1.18 // -// use ../foo/bar -// use ./baz +// use ../foo/bar +// use ./baz // -// replace example.com/foo v1.2.3 => example.com/bar v1.4.5 +// replace example.com/foo v1.2.3 => example.com/bar v1.4.5 // // The leading keyword can be factored out of adjacent lines to create a block, // like in Go imports. // -// use ( -// ../foo/bar -// ./baz -// ) +// use ( +// ../foo/bar +// ./baz +// ) // // The use directive specifies a module to be included in the workspace's // set of main modules. The argument to the use directive is the directory @@ -1417,22 +1404,22 @@ // // Usage: // -// go work [arguments] +// go work [arguments] // // The commands are: // -// edit edit go.work from tools or scripts -// init initialize workspace file -// sync sync workspace build list to modules -// use add modules to workspace file +// edit edit go.work from tools or scripts +// init initialize workspace file +// sync sync workspace build list to modules +// use add modules to workspace file // // Use "go help work " for more information about a command. // -// Edit go.work from tools or scripts +// # Edit go.work from tools or scripts // // Usage: // -// go work edit [editing flags] [go.work] +// go work edit [editing flags] [go.work] // // Edit provides a command-line interface for editing go.work, // for use primarily by tools or scripts. It only reads go.work; @@ -1473,36 +1460,35 @@ // The -json flag prints the final go.work file in JSON format instead of // writing it back to go.mod. The JSON output corresponds to these Go types: // -// type GoWork struct { -// Go string -// Use []Use -// Replace []Replace -// } +// type GoWork struct { +// Go string +// Use []Use +// Replace []Replace +// } // -// type Use struct { -// DiskPath string -// ModulePath string -// } +// type Use struct { +// DiskPath string +// ModulePath string +// } // -// type Replace struct { -// Old Module -// New Module -// } +// type Replace struct { +// Old Module +// New Module +// } // -// type Module struct { -// Path string -// Version string -// } +// type Module struct { +// Path string +// Version string +// } // // See the workspaces reference at https://go.dev/ref/mod#workspaces // for more information. // -// -// Initialize workspace file +// # Initialize workspace file // // Usage: // -// go work init [moddirs] +// go work init [moddirs] // // Init initializes and writes a new go.work file in the // current directory, in effect creating a new workspace at the current @@ -1518,12 +1504,11 @@ // See the workspaces reference at https://go.dev/ref/mod#workspaces // for more information. // -// -// Sync workspace build list to modules +// # Sync workspace build list to modules // // Usage: // -// go work sync +// go work sync // // Sync syncs the workspace's build list back to the // workspace's modules @@ -1544,12 +1529,11 @@ // See the workspaces reference at https://go.dev/ref/mod#workspaces // for more information. // -// -// Add modules to workspace file +// # Add modules to workspace file // // Usage: // -// go work use [-r] moddirs +// go work use [-r] moddirs // // Use provides a command-line interface for adding // directories, optionally recursively, to a go.work file. @@ -1566,12 +1550,11 @@ // See the workspaces reference at https://go.dev/ref/mod#workspaces // for more information. // -// -// Compile and run Go program +// # Compile and run Go program // // Usage: // -// go run [build flags] [-exec xprog] package [arguments...] +// go run [build flags] [-exec xprog] package [arguments...] // // Run compiles and runs the named main Go package. // Typically the package is specified as a list of .go source files from a single @@ -1591,7 +1574,9 @@ // // By default, 'go run' runs the compiled binary directly: 'a.out arguments...'. // If the -exec flag is given, 'go run' invokes the binary using xprog: -// 'xprog a.out arguments...'. +// +// 'xprog a.out arguments...'. +// // If the -exec flag is not given, GOOS or GOARCH is different from the system // default, and a program named go_$GOOS_$GOARCH_exec can be found // on the current search path, 'go run' invokes the binary using that program, @@ -1610,20 +1595,19 @@ // // See also: go build. // -// -// Test packages +// # Test packages // // Usage: // -// go test [build/test flags] [packages] [build/test flags & test binary flags] +// go test [build/test flags] [packages] [build/test flags & test binary flags] // // 'Go test' automates testing the packages named by the import paths. // It prints a summary of the test results in the format: // -// ok archive/tar 0.011s -// FAIL archive/zip 0.022s -// ok compress/gzip 0.033s -// ... +// ok archive/tar 0.011s +// FAIL archive/zip 0.022s +// ok compress/gzip 0.033s +// ... // // followed by detailed output for each failed package. // @@ -1702,33 +1686,33 @@ // // In addition to the build flags, the flags handled by 'go test' itself are: // -// -args -// Pass the remainder of the command line (everything after -args) -// to the test binary, uninterpreted and unchanged. -// Because this flag consumes the remainder of the command line, -// the package list (if present) must appear before this flag. +// -args +// Pass the remainder of the command line (everything after -args) +// to the test binary, uninterpreted and unchanged. +// Because this flag consumes the remainder of the command line, +// the package list (if present) must appear before this flag. // -// -c -// Compile the test binary to pkg.test but do not run it -// (where pkg is the last element of the package's import path). -// The file name can be changed with the -o flag. +// -c +// Compile the test binary to pkg.test but do not run it +// (where pkg is the last element of the package's import path). +// The file name can be changed with the -o flag. // -// -exec xprog -// Run the test binary using xprog. The behavior is the same as -// in 'go run'. See 'go help run' for details. +// -exec xprog +// Run the test binary using xprog. The behavior is the same as +// in 'go run'. See 'go help run' for details. // -// -i -// Install packages that are dependencies of the test. -// Do not run the test. -// The -i flag is deprecated. Compiled packages are cached automatically. +// -i +// Install packages that are dependencies of the test. +// Do not run the test. +// The -i flag is deprecated. Compiled packages are cached automatically. // -// -json -// Convert test output to JSON suitable for automated processing. -// See 'go doc test2json' for the encoding details. +// -json +// Convert test output to JSON suitable for automated processing. +// See 'go doc test2json' for the encoding details. // -// -o file -// Compile the test binary to the named file. -// The test still runs (unless -c or -i is specified). +// -o file +// Compile the test binary to the named file. +// The test still runs (unless -c or -i is specified). // // The test binary also accepts flags that control execution of the test; these // flags are also accessible by 'go test'. See 'go help testflag' for details. @@ -1738,12 +1722,11 @@ // // See also: go build, go vet. // -// -// Run specified go tool +// # Run specified go tool // // Usage: // -// go tool [-n] command [args...] +// go tool [-n] command [args...] // // Tool runs the go tool command identified by the arguments. // With no arguments it prints the list of known tools. @@ -1753,12 +1736,11 @@ // // For more about each tool command, see 'go doc cmd/'. // -// -// Print Go version +// # Print Go version // // Usage: // -// go version [-m] [-v] [file ...] +// go version [-m] [-v] [file ...] // // Version prints the build information for Go executables. // @@ -1780,12 +1762,11 @@ // // See also: go doc runtime/debug.BuildInfo. // -// -// Report likely mistakes in packages +// # Report likely mistakes in packages // // Usage: // -// go vet [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages] +// go vet [-n] [-x] [-vettool prog] [build flags] [vet flags] [packages] // // Vet runs the Go vet command on the packages named by the import paths. // @@ -1801,8 +1782,8 @@ // or additional checks. // For example, the 'shadow' analyzer can be built and run using these commands: // -// go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow -// go vet -vettool=$(which shadow) +// go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow +// go vet -vettool=$(which shadow) // // The build flags supported by go vet are those that control package resolution // and execution, such as -n, -x, -v, -tags, and -toolexec. @@ -1810,12 +1791,11 @@ // // See also: go fmt, go fix. // -// -// Build constraints +// # Build constraints // // A build constraint, also known as a build tag, is a line comment that begins // -// //go:build +// //go:build // // that lists the conditions under which a file should be included in the package. // Constraints may appear in any kind of source file (not just Go), but @@ -1834,31 +1814,33 @@ // build when the "linux" and "386" constraints are satisfied, or when // "darwin" is satisfied and "cgo" is not: // -// //go:build (linux && 386) || (darwin && !cgo) +// //go:build (linux && 386) || (darwin && !cgo) // // It is an error for a file to have more than one //go:build line. // // During a particular build, the following words are satisfied: // -// - the target operating system, as spelled by runtime.GOOS, set with the -// GOOS environment variable. -// - the target architecture, as spelled by runtime.GOARCH, set with the -// GOARCH environment variable. -// - "unix", if GOOS is a Unix or Unix-like system. -// - the compiler being used, either "gc" or "gccgo" -// - "cgo", if the cgo command is supported (see CGO_ENABLED in -// 'go help environment'). -// - a term for each Go major release, through the current version: -// "go1.1" from Go version 1.1 onward, "go1.12" from Go 1.12, and so on. -// - any additional tags given by the -tags flag (see 'go help build'). +// - the target operating system, as spelled by runtime.GOOS, set with the +// GOOS environment variable. +// - the target architecture, as spelled by runtime.GOARCH, set with the +// GOARCH environment variable. +// - "unix", if GOOS is a Unix or Unix-like system. +// - the compiler being used, either "gc" or "gccgo" +// - "cgo", if the cgo command is supported (see CGO_ENABLED in +// 'go help environment'). +// - a term for each Go major release, through the current version: +// "go1.1" from Go version 1.1 onward, "go1.12" from Go 1.12, and so on. +// - any additional tags given by the -tags flag (see 'go help build'). // // There are no separate build tags for beta or minor releases. // // If a file's name, after stripping the extension and a possible _test suffix, // matches any of the following patterns: -// *_GOOS -// *_GOARCH -// *_GOOS_GOARCH +// +// *_GOOS +// *_GOARCH +// *_GOOS_GOARCH +// // (example: source_windows_amd64.go) where GOOS and GOARCH represent // any known operating system and architecture values respectively, then // the file is considered to have an implicit build constraint requiring @@ -1875,19 +1857,19 @@ // // To keep a file from being considered for the build: // -// //go:build ignore +// //go:build ignore // // (any other unsatisfied word will work as well, but "ignore" is conventional.) // // To build a file only when using cgo, and only on Linux and OS X: // -// //go:build cgo && (linux || darwin) +// //go:build cgo && (linux || darwin) // // Such a file is usually paired with another file implementing the // default functionality for other systems, which in this case would // carry the constraint: // -// //go:build !(cgo && (linux || darwin)) +// //go:build !(cgo && (linux || darwin)) // // Naming a file dns_windows.go will cause it to be included only when // building the package for Windows; similarly, math_386.s will be included @@ -1897,57 +1879,55 @@ // with a "// +build" prefix. The gofmt command will add an equivalent //go:build // constraint when encountering the older syntax. // -// -// Build modes +// # Build modes // // The 'go build' and 'go install' commands take a -buildmode argument which // indicates which kind of object file is to be built. Currently supported values // are: // -// -buildmode=archive -// Build the listed non-main packages into .a files. Packages named -// main are ignored. +// -buildmode=archive +// Build the listed non-main packages into .a files. Packages named +// main are ignored. // -// -buildmode=c-archive -// Build the listed main package, plus all packages it imports, -// into a C archive file. The only callable symbols will be those -// functions exported using a cgo //export comment. Requires -// exactly one main package to be listed. +// -buildmode=c-archive +// Build the listed main package, plus all packages it imports, +// into a C archive file. The only callable symbols will be those +// functions exported using a cgo //export comment. Requires +// exactly one main package to be listed. // -// -buildmode=c-shared -// Build the listed main package, plus all packages it imports, -// into a C shared library. The only callable symbols will -// be those functions exported using a cgo //export comment. -// Requires exactly one main package to be listed. +// -buildmode=c-shared +// Build the listed main package, plus all packages it imports, +// into a C shared library. The only callable symbols will +// be those functions exported using a cgo //export comment. +// Requires exactly one main package to be listed. // -// -buildmode=default -// Listed main packages are built into executables and listed -// non-main packages are built into .a files (the default -// behavior). +// -buildmode=default +// Listed main packages are built into executables and listed +// non-main packages are built into .a files (the default +// behavior). // -// -buildmode=shared -// Combine all the listed non-main packages into a single shared -// library that will be used when building with the -linkshared -// option. Packages named main are ignored. +// -buildmode=shared +// Combine all the listed non-main packages into a single shared +// library that will be used when building with the -linkshared +// option. Packages named main are ignored. // -// -buildmode=exe -// Build the listed main packages and everything they import into -// executables. Packages not named main are ignored. +// -buildmode=exe +// Build the listed main packages and everything they import into +// executables. Packages not named main are ignored. // -// -buildmode=pie -// Build the listed main packages and everything they import into -// position independent executables (PIE). Packages not named -// main are ignored. +// -buildmode=pie +// Build the listed main packages and everything they import into +// position independent executables (PIE). Packages not named +// main are ignored. // -// -buildmode=plugin -// Build the listed main packages, plus all packages that they -// import, into a Go plugin. Packages not named main are ignored. +// -buildmode=plugin +// Build the listed main packages, plus all packages that they +// import, into a Go plugin. Packages not named main are ignored. // // On AIX, when linking a C program that uses a Go archive built with // -buildmode=c-archive, you must pass -Wl,-bnoobjreorder to the C compiler. // -// -// Calling between Go and C +// # Calling between Go and C // // There are two different ways to call between Go and C/C++ code. // @@ -1965,8 +1945,7 @@ // compiler. The CC or CXX environment variables may be set to determine // the C or C++ compiler, respectively, to use. // -// -// Build and test caching +// # Build and test caching // // The go command caches build outputs for reuse in future builds. // The default location for cache data is a subdirectory named go-build @@ -2011,8 +1990,7 @@ // GODEBUG=gocachetest=1 causes the go command to print details of its // decisions about whether to reuse a cached test result. // -// -// Environment variables +// # Environment variables // // The go command and the tools it invokes consult environment variables // for configuration. If an environment variable is unset, the go command @@ -2028,217 +2006,216 @@ // // General-purpose environment variables: // -// GO111MODULE -// Controls whether the go command runs in module-aware mode or GOPATH mode. -// May be "off", "on", or "auto". -// See https://golang.org/ref/mod#mod-commands. -// GCCGO -// The gccgo command to run for 'go build -compiler=gccgo'. -// GOARCH -// The architecture, or processor, for which to compile code. -// Examples are amd64, 386, arm, ppc64. -// GOBIN -// The directory where 'go install' will install a command. -// GOCACHE -// The directory where the go command will store cached -// information for reuse in future builds. -// GOMODCACHE -// The directory where the go command will store downloaded modules. -// GODEBUG -// Enable various debugging facilities. See 'go doc runtime' -// for details. -// GOENV -// The location of the Go environment configuration file. -// Cannot be set using 'go env -w'. -// Setting GOENV=off in the environment disables the use of the -// default configuration file. -// GOFLAGS -// A space-separated list of -flag=value settings to apply -// to go commands by default, when the given flag is known by -// the current command. Each entry must be a standalone flag. -// Because the entries are space-separated, flag values must -// not contain spaces. Flags listed on the command line -// are applied after this list and therefore override it. -// GOINSECURE -// Comma-separated list of glob patterns (in the syntax of Go's path.Match) -// of module path prefixes that should always be fetched in an insecure -// manner. Only applies to dependencies that are being fetched directly. -// GOINSECURE does not disable checksum database validation. GOPRIVATE or -// GONOSUMDB may be used to achieve that. -// GOOS -// The operating system for which to compile code. -// Examples are linux, darwin, windows, netbsd. -// GOPATH -// For more details see: 'go help gopath'. -// GOPROXY -// URL of Go module proxy. See https://golang.org/ref/mod#environment-variables -// and https://golang.org/ref/mod#module-proxy for details. -// GOPRIVATE, GONOPROXY, GONOSUMDB -// Comma-separated list of glob patterns (in the syntax of Go's path.Match) -// of module path prefixes that should always be fetched directly -// or that should not be compared against the checksum database. -// See https://golang.org/ref/mod#private-modules. -// GOROOT -// The root of the go tree. -// GOSUMDB -// The name of checksum database to use and optionally its public key and -// URL. See https://golang.org/ref/mod#authenticating. -// GOTMPDIR -// The directory where the go command will write -// temporary source files, packages, and binaries. -// GOVCS -// Lists version control commands that may be used with matching servers. -// See 'go help vcs'. -// GOWORK -// In module aware mode, use the given go.work file as a workspace file. -// By default or when GOWORK is "auto", the go command searches for a -// file named go.work in the current directory and then containing directories -// until one is found. If a valid go.work file is found, the modules -// specified will collectively be used as the main modules. If GOWORK -// is "off", or a go.work file is not found in "auto" mode, workspace -// mode is disabled. +// GO111MODULE +// Controls whether the go command runs in module-aware mode or GOPATH mode. +// May be "off", "on", or "auto". +// See https://golang.org/ref/mod#mod-commands. +// GCCGO +// The gccgo command to run for 'go build -compiler=gccgo'. +// GOARCH +// The architecture, or processor, for which to compile code. +// Examples are amd64, 386, arm, ppc64. +// GOBIN +// The directory where 'go install' will install a command. +// GOCACHE +// The directory where the go command will store cached +// information for reuse in future builds. +// GOMODCACHE +// The directory where the go command will store downloaded modules. +// GODEBUG +// Enable various debugging facilities. See 'go doc runtime' +// for details. +// GOENV +// The location of the Go environment configuration file. +// Cannot be set using 'go env -w'. +// Setting GOENV=off in the environment disables the use of the +// default configuration file. +// GOFLAGS +// A space-separated list of -flag=value settings to apply +// to go commands by default, when the given flag is known by +// the current command. Each entry must be a standalone flag. +// Because the entries are space-separated, flag values must +// not contain spaces. Flags listed on the command line +// are applied after this list and therefore override it. +// GOINSECURE +// Comma-separated list of glob patterns (in the syntax of Go's path.Match) +// of module path prefixes that should always be fetched in an insecure +// manner. Only applies to dependencies that are being fetched directly. +// GOINSECURE does not disable checksum database validation. GOPRIVATE or +// GONOSUMDB may be used to achieve that. +// GOOS +// The operating system for which to compile code. +// Examples are linux, darwin, windows, netbsd. +// GOPATH +// For more details see: 'go help gopath'. +// GOPROXY +// URL of Go module proxy. See https://golang.org/ref/mod#environment-variables +// and https://golang.org/ref/mod#module-proxy for details. +// GOPRIVATE, GONOPROXY, GONOSUMDB +// Comma-separated list of glob patterns (in the syntax of Go's path.Match) +// of module path prefixes that should always be fetched directly +// or that should not be compared against the checksum database. +// See https://golang.org/ref/mod#private-modules. +// GOROOT +// The root of the go tree. +// GOSUMDB +// The name of checksum database to use and optionally its public key and +// URL. See https://golang.org/ref/mod#authenticating. +// GOTMPDIR +// The directory where the go command will write +// temporary source files, packages, and binaries. +// GOVCS +// Lists version control commands that may be used with matching servers. +// See 'go help vcs'. +// GOWORK +// In module aware mode, use the given go.work file as a workspace file. +// By default or when GOWORK is "auto", the go command searches for a +// file named go.work in the current directory and then containing directories +// until one is found. If a valid go.work file is found, the modules +// specified will collectively be used as the main modules. If GOWORK +// is "off", or a go.work file is not found in "auto" mode, workspace +// mode is disabled. // // Environment variables for use with cgo: // -// AR -// The command to use to manipulate library archives when -// building with the gccgo compiler. -// The default is 'ar'. -// CC -// The command to use to compile C code. -// CGO_ENABLED -// Whether the cgo command is supported. Either 0 or 1. -// CGO_CFLAGS -// Flags that cgo will pass to the compiler when compiling -// C code. -// CGO_CFLAGS_ALLOW -// A regular expression specifying additional flags to allow -// to appear in #cgo CFLAGS source code directives. -// Does not apply to the CGO_CFLAGS environment variable. -// CGO_CFLAGS_DISALLOW -// A regular expression specifying flags that must be disallowed -// from appearing in #cgo CFLAGS source code directives. -// Does not apply to the CGO_CFLAGS environment variable. -// CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW -// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, -// but for the C preprocessor. -// CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW -// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, -// but for the C++ compiler. -// CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW -// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, -// but for the Fortran compiler. -// CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW -// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, -// but for the linker. -// CXX -// The command to use to compile C++ code. -// FC -// The command to use to compile Fortran code. -// PKG_CONFIG -// Path to pkg-config tool. +// AR +// The command to use to manipulate library archives when +// building with the gccgo compiler. +// The default is 'ar'. +// CC +// The command to use to compile C code. +// CGO_ENABLED +// Whether the cgo command is supported. Either 0 or 1. +// CGO_CFLAGS +// Flags that cgo will pass to the compiler when compiling +// C code. +// CGO_CFLAGS_ALLOW +// A regular expression specifying additional flags to allow +// to appear in #cgo CFLAGS source code directives. +// Does not apply to the CGO_CFLAGS environment variable. +// CGO_CFLAGS_DISALLOW +// A regular expression specifying flags that must be disallowed +// from appearing in #cgo CFLAGS source code directives. +// Does not apply to the CGO_CFLAGS environment variable. +// CGO_CPPFLAGS, CGO_CPPFLAGS_ALLOW, CGO_CPPFLAGS_DISALLOW +// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, +// but for the C preprocessor. +// CGO_CXXFLAGS, CGO_CXXFLAGS_ALLOW, CGO_CXXFLAGS_DISALLOW +// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, +// but for the C++ compiler. +// CGO_FFLAGS, CGO_FFLAGS_ALLOW, CGO_FFLAGS_DISALLOW +// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, +// but for the Fortran compiler. +// CGO_LDFLAGS, CGO_LDFLAGS_ALLOW, CGO_LDFLAGS_DISALLOW +// Like CGO_CFLAGS, CGO_CFLAGS_ALLOW, and CGO_CFLAGS_DISALLOW, +// but for the linker. +// CXX +// The command to use to compile C++ code. +// FC +// The command to use to compile Fortran code. +// PKG_CONFIG +// Path to pkg-config tool. // // Architecture-specific environment variables: // -// GOARM -// For GOARCH=arm, the ARM architecture for which to compile. -// Valid values are 5, 6, 7. -// GO386 -// For GOARCH=386, how to implement floating point instructions. -// Valid values are sse2 (default), softfloat. -// GOAMD64 -// For GOARCH=amd64, the microarchitecture level for which to compile. -// Valid values are v1 (default), v2, v3, v4. -// See https://golang.org/wiki/MinimumRequirements#amd64 -// GOMIPS -// For GOARCH=mips{,le}, whether to use floating point instructions. -// Valid values are hardfloat (default), softfloat. -// GOMIPS64 -// For GOARCH=mips64{,le}, whether to use floating point instructions. -// Valid values are hardfloat (default), softfloat. -// GOPPC64 -// For GOARCH=ppc64{,le}, the target ISA (Instruction Set Architecture). -// Valid values are power8 (default), power9. -// GOWASM -// For GOARCH=wasm, comma-separated list of experimental WebAssembly features to use. -// Valid values are satconv, signext. +// GOARM +// For GOARCH=arm, the ARM architecture for which to compile. +// Valid values are 5, 6, 7. +// GO386 +// For GOARCH=386, how to implement floating point instructions. +// Valid values are sse2 (default), softfloat. +// GOAMD64 +// For GOARCH=amd64, the microarchitecture level for which to compile. +// Valid values are v1 (default), v2, v3, v4. +// See https://golang.org/wiki/MinimumRequirements#amd64 +// GOMIPS +// For GOARCH=mips{,le}, whether to use floating point instructions. +// Valid values are hardfloat (default), softfloat. +// GOMIPS64 +// For GOARCH=mips64{,le}, whether to use floating point instructions. +// Valid values are hardfloat (default), softfloat. +// GOPPC64 +// For GOARCH=ppc64{,le}, the target ISA (Instruction Set Architecture). +// Valid values are power8 (default), power9. +// GOWASM +// For GOARCH=wasm, comma-separated list of experimental WebAssembly features to use. +// Valid values are satconv, signext. // // Special-purpose environment variables: // -// GCCGOTOOLDIR -// If set, where to find gccgo tools, such as cgo. -// The default is based on how gccgo was configured. -// GOEXPERIMENT -// Comma-separated list of toolchain experiments to enable or disable. -// The list of available experiments may change arbitrarily over time. -// See src/internal/goexperiment/flags.go for currently valid values. -// Warning: This variable is provided for the development and testing -// of the Go toolchain itself. Use beyond that purpose is unsupported. -// GOROOT_FINAL -// The root of the installed Go tree, when it is -// installed in a location other than where it is built. -// File names in stack traces are rewritten from GOROOT to -// GOROOT_FINAL. -// GO_EXTLINK_ENABLED -// Whether the linker should use external linking mode -// when using -linkmode=auto with code that uses cgo. -// Set to 0 to disable external linking mode, 1 to enable it. -// GIT_ALLOW_PROTOCOL -// Defined by Git. A colon-separated list of schemes that are allowed -// to be used with git fetch/clone. If set, any scheme not explicitly -// mentioned will be considered insecure by 'go get'. -// Because the variable is defined by Git, the default value cannot -// be set using 'go env -w'. +// GCCGOTOOLDIR +// If set, where to find gccgo tools, such as cgo. +// The default is based on how gccgo was configured. +// GOEXPERIMENT +// Comma-separated list of toolchain experiments to enable or disable. +// The list of available experiments may change arbitrarily over time. +// See src/internal/goexperiment/flags.go for currently valid values. +// Warning: This variable is provided for the development and testing +// of the Go toolchain itself. Use beyond that purpose is unsupported. +// GOROOT_FINAL +// The root of the installed Go tree, when it is +// installed in a location other than where it is built. +// File names in stack traces are rewritten from GOROOT to +// GOROOT_FINAL. +// GO_EXTLINK_ENABLED +// Whether the linker should use external linking mode +// when using -linkmode=auto with code that uses cgo. +// Set to 0 to disable external linking mode, 1 to enable it. +// GIT_ALLOW_PROTOCOL +// Defined by Git. A colon-separated list of schemes that are allowed +// to be used with git fetch/clone. If set, any scheme not explicitly +// mentioned will be considered insecure by 'go get'. +// Because the variable is defined by Git, the default value cannot +// be set using 'go env -w'. // // Additional information available from 'go env' but not read from the environment: // -// GOEXE -// The executable file name suffix (".exe" on Windows, "" on other systems). -// GOGCCFLAGS -// A space-separated list of arguments supplied to the CC command. -// GOHOSTARCH -// The architecture (GOARCH) of the Go toolchain binaries. -// GOHOSTOS -// The operating system (GOOS) of the Go toolchain binaries. -// GOMOD -// The absolute path to the go.mod of the main module. -// If module-aware mode is enabled, but there is no go.mod, GOMOD will be -// os.DevNull ("/dev/null" on Unix-like systems, "NUL" on Windows). -// If module-aware mode is disabled, GOMOD will be the empty string. -// GOTOOLDIR -// The directory where the go tools (compile, cover, doc, etc...) are installed. -// GOVERSION -// The version of the installed Go tree, as reported by runtime.Version. +// GOEXE +// The executable file name suffix (".exe" on Windows, "" on other systems). +// GOGCCFLAGS +// A space-separated list of arguments supplied to the CC command. +// GOHOSTARCH +// The architecture (GOARCH) of the Go toolchain binaries. +// GOHOSTOS +// The operating system (GOOS) of the Go toolchain binaries. +// GOMOD +// The absolute path to the go.mod of the main module. +// If module-aware mode is enabled, but there is no go.mod, GOMOD will be +// os.DevNull ("/dev/null" on Unix-like systems, "NUL" on Windows). +// If module-aware mode is disabled, GOMOD will be the empty string. +// GOTOOLDIR +// The directory where the go tools (compile, cover, doc, etc...) are installed. +// GOVERSION +// The version of the installed Go tree, as reported by runtime.Version. // -// -// File types +// # File types // // The go command examines the contents of a restricted set of files // in each directory. It identifies which files to examine based on // the extension of the file name. These extensions are: // -// .go -// Go source files. -// .c, .h -// C source files. -// If the package uses cgo or SWIG, these will be compiled with the -// OS-native compiler (typically gcc); otherwise they will -// trigger an error. -// .cc, .cpp, .cxx, .hh, .hpp, .hxx -// C++ source files. Only useful with cgo or SWIG, and always -// compiled with the OS-native compiler. -// .m -// Objective-C source files. Only useful with cgo, and always -// compiled with the OS-native compiler. -// .s, .S, .sx -// Assembler source files. -// If the package uses cgo or SWIG, these will be assembled with the -// OS-native assembler (typically gcc (sic)); otherwise they -// will be assembled with the Go assembler. -// .swig, .swigcxx -// SWIG definition files. -// .syso -// System object files. +// .go +// Go source files. +// .c, .h +// C source files. +// If the package uses cgo or SWIG, these will be compiled with the +// OS-native compiler (typically gcc); otherwise they will +// trigger an error. +// .cc, .cpp, .cxx, .hh, .hpp, .hxx +// C++ source files. Only useful with cgo or SWIG, and always +// compiled with the OS-native compiler. +// .m +// Objective-C source files. Only useful with cgo, and always +// compiled with the OS-native compiler. +// .s, .S, .sx +// Assembler source files. +// If the package uses cgo or SWIG, these will be assembled with the +// OS-native assembler (typically gcc (sic)); otherwise they +// will be assembled with the Go assembler. +// .swig, .swigcxx +// SWIG definition files. +// .syso +// System object files. // // Files of each of these types except .syso may contain build // constraints, but the go command stops scanning for build constraints @@ -2246,8 +2223,7 @@ // line comment. See the go/build package documentation for // more details. // -// -// The go.mod file +// # The go.mod file // // A module version is defined by a tree of source files, with a go.mod // file in its root. When the go command is run, it looks in the current @@ -2272,8 +2248,7 @@ // use 'go mod edit'. See 'go help mod edit' or // https://golang.org/ref/mod#go-mod-edit. // -// -// GOPATH environment variable +// # GOPATH environment variable // // The Go path is used to resolve import statements. // It is implemented by and documented in the go/build package. @@ -2317,21 +2292,21 @@ // // Here's an example directory layout: // -// GOPATH=/home/user/go +// GOPATH=/home/user/go // -// /home/user/go/ -// src/ -// foo/ -// bar/ (go code in package bar) -// x.go -// quux/ (go code in package main) -// y.go -// bin/ -// quux (installed command) -// pkg/ -// linux_amd64/ -// foo/ -// bar.a (installed package object) +// /home/user/go/ +// src/ +// foo/ +// bar/ (go code in package bar) +// x.go +// quux/ (go code in package main) +// y.go +// bin/ +// quux (installed command) +// pkg/ +// linux_amd64/ +// foo/ +// bar.a (installed package object) // // Go searches each directory listed in GOPATH to find source code, // but new packages are always downloaded into the first directory @@ -2339,33 +2314,32 @@ // // See https://golang.org/doc/code.html for an example. // -// GOPATH and Modules +// # GOPATH and Modules // // When using modules, GOPATH is no longer used for resolving imports. // However, it is still used to store downloaded source code (in GOPATH/pkg/mod) // and compiled commands (in GOPATH/bin). // -// Internal Directories +// # Internal Directories // // Code in or below a directory named "internal" is importable only // by code in the directory tree rooted at the parent of "internal". // Here's an extended version of the directory layout above: // -// /home/user/go/ -// src/ -// crash/ -// bang/ (go code in package bang) -// b.go -// foo/ (go code in package foo) -// f.go -// bar/ (go code in package bar) -// x.go -// internal/ -// baz/ (go code in package baz) -// z.go -// quux/ (go code in package main) -// y.go -// +// /home/user/go/ +// src/ +// crash/ +// bang/ (go code in package bang) +// b.go +// foo/ (go code in package foo) +// f.go +// bar/ (go code in package bar) +// x.go +// internal/ +// baz/ (go code in package baz) +// z.go +// quux/ (go code in package main) +// y.go // // The code in z.go is imported as "foo/internal/baz", but that // import statement can only appear in source files in the subtree @@ -2375,7 +2349,7 @@ // // See https://golang.org/s/go14internal for details. // -// Vendor Directories +// # Vendor Directories // // Go 1.6 includes support for using local copies of external dependencies // to satisfy imports of those dependencies, often referred to as vendoring. @@ -2389,23 +2363,23 @@ // but with the "internal" directory renamed to "vendor" // and a new foo/vendor/crash/bang directory added: // -// /home/user/go/ -// src/ -// crash/ -// bang/ (go code in package bang) -// b.go -// foo/ (go code in package foo) -// f.go -// bar/ (go code in package bar) -// x.go -// vendor/ -// crash/ -// bang/ (go code in package bang) -// b.go -// baz/ (go code in package baz) -// z.go -// quux/ (go code in package main) -// y.go +// /home/user/go/ +// src/ +// crash/ +// bang/ (go code in package bang) +// b.go +// foo/ (go code in package foo) +// f.go +// bar/ (go code in package bar) +// x.go +// vendor/ +// crash/ +// bang/ (go code in package bang) +// b.go +// baz/ (go code in package baz) +// z.go +// quux/ (go code in package main) +// y.go // // The same visibility rules apply as for internal, but the code // in z.go is imported as "baz", not as "foo/vendor/baz". @@ -2427,8 +2401,7 @@ // // See https://golang.org/s/go15vendor for details. // -// -// Legacy GOPATH go get +// # Legacy GOPATH go get // // The 'go get' command changes behavior depending on whether the // go command is running in module-aware mode or legacy GOPATH mode. @@ -2490,8 +2463,7 @@ // // See also: go build, go install, go clean. // -// -// Module proxy protocol +// # Module proxy protocol // // A Go module proxy is any web server that can respond to GET requests for // URLs of a specified form. The requests have no query parameters, so even @@ -2501,15 +2473,14 @@ // For details on the GOPROXY protocol, see // https://golang.org/ref/mod#goproxy-protocol. // -// -// Import path syntax +// # Import path syntax // // An import path (see 'go help packages') denotes a package stored in the local // file system. In general, an import path denotes either a standard package (such // as "unicode/utf8") or a package found in one of the work spaces (For more // details see: 'go help gopath'). // -// Relative import paths +// # Relative import paths // // An import path beginning with ./ or ../ is called a relative path. // The toolchain supports relative import paths as a shortcut in two ways. @@ -2533,7 +2504,7 @@ // To avoid ambiguity, Go programs cannot use relative import paths // within a work space. // -// Remote import paths +// # Remote import paths // // Certain import paths also // describe how to obtain the source code for the package using @@ -2541,29 +2512,29 @@ // // A few common code hosting sites have special syntax: // -// Bitbucket (Git, Mercurial) +// Bitbucket (Git, Mercurial) // -// import "bitbucket.org/user/project" -// import "bitbucket.org/user/project/sub/directory" +// import "bitbucket.org/user/project" +// import "bitbucket.org/user/project/sub/directory" // -// GitHub (Git) +// GitHub (Git) // -// import "github.com/user/project" -// import "github.com/user/project/sub/directory" +// import "github.com/user/project" +// import "github.com/user/project/sub/directory" // -// Launchpad (Bazaar) +// Launchpad (Bazaar) // -// import "launchpad.net/project" -// import "launchpad.net/project/series" -// import "launchpad.net/project/series/sub/directory" +// import "launchpad.net/project" +// import "launchpad.net/project/series" +// import "launchpad.net/project/series/sub/directory" // -// import "launchpad.net/~user/project/branch" -// import "launchpad.net/~user/project/branch/sub/directory" +// import "launchpad.net/~user/project/branch" +// import "launchpad.net/~user/project/branch/sub/directory" // -// IBM DevOps Services (Git) +// IBM DevOps Services (Git) // -// import "hub.jazz.net/git/user/project" -// import "hub.jazz.net/git/user/project/sub/directory" +// import "hub.jazz.net/git/user/project" +// import "hub.jazz.net/git/user/project/sub/directory" // // For code hosted on other servers, import paths may either be qualified // with the version control type, or the go tool can dynamically fetch @@ -2572,26 +2543,26 @@ // // To declare the code location, an import path of the form // -// repository.vcs/path +// repository.vcs/path // // specifies the given repository, with or without the .vcs suffix, // using the named version control system, and then the path inside // that repository. The supported version control systems are: // -// Bazaar .bzr -// Fossil .fossil -// Git .git -// Mercurial .hg -// Subversion .svn +// Bazaar .bzr +// Fossil .fossil +// Git .git +// Mercurial .hg +// Subversion .svn // // For example, // -// import "example.org/user/foo.hg" +// import "example.org/user/foo.hg" // // denotes the root directory of the Mercurial repository at // example.org/user/foo or foo.hg, and // -// import "example.org/repo.git/foo/bar" +// import "example.org/repo.git/foo/bar" // // denotes the foo/bar directory of the Git repository at // example.org/repo or repo.git. @@ -2612,7 +2583,7 @@ // // The meta tag has the form: // -// +// // // The import-prefix is the import path corresponding to the repository // root. It must be a prefix or an exact match of the package being @@ -2630,16 +2601,16 @@ // // For example, // -// import "example.org/pkg/foo" +// import "example.org/pkg/foo" // // will result in the following requests: // -// https://example.org/pkg/foo?go-get=1 (preferred) -// http://example.org/pkg/foo?go-get=1 (fallback, only with use of correctly set GOINSECURE) +// https://example.org/pkg/foo?go-get=1 (preferred) +// http://example.org/pkg/foo?go-get=1 (fallback, only with use of correctly set GOINSECURE) // // If that page contains the meta tag // -// +// // // the go tool will verify that https://example.org/?go-get=1 contains the // same meta tag and then git clone https://code.org/r/p/exproj into @@ -2656,14 +2627,14 @@ // recognized and is preferred over those listing version control systems. // That variant uses "mod" as the vcs in the content value, as in: // -// +// // // This tag means to fetch modules with paths beginning with example.org // from the module proxy available at the URL https://code.org/moduleproxy. // See https://golang.org/ref/mod#goproxy-protocol for details about the // proxy protocol. // -// Import path checking +// # Import path checking // // When the custom import path feature described above redirects to a // known code hosting site, each of the resulting packages has two possible @@ -2672,8 +2643,8 @@ // A package statement is said to have an "import comment" if it is immediately // followed (before the next newline) by a comment of one of these two forms: // -// package math // import "path" -// package math /* import "path" */ +// package math // import "path" +// package math /* import "path" */ // // The go command will refuse to install a package with an import comment // unless it is being referred to by that import path. In this way, import comments @@ -2689,8 +2660,7 @@ // // See https://golang.org/s/go14customimport for details. // -// -// Modules, module versions, and more +// # Modules, module versions, and more // // Modules are how Go manages dependencies. // @@ -2714,8 +2684,7 @@ // GOPRIVATE, and other environment variables. See 'go help environment' // and https://golang.org/ref/mod#private-module-privacy for more information. // -// -// Module authentication using go.sum +// # Module authentication using go.sum // // When the go command downloads a module zip file or go.mod file into the // module cache, it computes a cryptographic hash and compares it with a known @@ -2726,12 +2695,11 @@ // // For details, see https://golang.org/ref/mod#authenticating. // -// -// Package lists and patterns +// # Package lists and patterns // // Many commands apply to a set of packages: // -// go action [packages] +// go action [packages] // // Usually, [packages] is a list of import paths. // @@ -2810,8 +2778,7 @@ // Directory and file names that begin with "." or "_" are ignored // by the go tool, as are directories named "testdata". // -// -// Configuration for downloading non-public code +// # Configuration for downloading non-public code // // The go command defaults to downloading modules from the public Go module // mirror at proxy.golang.org. It also defaults to validating downloaded modules, @@ -2824,7 +2791,7 @@ // glob patterns (in the syntax of Go's path.Match) of module path prefixes. // For example, // -// GOPRIVATE=*.corp.example.com,rsc.io/private +// GOPRIVATE=*.corp.example.com,rsc.io/private // // causes the go command to treat as private any module with a path prefix // matching either pattern, including git.corp.example.com/xyzzy, rsc.io/private, @@ -2838,9 +2805,9 @@ // For example, if a company ran a module proxy serving private modules, // users would configure go using: // -// GOPRIVATE=*.corp.example.com -// GOPROXY=proxy.example.com -// GONOPROXY=none +// GOPRIVATE=*.corp.example.com +// GOPROXY=proxy.example.com +// GONOPROXY=none // // The GOPRIVATE variable is also used to define the "public" and "private" // patterns for the GOVCS variable; see 'go help vcs'. For that usage, @@ -2852,8 +2819,7 @@ // // For more details, see https://golang.org/ref/mod#private-modules. // -// -// Testing flags +// # Testing flags // // The 'go test' command takes both flags that apply to 'go test' itself // and flags that apply to the resulting test binary. @@ -2866,204 +2832,204 @@ // The following flags are recognized by the 'go test' command and // control the execution of any test: // -// -bench regexp -// Run only those benchmarks matching a regular expression. -// By default, no benchmarks are run. -// To run all benchmarks, use '-bench .' or '-bench=.'. -// The regular expression is split by unbracketed slash (/) -// characters into a sequence of regular expressions, and each -// part of a benchmark's identifier must match the corresponding -// element in the sequence, if any. Possible parents of matches -// are run with b.N=1 to identify sub-benchmarks. For example, -// given -bench=X/Y, top-level benchmarks matching X are run -// with b.N=1 to find any sub-benchmarks matching Y, which are -// then run in full. +// -bench regexp +// Run only those benchmarks matching a regular expression. +// By default, no benchmarks are run. +// To run all benchmarks, use '-bench .' or '-bench=.'. +// The regular expression is split by unbracketed slash (/) +// characters into a sequence of regular expressions, and each +// part of a benchmark's identifier must match the corresponding +// element in the sequence, if any. Possible parents of matches +// are run with b.N=1 to identify sub-benchmarks. For example, +// given -bench=X/Y, top-level benchmarks matching X are run +// with b.N=1 to find any sub-benchmarks matching Y, which are +// then run in full. // -// -benchtime t -// Run enough iterations of each benchmark to take t, specified -// as a time.Duration (for example, -benchtime 1h30s). -// The default is 1 second (1s). -// The special syntax Nx means to run the benchmark N times -// (for example, -benchtime 100x). +// -benchtime t +// Run enough iterations of each benchmark to take t, specified +// as a time.Duration (for example, -benchtime 1h30s). +// The default is 1 second (1s). +// The special syntax Nx means to run the benchmark N times +// (for example, -benchtime 100x). // -// -count n -// Run each test, benchmark, and fuzz seed n times (default 1). -// If -cpu is set, run n times for each GOMAXPROCS value. -// Examples are always run once. -count does not apply to -// fuzz tests matched by -fuzz. +// -count n +// Run each test, benchmark, and fuzz seed n times (default 1). +// If -cpu is set, run n times for each GOMAXPROCS value. +// Examples are always run once. -count does not apply to +// fuzz tests matched by -fuzz. // -// -cover -// Enable coverage analysis. -// Note that because coverage works by annotating the source -// code before compilation, compilation and test failures with -// coverage enabled may report line numbers that don't correspond -// to the original sources. +// -cover +// Enable coverage analysis. +// Note that because coverage works by annotating the source +// code before compilation, compilation and test failures with +// coverage enabled may report line numbers that don't correspond +// to the original sources. // -// -covermode set,count,atomic -// Set the mode for coverage analysis for the package[s] -// being tested. The default is "set" unless -race is enabled, -// in which case it is "atomic". -// The values: -// set: bool: does this statement run? -// count: int: how many times does this statement run? -// atomic: int: count, but correct in multithreaded tests; -// significantly more expensive. -// Sets -cover. +// -covermode set,count,atomic +// Set the mode for coverage analysis for the package[s] +// being tested. The default is "set" unless -race is enabled, +// in which case it is "atomic". +// The values: +// set: bool: does this statement run? +// count: int: how many times does this statement run? +// atomic: int: count, but correct in multithreaded tests; +// significantly more expensive. +// Sets -cover. // -// -coverpkg pattern1,pattern2,pattern3 -// Apply coverage analysis in each test to packages matching the patterns. -// The default is for each test to analyze only the package being tested. -// See 'go help packages' for a description of package patterns. -// Sets -cover. +// -coverpkg pattern1,pattern2,pattern3 +// Apply coverage analysis in each test to packages matching the patterns. +// The default is for each test to analyze only the package being tested. +// See 'go help packages' for a description of package patterns. +// Sets -cover. // -// -cpu 1,2,4 -// Specify a list of GOMAXPROCS values for which the tests, benchmarks or -// fuzz tests should be executed. The default is the current value -// of GOMAXPROCS. -cpu does not apply to fuzz tests matched by -fuzz. +// -cpu 1,2,4 +// Specify a list of GOMAXPROCS values for which the tests, benchmarks or +// fuzz tests should be executed. The default is the current value +// of GOMAXPROCS. -cpu does not apply to fuzz tests matched by -fuzz. // -// -failfast -// Do not start new tests after the first test failure. +// -failfast +// Do not start new tests after the first test failure. // -// -fuzz regexp -// Run the fuzz test matching the regular expression. When specified, -// the command line argument must match exactly one package within the -// main module, and regexp must match exactly one fuzz test within -// that package. Fuzzing will occur after tests, benchmarks, seed corpora -// of other fuzz tests, and examples have completed. See the Fuzzing -// section of the testing package documentation for details. +// -fuzz regexp +// Run the fuzz test matching the regular expression. When specified, +// the command line argument must match exactly one package within the +// main module, and regexp must match exactly one fuzz test within +// that package. Fuzzing will occur after tests, benchmarks, seed corpora +// of other fuzz tests, and examples have completed. See the Fuzzing +// section of the testing package documentation for details. // -// -fuzztime t -// Run enough iterations of the fuzz target during fuzzing to take t, -// specified as a time.Duration (for example, -fuzztime 1h30s). -// The default is to run forever. -// The special syntax Nx means to run the fuzz target N times -// (for example, -fuzztime 1000x). +// -fuzztime t +// Run enough iterations of the fuzz target during fuzzing to take t, +// specified as a time.Duration (for example, -fuzztime 1h30s). +// The default is to run forever. +// The special syntax Nx means to run the fuzz target N times +// (for example, -fuzztime 1000x). // -// -fuzzminimizetime t -// Run enough iterations of the fuzz target during each minimization -// attempt to take t, as specified as a time.Duration (for example, -// -fuzzminimizetime 30s). -// The default is 60s. -// The special syntax Nx means to run the fuzz target N times -// (for example, -fuzzminimizetime 100x). +// -fuzzminimizetime t +// Run enough iterations of the fuzz target during each minimization +// attempt to take t, as specified as a time.Duration (for example, +// -fuzzminimizetime 30s). +// The default is 60s. +// The special syntax Nx means to run the fuzz target N times +// (for example, -fuzzminimizetime 100x). // -// -json -// Log verbose output and test results in JSON. This presents the -// same information as the -v flag in a machine-readable format. +// -json +// Log verbose output and test results in JSON. This presents the +// same information as the -v flag in a machine-readable format. // -// -list regexp -// List tests, benchmarks, fuzz tests, or examples matching the regular -// expression. No tests, benchmarks, fuzz tests, or examples will be run. -// This will only list top-level tests. No subtest or subbenchmarks will be -// shown. +// -list regexp +// List tests, benchmarks, fuzz tests, or examples matching the regular +// expression. No tests, benchmarks, fuzz tests, or examples will be run. +// This will only list top-level tests. No subtest or subbenchmarks will be +// shown. // -// -parallel n -// Allow parallel execution of test functions that call t.Parallel, and -// fuzz targets that call t.Parallel when running the seed corpus. -// The value of this flag is the maximum number of tests to run -// simultaneously. -// While fuzzing, the value of this flag is the maximum number of -// subprocesses that may call the fuzz function simultaneously, regardless of -// whether T.Parallel is called. -// By default, -parallel is set to the value of GOMAXPROCS. -// Setting -parallel to values higher than GOMAXPROCS may cause degraded -// performance due to CPU contention, especially when fuzzing. -// Note that -parallel only applies within a single test binary. -// The 'go test' command may run tests for different packages -// in parallel as well, according to the setting of the -p flag -// (see 'go help build'). +// -parallel n +// Allow parallel execution of test functions that call t.Parallel, and +// fuzz targets that call t.Parallel when running the seed corpus. +// The value of this flag is the maximum number of tests to run +// simultaneously. +// While fuzzing, the value of this flag is the maximum number of +// subprocesses that may call the fuzz function simultaneously, regardless of +// whether T.Parallel is called. +// By default, -parallel is set to the value of GOMAXPROCS. +// Setting -parallel to values higher than GOMAXPROCS may cause degraded +// performance due to CPU contention, especially when fuzzing. +// Note that -parallel only applies within a single test binary. +// The 'go test' command may run tests for different packages +// in parallel as well, according to the setting of the -p flag +// (see 'go help build'). // -// -run regexp -// Run only those tests, examples, and fuzz tests matching the regular -// expression. For tests, the regular expression is split by unbracketed -// slash (/) characters into a sequence of regular expressions, and each -// part of a test's identifier must match the corresponding element in -// the sequence, if any. Note that possible parents of matches are -// run too, so that -run=X/Y matches and runs and reports the result -// of all tests matching X, even those without sub-tests matching Y, -// because it must run them to look for those sub-tests. +// -run regexp +// Run only those tests, examples, and fuzz tests matching the regular +// expression. For tests, the regular expression is split by unbracketed +// slash (/) characters into a sequence of regular expressions, and each +// part of a test's identifier must match the corresponding element in +// the sequence, if any. Note that possible parents of matches are +// run too, so that -run=X/Y matches and runs and reports the result +// of all tests matching X, even those without sub-tests matching Y, +// because it must run them to look for those sub-tests. // -// -short -// Tell long-running tests to shorten their run time. -// It is off by default but set during all.bash so that installing -// the Go tree can run a sanity check but not spend time running -// exhaustive tests. +// -short +// Tell long-running tests to shorten their run time. +// It is off by default but set during all.bash so that installing +// the Go tree can run a sanity check but not spend time running +// exhaustive tests. // -// -shuffle off,on,N -// Randomize the execution order of tests and benchmarks. -// It is off by default. If -shuffle is set to on, then it will seed -// the randomizer using the system clock. If -shuffle is set to an -// integer N, then N will be used as the seed value. In both cases, -// the seed will be reported for reproducibility. +// -shuffle off,on,N +// Randomize the execution order of tests and benchmarks. +// It is off by default. If -shuffle is set to on, then it will seed +// the randomizer using the system clock. If -shuffle is set to an +// integer N, then N will be used as the seed value. In both cases, +// the seed will be reported for reproducibility. // -// -timeout d -// If a test binary runs longer than duration d, panic. -// If d is 0, the timeout is disabled. -// The default is 10 minutes (10m). +// -timeout d +// If a test binary runs longer than duration d, panic. +// If d is 0, the timeout is disabled. +// The default is 10 minutes (10m). // -// -v -// Verbose output: log all tests as they are run. Also print all -// text from Log and Logf calls even if the test succeeds. +// -v +// Verbose output: log all tests as they are run. Also print all +// text from Log and Logf calls even if the test succeeds. // -// -vet list -// Configure the invocation of "go vet" during "go test" -// to use the comma-separated list of vet checks. -// If list is empty, "go test" runs "go vet" with a curated list of -// checks believed to be always worth addressing. -// If list is "off", "go test" does not run "go vet" at all. +// -vet list +// Configure the invocation of "go vet" during "go test" +// to use the comma-separated list of vet checks. +// If list is empty, "go test" runs "go vet" with a curated list of +// checks believed to be always worth addressing. +// If list is "off", "go test" does not run "go vet" at all. // // The following flags are also recognized by 'go test' and can be used to // profile the tests during execution: // -// -benchmem -// Print memory allocation statistics for benchmarks. +// -benchmem +// Print memory allocation statistics for benchmarks. // -// -blockprofile block.out -// Write a goroutine blocking profile to the specified file -// when all tests are complete. -// Writes test binary as -c would. +// -blockprofile block.out +// Write a goroutine blocking profile to the specified file +// when all tests are complete. +// Writes test binary as -c would. // -// -blockprofilerate n -// Control the detail provided in goroutine blocking profiles by -// calling runtime.SetBlockProfileRate with n. -// See 'go doc runtime.SetBlockProfileRate'. -// The profiler aims to sample, on average, one blocking event every -// n nanoseconds the program spends blocked. By default, -// if -test.blockprofile is set without this flag, all blocking events -// are recorded, equivalent to -test.blockprofilerate=1. +// -blockprofilerate n +// Control the detail provided in goroutine blocking profiles by +// calling runtime.SetBlockProfileRate with n. +// See 'go doc runtime.SetBlockProfileRate'. +// The profiler aims to sample, on average, one blocking event every +// n nanoseconds the program spends blocked. By default, +// if -test.blockprofile is set without this flag, all blocking events +// are recorded, equivalent to -test.blockprofilerate=1. // -// -coverprofile cover.out -// Write a coverage profile to the file after all tests have passed. -// Sets -cover. +// -coverprofile cover.out +// Write a coverage profile to the file after all tests have passed. +// Sets -cover. // -// -cpuprofile cpu.out -// Write a CPU profile to the specified file before exiting. -// Writes test binary as -c would. +// -cpuprofile cpu.out +// Write a CPU profile to the specified file before exiting. +// Writes test binary as -c would. // -// -memprofile mem.out -// Write an allocation profile to the file after all tests have passed. -// Writes test binary as -c would. +// -memprofile mem.out +// Write an allocation profile to the file after all tests have passed. +// Writes test binary as -c would. // -// -memprofilerate n -// Enable more precise (and expensive) memory allocation profiles by -// setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'. -// To profile all memory allocations, use -test.memprofilerate=1. +// -memprofilerate n +// Enable more precise (and expensive) memory allocation profiles by +// setting runtime.MemProfileRate. See 'go doc runtime.MemProfileRate'. +// To profile all memory allocations, use -test.memprofilerate=1. // -// -mutexprofile mutex.out -// Write a mutex contention profile to the specified file -// when all tests are complete. -// Writes test binary as -c would. +// -mutexprofile mutex.out +// Write a mutex contention profile to the specified file +// when all tests are complete. +// Writes test binary as -c would. // -// -mutexprofilefraction n -// Sample 1 in n stack traces of goroutines holding a -// contended mutex. +// -mutexprofilefraction n +// Sample 1 in n stack traces of goroutines holding a +// contended mutex. // -// -outputdir directory -// Place output files from profiling in the specified directory, -// by default the directory in which "go test" is running. +// -outputdir directory +// Place output files from profiling in the specified directory, +// by default the directory in which "go test" is running. // -// -trace trace.out -// Write an execution trace to the specified file before exiting. +// -trace trace.out +// Write an execution trace to the specified file before exiting. // // Each of these flags is also recognized with an optional 'test.' prefix, // as in -test.v. When invoking the generated test binary (the result of @@ -3075,11 +3041,11 @@ // // For instance, the command // -// go test -v -myflag testdata -cpuprofile=prof.out -x +// go test -v -myflag testdata -cpuprofile=prof.out -x // // will compile the test binary and then run it as // -// pkg.test -test.v -myflag testdata -test.cpuprofile=prof.out +// pkg.test -test.v -myflag testdata -test.cpuprofile=prof.out // // (The -x flag is removed because it applies only to the go command's // execution, not to the test itself.) @@ -3114,27 +3080,26 @@ // // For instance, the command // -// go test -v -args -x -v +// go test -v -args -x -v // // will compile the test binary and then run it as // -// pkg.test -test.v -x -v +// pkg.test -test.v -x -v // // Similarly, // -// go test -args math +// go test -args math // // will compile the test binary and then run it as // -// pkg.test math +// pkg.test math // // In the first example, the -x and the second -v are passed through to the // test binary unchanged and with no effect on the go command itself. // In the second example, the argument math is passed through to the test // binary, instead of being interpreted as the package list. // -// -// Testing functions +// # Testing functions // // The 'go test' command expects to find test, benchmark, and example functions // in the "*_test.go" files corresponding to the package under test. @@ -3142,15 +3107,15 @@ // A test function is one named TestXxx (where Xxx does not start with a // lower case letter) and should have the signature, // -// func TestXxx(t *testing.T) { ... } +// func TestXxx(t *testing.T) { ... } // // A benchmark function is one named BenchmarkXxx and should have the signature, // -// func BenchmarkXxx(b *testing.B) { ... } +// func BenchmarkXxx(b *testing.B) { ... } // // A fuzz test is one named FuzzXxx and should have the signature, // -// func FuzzXxx(f *testing.F) { ... } +// func FuzzXxx(f *testing.F) { ... } // // An example function is similar to a test function but, instead of using // *testing.T to report success or failure, prints output to os.Stdout. @@ -3169,25 +3134,25 @@ // // Here is an example of an example: // -// func ExamplePrintln() { -// Println("The output of\nthis example.") -// // Output: The output of -// // this example. -// } +// func ExamplePrintln() { +// Println("The output of\nthis example.") +// // Output: The output of +// // this example. +// } // // Here is another example where the ordering of the output is ignored: // -// func ExamplePerm() { -// for _, value := range Perm(4) { -// fmt.Println(value) -// } +// func ExamplePerm() { +// for _, value := range Perm(4) { +// fmt.Println(value) +// } // -// // Unordered output: 4 -// // 2 -// // 1 -// // 3 -// // 0 -// } +// // Unordered output: 4 +// // 2 +// // 1 +// // 3 +// // 0 +// } // // The entire test file is presented as the example when it contains a single // example function, at least one other function, type, variable, or constant @@ -3195,8 +3160,7 @@ // // See the documentation of the testing package for more information. // -// -// Controlling version control with GOVCS +// # Controlling version control with GOVCS // // The 'go get' command can run version control commands like git // to download imported code. This functionality is critical to the decentralized @@ -3245,7 +3209,7 @@ // // For example, consider: // -// GOVCS=github.com:git,evil.com:off,*:git|hg +// GOVCS=github.com:git,evil.com:off,*:git|hg // // With this setting, code with a module or import path beginning with // github.com/ can only use git; paths on evil.com cannot use any version @@ -3262,14 +3226,12 @@ // // To allow unfettered use of any version control system for any package, use: // -// GOVCS=*:all +// GOVCS=*:all // // To disable all use of version control, use: // -// GOVCS=*:off +// GOVCS=*:off // // The 'go env -w' command (see 'go help env') can be used to set the GOVCS // variable for future go command invocations. -// -// package main diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index b4387dc078..905dd68274 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -78,6 +78,10 @@ func tooSlow(t *testing.T) { // (temp) directory. var testGOROOT string +// testGOROOT_FINAL is the GOROOT_FINAL with which the test binary is assumed to +// have been built. +var testGOROOT_FINAL = os.Getenv("GOROOT_FINAL") + var testGOCACHE string var testGo string diff --git a/src/cmd/go/internal/bug/bug.go b/src/cmd/go/internal/bug/bug.go index 702dc2a14a..b4181b1e44 100644 --- a/src/cmd/go/internal/bug/bug.go +++ b/src/cmd/go/internal/bug/bug.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package bug implements the ``go bug'' command. +// Package bug implements the “go bug” command. package bug import ( diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index a11a1a7655..c6ddfe55d5 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -44,9 +44,9 @@ func exeSuffix() string { // These are general "build flags" used by build and other commands. var ( - BuildA bool // -a flag - BuildBuildmode string // -buildmode flag - BuildBuildvcs bool // -buildvcs flag + BuildA bool // -a flag + BuildBuildmode string // -buildmode flag + BuildBuildvcs = "auto" // -buildvcs flag: "true", "false", or "auto" BuildContext = defaultContext() BuildMod string // -mod flag BuildModExplicit bool // whether -mod was set explicitly diff --git a/src/cmd/go/internal/clean/clean.go b/src/cmd/go/internal/clean/clean.go index dc93cdf598..8564411fb6 100644 --- a/src/cmd/go/internal/clean/clean.go +++ b/src/cmd/go/internal/clean/clean.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package clean implements the ``go clean'' command. +// Package clean implements the “go clean” command. package clean import ( diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go index 7741a9022c..3b6cd94799 100644 --- a/src/cmd/go/internal/doc/doc.go +++ b/src/cmd/go/internal/doc/doc.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package doc implements the ``go doc'' command. +// Package doc implements the “go doc” command. package doc import ( diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index fcabc8d1c7..529351dfbd 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package envcmd implements the ``go env'' command. +// Package envcmd implements the “go env” command. package envcmd import ( @@ -184,15 +184,23 @@ func ExtraEnvVarsCostly() []cfg.EnvVar { } cmd := b.GccCmd(".", "") + join := func(s []string) string { + q, err := quoted.Join(s) + if err != nil { + return strings.Join(s, " ") + } + return q + } + return []cfg.EnvVar{ // Note: Update the switch in runEnv below when adding to this list. - {Name: "CGO_CFLAGS", Value: strings.Join(cflags, " ")}, - {Name: "CGO_CPPFLAGS", Value: strings.Join(cppflags, " ")}, - {Name: "CGO_CXXFLAGS", Value: strings.Join(cxxflags, " ")}, - {Name: "CGO_FFLAGS", Value: strings.Join(fflags, " ")}, - {Name: "CGO_LDFLAGS", Value: strings.Join(ldflags, " ")}, + {Name: "CGO_CFLAGS", Value: join(cflags)}, + {Name: "CGO_CPPFLAGS", Value: join(cppflags)}, + {Name: "CGO_CXXFLAGS", Value: join(cxxflags)}, + {Name: "CGO_FFLAGS", Value: join(fflags)}, + {Name: "CGO_LDFLAGS", Value: join(ldflags)}, {Name: "PKG_CONFIG", Value: b.PkgconfigCmd()}, - {Name: "GOGCCFLAGS", Value: strings.Join(cmd[3:], " ")}, + {Name: "GOGCCFLAGS", Value: join(cmd[3:])}, } } diff --git a/src/cmd/go/internal/fix/fix.go b/src/cmd/go/internal/fix/fix.go index d8ba353de6..3705b30ef9 100644 --- a/src/cmd/go/internal/fix/fix.go +++ b/src/cmd/go/internal/fix/fix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package fix implements the ``go fix'' command. +// Package fix implements the “go fix” command. package fix import ( diff --git a/src/cmd/go/internal/fmtcmd/fmt.go b/src/cmd/go/internal/fmtcmd/fmt.go index 19656eab7f..3dc29d40b2 100644 --- a/src/cmd/go/internal/fmtcmd/fmt.go +++ b/src/cmd/go/internal/fmtcmd/fmt.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package fmtcmd implements the ``go fmt'' command. +// Package fmtcmd implements the “go fmt” command. package fmtcmd import ( diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go index 54ccfe78f2..a46f4f8908 100644 --- a/src/cmd/go/internal/generate/generate.go +++ b/src/cmd/go/internal/generate/generate.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package generate implements the ``go generate'' command. +// Package generate implements the “go generate” command. package generate import ( @@ -84,6 +84,9 @@ Go generate sets several variables when it runs the generator: The line number of the directive in the source file. $GOPACKAGE The name of the package of the file containing the directive. + $GOROOT + The GOROOT directory for the 'go' command that invoked the + generator, containing the Go toolchain and standard library. $DOLLAR A dollar sign. @@ -326,6 +329,7 @@ func isGoGenerate(buf []byte) bool { // single go:generate command. func (g *Generator) setEnv() { g.env = []string{ + "GOROOT=" + cfg.GOROOT, "GOARCH=" + cfg.BuildContext.GOARCH, "GOOS=" + cfg.BuildContext.GOOS, "GOFILE=" + g.file, diff --git a/src/cmd/go/internal/generate/generate_test.go b/src/cmd/go/internal/generate/generate_test.go index b546218a3c..15b1279f36 100644 --- a/src/cmd/go/internal/generate/generate_test.go +++ b/src/cmd/go/internal/generate/generate_test.go @@ -78,11 +78,11 @@ var defEnvMap = map[string]string{ // TestGenerateCommandShortHand - similar to TestGenerateCommandParse, // except: -// 1. if the result starts with -command, record that shorthand -// before moving on to the next test. -// 2. If a source line number is specified, set that in the parser -// before executing the test. i.e., execute the split as if it -// processing that source line. +// 1. if the result starts with -command, record that shorthand +// before moving on to the next test. +// 2. If a source line number is specified, set that in the parser +// before executing the test. i.e., execute the split as if it +// processing that source line. func TestGenerateCommandShorthand(t *testing.T) { g := &Generator{ r: nil, // Unused here. @@ -216,11 +216,11 @@ var splitTestsLines = []splitTestWithLine{ // TestGenerateCommandShortHand - similar to TestGenerateCommandParse, // except: -// 1. if the result starts with -command, record that shorthand -// before moving on to the next test. -// 2. If a source line number is specified, set that in the parser -// before executing the test. i.e., execute the split as if it -// processing that source line. +// 1. if the result starts with -command, record that shorthand +// before moving on to the next test. +// 2. If a source line number is specified, set that in the parser +// before executing the test. i.e., execute the split as if it +// processing that source line. func TestGenerateCommandShortHand2(t *testing.T) { g := &Generator{ r: nil, // Unused here. diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go index 8cf8fe6645..1bb67bcf51 100644 --- a/src/cmd/go/internal/get/get.go +++ b/src/cmd/go/internal/get/get.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package get implements the ``go get'' command. +// Package get implements the “go get” command. package get import ( diff --git a/src/cmd/go/internal/help/help.go b/src/cmd/go/internal/help/help.go index 2a07d2423b..f73097af84 100644 --- a/src/cmd/go/internal/help/help.go +++ b/src/cmd/go/internal/help/help.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package help implements the ``go help'' command. +// Package help implements the “go help” command. package help import ( diff --git a/src/cmd/go/internal/imports/build.go b/src/cmd/go/internal/imports/build.go index 10e90fc216..53fa1967f7 100644 --- a/src/cmd/go/internal/imports/build.go +++ b/src/cmd/go/internal/imports/build.go @@ -215,7 +215,9 @@ func matchTag(name string, tags map[string]bool, prefer bool) bool { } // eval is like +// // x.Eval(func(tag string) bool { return matchTag(tag, tags) }) +// // except that it implements the special case for tags["*"] meaning // all tags are both true and false at the same time. func eval(x constraint.Expr, tags map[string]bool, prefer bool) bool { @@ -236,17 +238,18 @@ func eval(x constraint.Expr, tags map[string]bool, prefer bool) bool { // suffix which does not match the current system. // The recognized name formats are: // -// name_$(GOOS).* -// name_$(GOARCH).* -// name_$(GOOS)_$(GOARCH).* -// name_$(GOOS)_test.* -// name_$(GOARCH)_test.* -// name_$(GOOS)_$(GOARCH)_test.* +// name_$(GOOS).* +// name_$(GOARCH).* +// name_$(GOOS)_$(GOARCH).* +// name_$(GOOS)_test.* +// name_$(GOARCH)_test.* +// name_$(GOOS)_$(GOARCH)_test.* // // Exceptions: -// if GOOS=android, then files with GOOS=linux are also matched. -// if GOOS=illumos, then files with GOOS=solaris are also matched. -// if GOOS=ios, then files with GOOS=darwin are also matched. +// +// if GOOS=android, then files with GOOS=linux are also matched. +// if GOOS=illumos, then files with GOOS=solaris are also matched. +// if GOOS=ios, then files with GOOS=darwin are also matched. // // If tags["*"] is true, then MatchFile will consider all possible // GOOS and GOARCH to be available and will consequently diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go index 5fc33989cd..17864e1da7 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package list implements the ``go list'' command. +// Package list implements the “go list” command. package list import ( @@ -567,7 +567,7 @@ func runList(ctx context.Context, cmd *base.Command, args []string) { pkgOpts := load.PackageOpts{ IgnoreImports: *listFind, ModResolveTests: *listTest, - LoadVCS: cfg.BuildBuildvcs, + LoadVCS: true, } pkgs := load.PackagesAndErrors(ctx, pkgOpts, args) if !*listE { diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index a1cfcad826..10799ad516 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -17,6 +17,7 @@ import ( "internal/goroot" "io/fs" "os" + "os/exec" "path" pathpkg "path" "path/filepath" @@ -196,9 +197,9 @@ func (p *Package) Desc() string { // IsTestOnly reports whether p is a test-only package. // // A “test-only” package is one that: -// - is a test-only variant of an ordinary package, or -// - is a synthesized "main" package for a test binary, or -// - contains only _test.go files. +// - is a test-only variant of an ordinary package, or +// - is a synthesized "main" package for a test binary, or +// - contains only _test.go files. func (p *Package) IsTestOnly() bool { return p.ForTest != "" || p.Internal.TestmainGo != nil || @@ -2062,7 +2063,8 @@ func resolveEmbed(pkgdir string, patterns []string) (files []string, pmap map[st // then there may be other things lying around, like symbolic links or .git directories.) var list []string for _, file := range match { - rel := filepath.ToSlash(file[len(pkgdir)+1:]) // file, relative to p.Dir + // relative path to p.Dir which begins without prefix slash + rel := filepath.ToSlash(str.TrimFilePathPrefix(file, pkgdir)) what := "file" info, err := fsys.Lstat(file) @@ -2112,7 +2114,7 @@ func resolveEmbed(pkgdir string, patterns []string) (files []string, pmap map[st if err != nil { return err } - rel := filepath.ToSlash(path[len(pkgdir)+1:]) + rel := filepath.ToSlash(str.TrimFilePathPrefix(path, pkgdir)) name := info.Name() if path != file && (isBadEmbedName(name) || ((name[0] == '.' || name[0] == '_') && !all)) { // Ignore bad names, assuming they won't go into modules. @@ -2377,7 +2379,7 @@ func (p *Package) setBuildInfo(includeVCS bool) { var vcsCmd *vcs.Cmd var err error const allowNesting = true - if includeVCS && p.Module != nil && p.Module.Version == "" && !p.Standard && !p.IsTestOnly() { + if includeVCS && cfg.BuildBuildvcs != "false" && p.Module != nil && p.Module.Version == "" && !p.Standard && !p.IsTestOnly() { repoDir, vcsCmd, err = vcs.FromDir(base.Cwd(), "", allowNesting) if err != nil && !errors.Is(err, os.ErrNotExist) { setVCSError(err) @@ -2389,7 +2391,14 @@ func (p *Package) setBuildInfo(includeVCS bool) { // repository containing the working directory. Don't include VCS info. // If the repo contains the module or vice versa, but they are not // the same directory, it's likely an error (see below). - repoDir, vcsCmd = "", nil + goto omitVCS + } + if cfg.BuildBuildvcs == "auto" && vcsCmd != nil && vcsCmd.Cmd != "" { + if _, err := exec.LookPath(vcsCmd.Cmd); err != nil { + // We fould a repository, but the required VCS tool is not present. + // "-buildvcs=auto" means that we should silently drop the VCS metadata. + goto omitVCS + } } } if repoDir != "" && vcsCmd.Status != nil { @@ -2403,8 +2412,11 @@ func (p *Package) setBuildInfo(includeVCS bool) { return } if pkgRepoDir != repoDir { - setVCSError(fmt.Errorf("main package is in repository %q but current directory is in repository %q", pkgRepoDir, repoDir)) - return + if cfg.BuildBuildvcs != "auto" { + setVCSError(fmt.Errorf("main package is in repository %q but current directory is in repository %q", pkgRepoDir, repoDir)) + return + } + goto omitVCS } modRepoDir, _, err := vcs.FromDir(p.Module.Dir, "", allowNesting) if err != nil { @@ -2412,8 +2424,11 @@ func (p *Package) setBuildInfo(includeVCS bool) { return } if modRepoDir != repoDir { - setVCSError(fmt.Errorf("main module is in repository %q but current directory is in repository %q", modRepoDir, repoDir)) - return + if cfg.BuildBuildvcs != "auto" { + setVCSError(fmt.Errorf("main module is in repository %q but current directory is in repository %q", modRepoDir, repoDir)) + return + } + goto omitVCS } type vcsStatusError struct { @@ -2440,6 +2455,7 @@ func (p *Package) setBuildInfo(includeVCS bool) { } appendSetting("vcs.modified", strconv.FormatBool(st.Uncommitted)) } +omitVCS: p.Internal.BuildInfo = info.String() } diff --git a/src/cmd/go/internal/load/test.go b/src/cmd/go/internal/load/test.go index 39f1131a43..3780f358f4 100644 --- a/src/cmd/go/internal/load/test.go +++ b/src/cmd/go/internal/load/test.go @@ -76,9 +76,9 @@ func TestPackagesFor(ctx context.Context, opts PackageOpts, p *Package, cover *T } // TestPackagesAndErrors returns three packages: -// - pmain, the package main corresponding to the test binary (running tests in ptest and pxtest). -// - ptest, the package p compiled with added "package p" test files. -// - pxtest, the result of compiling any "package p_test" (external) test files. +// - pmain, the package main corresponding to the test binary (running tests in ptest and pxtest). +// - ptest, the package p compiled with added "package p" test files. +// - pxtest, the result of compiling any "package p_test" (external) test files. // // If the package has no "package p_test" test files, pxtest will be nil. // If the non-test compilation of package p can be reused diff --git a/src/cmd/go/internal/lockedfile/lockedfile_plan9.go b/src/cmd/go/internal/lockedfile/lockedfile_plan9.go index 35669388e0..a2ce794b96 100644 --- a/src/cmd/go/internal/lockedfile/lockedfile_plan9.go +++ b/src/cmd/go/internal/lockedfile/lockedfile_plan9.go @@ -17,9 +17,9 @@ import ( // Opening an exclusive-use file returns an error. // The expected error strings are: // -// - "open/create -- file is locked" (cwfs, kfs) -// - "exclusive lock" (fossil) -// - "exclusive use file already open" (ramfs) +// - "open/create -- file is locked" (cwfs, kfs) +// - "exclusive lock" (fossil) +// - "exclusive use file already open" (ramfs) var lockedErrStrings = [...]string{ "file is locked", "exclusive lock", diff --git a/src/cmd/go/internal/lockedfile/lockedfile_test.go b/src/cmd/go/internal/lockedfile/lockedfile_test.go index c9907db46c..79352bc8c7 100644 --- a/src/cmd/go/internal/lockedfile/lockedfile_test.go +++ b/src/cmd/go/internal/lockedfile/lockedfile_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // js does not support inter-process file locking. +// //go:build !js package lockedfile_test diff --git a/src/cmd/go/internal/lockedfile/transform_test.go b/src/cmd/go/internal/lockedfile/transform_test.go index 3c1caa334e..833cbf7879 100644 --- a/src/cmd/go/internal/lockedfile/transform_test.go +++ b/src/cmd/go/internal/lockedfile/transform_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // js does not support inter-process file locking. +// //go:build !js package lockedfile_test diff --git a/src/cmd/go/internal/modcmd/mod.go b/src/cmd/go/internal/modcmd/mod.go index d72d0cacd6..125ba336a0 100644 --- a/src/cmd/go/internal/modcmd/mod.go +++ b/src/cmd/go/internal/modcmd/mod.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package modcmd implements the ``go mod'' command. +// Package modcmd implements the “go mod” command. package modcmd import ( diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go index 3d8463e892..08a474f61b 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package modget implements the module-aware ``go get'' command. +// Package modget implements the module-aware “go get” command. package modget // The arguments to 'go get' are patterns with optional version queries, with @@ -731,10 +731,10 @@ func (r *resolver) performWildcardQueries(ctx context.Context) { } // queryWildcard adds a candidate set to q for each module for which: -// - some version of the module is already in the build list, and -// - that module exists at some version matching q.version, and -// - either the module path itself matches q.pattern, or some package within -// the module at q.version matches q.pattern. +// - some version of the module is already in the build list, and +// - that module exists at some version matching q.version, and +// - either the module path itself matches q.pattern, or some package within +// the module at q.version matches q.pattern. func (r *resolver) queryWildcard(ctx context.Context, q *query) { // For wildcard patterns, modload.QueryPattern only identifies modules // matching the prefix of the path before the wildcard. However, the build diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go index 6f9072c8c4..5b8d6051f3 100644 --- a/src/cmd/go/internal/modload/buildlist.go +++ b/src/cmd/go/internal/modload/buildlist.go @@ -676,11 +676,11 @@ func updateWorkspaceRoots(ctx context.Context, rs *Requirements, add []module.Ve // invariants of the go.mod file needed to support graph pruning for the given // packages: // -// 1. For each package marked with pkgInAll, the module path that provided that -// package is included as a root. -// 2. For all packages, the module that provided that package either remains -// selected at the same version or is upgraded by the dependencies of a -// root. +// 1. For each package marked with pkgInAll, the module path that provided that +// package is included as a root. +// 2. For all packages, the module that provided that package either remains +// selected at the same version or is upgraded by the dependencies of a +// root. // // If any module that provided a package has been upgraded above its previous // version, the caller may need to reload and recompute the package graph. @@ -769,17 +769,17 @@ func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[ // updatePrunedRoots returns a set of root requirements that maintains the // invariants of the go.mod file needed to support graph pruning: // -// 1. The selected version of the module providing each package marked with -// either pkgInAll or pkgIsRoot is included as a root. -// Note that certain root patterns (such as '...') may explode the root set -// to contain every module that provides any package imported (or merely -// required) by any other module. -// 2. Each root appears only once, at the selected version of its path -// (if rs.graph is non-nil) or at the highest version otherwise present as a -// root (otherwise). -// 3. Every module path that appears as a root in rs remains a root. -// 4. Every version in add is selected at its given version unless upgraded by -// (the dependencies of) an existing root or another module in add. +// 1. The selected version of the module providing each package marked with +// either pkgInAll or pkgIsRoot is included as a root. +// Note that certain root patterns (such as '...') may explode the root set +// to contain every module that provides any package imported (or merely +// required) by any other module. +// 2. Each root appears only once, at the selected version of its path +// (if rs.graph is non-nil) or at the highest version otherwise present as a +// root (otherwise). +// 3. Every module path that appears as a root in rs remains a root. +// 4. Every version in add is selected at its given version unless upgraded by +// (the dependencies of) an existing root or another module in add. // // The packages in pkgs are assumed to have been loaded from either the roots of // rs or the modules selected in the graph of rs. @@ -787,26 +787,26 @@ func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[ // The above invariants together imply the graph-pruning invariants for the // go.mod file: // -// 1. (The import invariant.) Every module that provides a package transitively -// imported by any package or test in the main module is included as a root. -// This follows by induction from (1) and (3) above. Transitively-imported -// packages loaded during this invocation are marked with pkgInAll (1), -// and by hypothesis any transitively-imported packages loaded in previous -// invocations were already roots in rs (3). +// 1. (The import invariant.) Every module that provides a package transitively +// imported by any package or test in the main module is included as a root. +// This follows by induction from (1) and (3) above. Transitively-imported +// packages loaded during this invocation are marked with pkgInAll (1), +// and by hypothesis any transitively-imported packages loaded in previous +// invocations were already roots in rs (3). // -// 2. (The argument invariant.) Every module that provides a package matching -// an explicit package pattern is included as a root. This follows directly -// from (1): packages matching explicit package patterns are marked with -// pkgIsRoot. +// 2. (The argument invariant.) Every module that provides a package matching +// an explicit package pattern is included as a root. This follows directly +// from (1): packages matching explicit package patterns are marked with +// pkgIsRoot. // -// 3. (The completeness invariant.) Every module that contributed any package -// to the build is required by either the main module or one of the modules -// it requires explicitly. This invariant is left up to the caller, who must -// not load packages from outside the module graph but may add roots to the -// graph, but is facilited by (3). If the caller adds roots to the graph in -// order to resolve missing packages, then updatePrunedRoots will retain them, -// the selected versions of those roots cannot regress, and they will -// eventually be written back to the main module's go.mod file. +// 3. (The completeness invariant.) Every module that contributed any package +// to the build is required by either the main module or one of the modules +// it requires explicitly. This invariant is left up to the caller, who must +// not load packages from outside the module graph but may add roots to the +// graph, but is facilited by (3). If the caller adds roots to the graph in +// order to resolve missing packages, then updatePrunedRoots will retain them, +// the selected versions of those roots cannot regress, and they will +// eventually be written back to the main module's go.mod file. // // (See https://golang.org/design/36460-lazy-module-loading#invariants for more // detail.) @@ -1162,14 +1162,14 @@ func tidyUnprunedRoots(ctx context.Context, mainModule module.Version, direct ma // // The roots are updated such that: // -// 1. The selected version of every module path in direct is included as a root -// (if it is not "none"). -// 2. Each root is the selected version of its path. (We say that such a root -// set is “consistent”.) -// 3. Every version selected in the graph of rs remains selected unless upgraded -// by a dependency in add. -// 4. Every version in add is selected at its given version unless upgraded by -// (the dependencies of) an existing root or another module in add. +// 1. The selected version of every module path in direct is included as a root +// (if it is not "none"). +// 2. Each root is the selected version of its path. (We say that such a root +// set is “consistent”.) +// 3. Every version selected in the graph of rs remains selected unless upgraded +// by a dependency in add. +// 4. Every version in add is selected at its given version unless upgraded by +// (the dependencies of) an existing root or another module in add. func updateUnprunedRoots(ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) { mg, err := rs.Graph(ctx) if err != nil { diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go index 0f37e3b2e9..c556664c35 100644 --- a/src/cmd/go/internal/modload/edit.go +++ b/src/cmd/go/internal/modload/edit.go @@ -16,20 +16,20 @@ import ( // editRequirements returns an edited version of rs such that: // -// 1. Each module version in mustSelect is selected. +// 1. Each module version in mustSelect is selected. // -// 2. Each module version in tryUpgrade is upgraded toward the indicated -// version as far as can be done without violating (1). +// 2. Each module version in tryUpgrade is upgraded toward the indicated +// version as far as can be done without violating (1). // -// 3. Each module version in rs.rootModules (or rs.graph, if rs is unpruned) -// is downgraded from its original version only to the extent needed to -// satisfy (1), or upgraded only to the extent needed to satisfy (1) and -// (2). +// 3. Each module version in rs.rootModules (or rs.graph, if rs is unpruned) +// is downgraded from its original version only to the extent needed to +// satisfy (1), or upgraded only to the extent needed to satisfy (1) and +// (2). // -// 4. No module is upgraded above the maximum version of its path found in the -// dependency graph of rs, the combined dependency graph of the versions in -// mustSelect, or the dependencies of each individual module version in -// tryUpgrade. +// 4. No module is upgraded above the maximum version of its path found in the +// dependency graph of rs, the combined dependency graph of the versions in +// mustSelect, or the dependencies of each individual module version in +// tryUpgrade. // // Generally, the module versions in mustSelect are due to the module or a // package within the module matching an explicit command line argument to 'go diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go index c170699535..e85a33dd50 100644 --- a/src/cmd/go/internal/modload/load.go +++ b/src/cmd/go/internal/modload/load.go @@ -1222,16 +1222,16 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader { // // In particular: // -// - Modules that provide packages directly imported from the main module are -// marked as direct, and are promoted to explicit roots. If a needed root -// cannot be promoted due to -mod=readonly or -mod=vendor, the importing -// package is marked with an error. +// - Modules that provide packages directly imported from the main module are +// marked as direct, and are promoted to explicit roots. If a needed root +// cannot be promoted due to -mod=readonly or -mod=vendor, the importing +// package is marked with an error. // -// - If ld scanned the "all" pattern independent of build constraints, it is -// guaranteed to have seen every direct import. Module dependencies that did -// not provide any directly-imported package are then marked as indirect. +// - If ld scanned the "all" pattern independent of build constraints, it is +// guaranteed to have seen every direct import. Module dependencies that did +// not provide any directly-imported package are then marked as indirect. // -// - Root dependencies are updated to their selected versions. +// - Root dependencies are updated to their selected versions. // // The "changed" return value reports whether the update changed the selected // version of any module that either provided a loaded package or may now diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go index 33808ea109..27af78d99e 100644 --- a/src/cmd/go/internal/modload/query.go +++ b/src/cmd/go/internal/modload/query.go @@ -33,19 +33,27 @@ import ( // The version must take one of the following forms: // // - the literal string "latest", denoting the latest available, allowed -// tagged version, with non-prereleases preferred over prereleases. -// If there are no tagged versions in the repo, latest returns the most -// recent commit. +// +// tagged version, with non-prereleases preferred over prereleases. +// If there are no tagged versions in the repo, latest returns the most +// recent commit. +// // - the literal string "upgrade", equivalent to "latest" except that if -// current is a newer version, current will be returned (see below). +// +// current is a newer version, current will be returned (see below). +// // - the literal string "patch", denoting the latest available tagged version -// with the same major and minor number as current (see below). +// +// with the same major and minor number as current (see below). +// // - v1, denoting the latest available tagged version v1.x.x. // - v1.2, denoting the latest available tagged version v1.2.x. // - v1.2.3, a semantic version string denoting that tagged version. // - v1.2.3, >=v1.2.3, -// denoting the version closest to the target and satisfying the given operator, -// with non-prereleases preferred over prereleases. +// +// denoting the version closest to the target and satisfying the given operator, +// with non-prereleases preferred over prereleases. +// // - a repository commit identifier or tag, denoting that commit. // // current denotes the currently-selected version of the module; it may be @@ -433,9 +441,9 @@ func (qm *queryMatcher) allowsVersion(ctx context.Context, v string) bool { // filterVersions classifies versions into releases and pre-releases, filtering // out: -// 1. versions that do not satisfy the 'allowed' predicate, and -// 2. "+incompatible" versions, if a compatible one satisfies the predicate -// and the incompatible version is not preferred. +// 1. versions that do not satisfy the 'allowed' predicate, and +// 2. "+incompatible" versions, if a compatible one satisfies the predicate +// and the incompatible version is not preferred. // // If the allowed predicate returns an error not equivalent to ErrDisallowed, // filterVersions returns that error. diff --git a/src/cmd/go/internal/modload/stat_openfile.go b/src/cmd/go/internal/modload/stat_openfile.go index ff7c124af5..5773073d90 100644 --- a/src/cmd/go/internal/modload/stat_openfile.go +++ b/src/cmd/go/internal/modload/stat_openfile.go @@ -8,7 +8,7 @@ // are checked by the server and group information is not known to the client, // access must open the file to check permissions.” // -// aix and js,wasm are similar, in that they do not define syscall.Access. +// js,wasm is similar, in that it does not define syscall.Access. package modload diff --git a/src/cmd/go/internal/robustio/robustio.go b/src/cmd/go/internal/robustio/robustio.go index ce3dbbde6d..15b33773cf 100644 --- a/src/cmd/go/internal/robustio/robustio.go +++ b/src/cmd/go/internal/robustio/robustio.go @@ -42,9 +42,9 @@ func RemoveAll(path string) error { // in this package attempt to mitigate. // // Errors considered ephemeral include: -// - syscall.ERROR_ACCESS_DENIED -// - syscall.ERROR_FILE_NOT_FOUND -// - internal/syscall/windows.ERROR_SHARING_VIOLATION +// - syscall.ERROR_ACCESS_DENIED +// - syscall.ERROR_FILE_NOT_FOUND +// - internal/syscall/windows.ERROR_SHARING_VIOLATION // // This set may be expanded in the future; programs must not rely on the // non-ephemerality of any given error. diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go index 35c5783373..ebe1611819 100644 --- a/src/cmd/go/internal/run/run.go +++ b/src/cmd/go/internal/run/run.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package run implements the ``go run'' command. +// Package run implements the “go run” command. package run import ( diff --git a/src/cmd/go/internal/str/path.go b/src/cmd/go/internal/str/path.go index 0c8aaeaca1..a69e171f8c 100644 --- a/src/cmd/go/internal/str/path.go +++ b/src/cmd/go/internal/str/path.go @@ -58,8 +58,11 @@ func TrimFilePathPrefix(s, prefix string) string { if !HasFilePathPrefix(s, prefix) { return s } - if len(s) == len(prefix) { - return "" + trimmed := s[len(prefix):] + if len(trimmed) == 0 || trimmed[0] != filepath.Separator { + // Prefix either is equal to s, or ends with a separator + // (for example, if it is exactly "/"). + return trimmed } - return s[len(prefix)+1:] + return trimmed[1:] } diff --git a/src/cmd/go/internal/str/str.go b/src/cmd/go/internal/str/str.go index 021bfbff77..975869d760 100644 --- a/src/cmd/go/internal/str/str.go +++ b/src/cmd/go/internal/str/str.go @@ -30,7 +30,9 @@ func StringList(args ...any) []string { } // ToFold returns a string with the property that +// // strings.EqualFold(s, t) iff ToFold(s) == ToFold(t) +// // This lets us test a large set of strings for fold-equivalent // duplicates without making a quadratic number of calls // to EqualFold. Note that strings.ToUpper and strings.ToLower diff --git a/src/cmd/go/internal/str/str_test.go b/src/cmd/go/internal/str/str_test.go index 8ea758e0a8..158fe65dc1 100644 --- a/src/cmd/go/internal/str/str_test.go +++ b/src/cmd/go/internal/str/str_test.go @@ -5,6 +5,8 @@ package str import ( + "os" + "runtime" "testing" ) @@ -27,3 +29,72 @@ func TestFoldDup(t *testing.T) { } } } + +type trimFilePathPrefixTest struct { + s, prefix, want string +} + +func TestTrimFilePathPrefixSlash(t *testing.T) { + if os.PathSeparator != '/' { + t.Skipf("test requires slash-separated file paths") + } + tests := []trimFilePathPrefixTest{ + {"/foo", "", "foo"}, + {"/foo", "/", "foo"}, + {"/foo", "/foo", ""}, + {"/foo/bar", "/foo", "bar"}, + {"/foo/bar", "/foo/", "bar"}, + // if prefix is not s's prefix, return s + {"/foo", "/bar", "/foo"}, + {"/foo", "/foo/bar", "/foo"}, + } + + for _, tt := range tests { + if got := TrimFilePathPrefix(tt.s, tt.prefix); got != tt.want { + t.Errorf("TrimFilePathPrefix(%q, %q) = %q, want %q", tt.s, tt.prefix, got, tt.want) + } + } +} + +func TestTrimFilePathPrefixWindows(t *testing.T) { + if runtime.GOOS != "windows" { + t.Skipf("test requires Windows file paths") + } + tests := []trimFilePathPrefixTest{ + {`C:\foo`, `C:`, `foo`}, + {`C:\foo`, `C:\`, `foo`}, + {`C:\foo`, `C:\foo`, ``}, + {`C:\foo\bar`, `C:\foo`, `bar`}, + {`C:\foo\bar`, `C:\foo\`, `bar`}, + // if prefix is not s's prefix, return s + {`C:\foo`, `C:\bar`, `C:\foo`}, + {`C:\foo`, `C:\foo\bar`, `C:\foo`}, + // if volumes are different, return s + {`C:\foo`, ``, `C:\foo`}, + {`C:\foo`, `\foo`, `C:\foo`}, + {`C:\foo`, `D:\foo`, `C:\foo`}, + + //UNC path + {`\\host\share\foo`, `\\host\share`, `foo`}, + {`\\host\share\foo`, `\\host\share\`, `foo`}, + {`\\host\share\foo`, `\\host\share\foo`, ``}, + {`\\host\share\foo\bar`, `\\host\share\foo`, `bar`}, + {`\\host\share\foo\bar`, `\\host\share\foo\`, `bar`}, + // if prefix is not s's prefix, return s + {`\\host\share\foo`, `\\host\share\bar`, `\\host\share\foo`}, + {`\\host\share\foo`, `\\host\share\foo\bar`, `\\host\share\foo`}, + // if either host or share name is different, return s + {`\\host\share\foo`, ``, `\\host\share\foo`}, + {`\\host\share\foo`, `\foo`, `\\host\share\foo`}, + {`\\host\share\foo`, `\\host\other\`, `\\host\share\foo`}, + {`\\host\share\foo`, `\\other\share\`, `\\host\share\foo`}, + {`\\host\share\foo`, `\\host\`, `\\host\share\foo`}, + {`\\host\share\foo`, `\share\`, `\\host\share\foo`}, + } + + for _, tt := range tests { + if got := TrimFilePathPrefix(tt.s, tt.prefix); got != tt.want { + t.Errorf("TrimFilePathPrefix(%q, %q) = %q, want %q", tt.s, tt.prefix, got, tt.want) + } + } +} diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go index c046caca25..f3cd0b1392 100644 --- a/src/cmd/go/internal/test/testflag.go +++ b/src/cmd/go/internal/test/testflag.go @@ -270,6 +270,7 @@ func (f *shuffleFlag) Set(value string) error { // pkg.test's arguments. // We allow known flags both before and after the package name list, // to allow both +// // go test fmt -custom-flag-for-fmt-test // go test -x math func testFlags(args []string) (packageNames, passToTest []string) { diff --git a/src/cmd/go/internal/tool/tool.go b/src/cmd/go/internal/tool/tool.go index 4fe4c2baed..e8b55092d8 100644 --- a/src/cmd/go/internal/tool/tool.go +++ b/src/cmd/go/internal/tool/tool.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package tool implements the ``go tool'' command. +// Package tool implements the “go tool” command. package tool import ( diff --git a/src/cmd/go/internal/version/version.go b/src/cmd/go/internal/version/version.go index 1c0eb5407d..5de7b83efa 100644 --- a/src/cmd/go/internal/version/version.go +++ b/src/cmd/go/internal/version/version.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package version implements the ``go version'' command. +// Package version implements the “go version” command. package version import ( diff --git a/src/cmd/go/internal/vet/vet.go b/src/cmd/go/internal/vet/vet.go index d3e0dd8116..a0b11fdd3d 100644 --- a/src/cmd/go/internal/vet/vet.go +++ b/src/cmd/go/internal/vet/vet.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package vet implements the ``go vet'' command. +// Package vet implements the “go vet” command. package vet import ( diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go index 42f052d341..e9a8ee6cb3 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "runtime" + "strconv" "strings" "cmd/go/internal/base" @@ -91,11 +92,13 @@ and test commands: -buildmode mode build mode to use. See 'go help buildmode' for more. -buildvcs - Whether to stamp binaries with version control information. By default, - version control information is stamped into a binary if the main package - and the main module containing it are in the repository containing the - current directory (if there is a repository). Use -buildvcs=false to - omit version control information. + Whether to stamp binaries with version control information + ("true", "false", or "auto"). By default ("auto"), version control + information is stamped into a binary if the main package, the main module + containing it, and the current directory are all in the same repository. + Use -buildvcs=false to always omit version control information, or + -buildvcs=true to error out if version control information is available but + cannot be included due to a missing tool or ambiguous directory structure. -compiler name name of compiler to use, as in runtime.Compiler (gccgo or gc). -gccgoflags '[pattern=]arg list' @@ -302,7 +305,7 @@ func AddBuildFlags(cmd *base.Command, mask BuildFlagMask) { cmd.Flag.Var((*base.StringsFlag)(&cfg.BuildToolexec), "toolexec", "") cmd.Flag.BoolVar(&cfg.BuildTrimpath, "trimpath", false, "") cmd.Flag.BoolVar(&cfg.BuildWork, "work", false, "") - cmd.Flag.BoolVar(&cfg.BuildBuildvcs, "buildvcs", true, "") + cmd.Flag.Var((*buildvcsFlag)(&cfg.BuildBuildvcs), "buildvcs", "") // Undocumented, unstable debugging flags. cmd.Flag.StringVar(&cfg.DebugActiongraph, "debug-actiongraph", "", "") @@ -332,6 +335,29 @@ func (v *tagsFlag) String() string { return "" } +// buildvcsFlag is the implementation of the -buildvcs flag. +type buildvcsFlag string + +func (f *buildvcsFlag) IsBoolFlag() bool { return true } // allow -buildvcs (without arguments) + +func (f *buildvcsFlag) Set(s string) error { + // https://go.dev/issue/51748: allow "-buildvcs=auto", + // in addition to the usual "true" and "false". + if s == "" || s == "auto" { + *f = "auto" + return nil + } + + b, err := strconv.ParseBool(s) + if err != nil { + return errors.New("value is neither 'auto' nor a valid bool") + } + *f = (buildvcsFlag)(strconv.FormatBool(b)) // convert to canonical "true" or "false" + return nil +} + +func (f *buildvcsFlag) String() string { return string(*f) } + // fileExtSplit expects a filename and returns the name // and ext (without the dot). If the file has no // extension, ext will be empty. @@ -379,7 +405,7 @@ func runBuild(ctx context.Context, cmd *base.Command, args []string) { var b Builder b.Init() - pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{LoadVCS: cfg.BuildBuildvcs}, args) + pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{LoadVCS: true}, args) load.CheckPackageErrors(pkgs) explicitO := len(cfg.BuildO) > 0 @@ -532,16 +558,22 @@ See also: go build, go get, go clean. // libname returns the filename to use for the shared library when using // -buildmode=shared. The rules we use are: // Use arguments for special 'meta' packages: +// // std --> libstd.so // std cmd --> libstd,cmd.so +// // A single non-meta argument with trailing "/..." is special cased: +// // foo/... --> libfoo.so // (A relative path like "./..." expands the "." first) +// // Use import paths for other cases, changing '/' to '-': +// // somelib --> libsubdir-somelib.so // ./ or ../ --> libsubdir-somelib.so // gopkg.in/tomb.v2 -> libgopkg.in-tomb.v2.so // a/... b/... ---> liba/c,b/d.so - all matching import paths +// // Name parts are joined with ','. func libname(args []string, pkgs []*load.Package) (string, error) { var libname string @@ -603,7 +635,7 @@ func runInstall(ctx context.Context, cmd *base.Command, args []string) { modload.InitWorkfile() BuildInit() - pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{LoadVCS: cfg.BuildBuildvcs}, args) + pkgs := load.PackagesAndErrors(ctx, load.PackageOpts{LoadVCS: true}, args) if cfg.ModulesEnabled && !modload.HasModRoot() { haveErrors := false allMissingErrors := true diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 4252209f10..9c9d58b2a1 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -303,7 +303,9 @@ func (b *Builder) buildActionID(a *Action) cache.ActionID { fmt.Fprintf(h, "fuzz %q\n", fuzzFlags) } } - fmt.Fprintf(h, "modinfo %q\n", p.Internal.BuildInfo) + if p.Internal.BuildInfo != "" { + fmt.Fprintf(h, "modinfo %q\n", p.Internal.BuildInfo) + } // Configuration specific to compiler toolchain. switch cfg.BuildToolchainName { @@ -1882,6 +1884,7 @@ func (b *Builder) installHeader(ctx context.Context, a *Action) error { } // cover runs, in effect, +// // go tool cover -mode=b.coverMode -var="varName" -o dst.go src.go func (b *Builder) cover(a *Action, dst, src string, varName string) error { return b.run(a, a.Objdir, "cover "+a.Package.ImportPath, nil, diff --git a/src/cmd/go/internal/work/security.go b/src/cmd/go/internal/work/security.go index d1e2c673fa..0bf8763543 100644 --- a/src/cmd/go/internal/work/security.go +++ b/src/cmd/go/internal/work/security.go @@ -171,7 +171,7 @@ var validLinkerFlags = []*lazyregexp.Regexp{ // Note that any wildcards in -Wl need to exclude comma, // since -Wl splits its argument at commas and passes // them all to the linker uninterpreted. Allowing comma - // in a wildcard would allow tunnelling arbitrary additional + // in a wildcard would allow tunneling arbitrary additional // linker arguments through one of these. re(`-Wl,--(no-)?allow-multiple-definition`), re(`-Wl,--(no-)?allow-shlib-undefined`), diff --git a/src/cmd/go/internal/workcmd/work.go b/src/cmd/go/internal/workcmd/work.go index 39c81e8f5d..c99cc2a3fa 100644 --- a/src/cmd/go/internal/workcmd/work.go +++ b/src/cmd/go/internal/workcmd/work.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package workcmd implements the ``go work'' command. +// Package workcmd implements the “go work” command. package workcmd import ( diff --git a/src/cmd/go/script_test.go b/src/cmd/go/script_test.go index 76c542f32a..6254cf97c1 100644 --- a/src/cmd/go/script_test.go +++ b/src/cmd/go/script_test.go @@ -175,7 +175,7 @@ func (ts *testScript) setup() { "GOPROXY=" + proxyURL, "GOPRIVATE=", "GOROOT=" + testGOROOT, - "GOROOT_FINAL=" + os.Getenv("GOROOT_FINAL"), // causes spurious rebuilds and breaks the "stale" built-in if not propagated + "GOROOT_FINAL=" + testGOROOT_FINAL, // causes spurious rebuilds and breaks the "stale" built-in if not propagated "GOTRACEBACK=system", "TESTGO_GOROOT=" + testGOROOT, "GOSUMDB=" + testSumDBVerifierKey, @@ -385,6 +385,8 @@ Script: } } } + case "mismatched-goroot": + ok = testGOROOT_FINAL != "" && testGOROOT_FINAL != testGOROOT default: if strings.HasPrefix(cond.tag, "exec:") { prog := cond.tag[len("exec:"):] diff --git a/src/cmd/go/testdata/script/README b/src/cmd/go/testdata/script/README index 17b582d662..85e575d56e 100644 --- a/src/cmd/go/testdata/script/README +++ b/src/cmd/go/testdata/script/README @@ -90,6 +90,8 @@ should only run when the condition is satisfied. The available conditions are: - [exec:prog] for whether prog is available for execution (found by exec.LookPath) - [GODEBUG:value] for whether value is one of the comma-separated entries in the GODEBUG variable - [buildmode:value] for whether -buildmode=value is supported + - [trimpath] for whether the 'go' binary was built with -trimpath + - [mismatched-goroot] for whether the test's GOROOT_FINAL does not match the real GOROOT A condition can be negated: [!short] means to run the rest of the line when testing.Short() is false. Multiple conditions may be given for a single diff --git a/src/cmd/go/testdata/script/build_buildvcs_auto.txt b/src/cmd/go/testdata/script/build_buildvcs_auto.txt new file mode 100644 index 0000000000..9eac568045 --- /dev/null +++ b/src/cmd/go/testdata/script/build_buildvcs_auto.txt @@ -0,0 +1,87 @@ +# Regression test for https://go.dev/issue/51748: by default, 'go build' should +# not attempt to stamp VCS information when the VCS tool is not present. + +[short] skip +[!exec:git] skip + +cd sub +exec git init . +exec git add sub.go +exec git commit -m 'initial state' +cd .. + +exec git init +exec git submodule add ./sub +exec git add go.mod example.go +exec git commit -m 'initial state' + + +# Control case: with a git binary in $PATH, +# 'go build' on a package in the same git repo +# succeeds and stamps VCS metadata by default. + +go build -o example.exe . +go version -m example.exe +stdout '^\tbuild\tvcs=git$' +stdout '^\tbuild\tvcs.modified=false$' + + +# Building a binary from a different (nested) VCS repo should not stamp VCS +# info. It should be an error if VCS stamps are requested explicitly with +# '-buildvcs' (since we know the VCS metadata exists), but not an error +# with '-buildvcs=auto'. + +go build -o sub.exe ./sub +go version -m sub.exe +! stdout '^\tbuild\tvcs' + +! go build -buildvcs -o sub.exe ./sub +stderr '\Aerror obtaining VCS status: main package is in repository ".*" but current directory is in repository ".*"\n\tUse -buildvcs=false to disable VCS stamping.\n\z' + +cd ./sub +go build -o sub.exe . +go version -m sub.exe +! stdout '^\tbuild\tvcs' + +! go build -buildvcs -o sub.exe . +stderr '\Aerror obtaining VCS status: main module is in repository ".*" but current directory is in repository ".*"\n\tUse -buildvcs=false to disable VCS stamping.\n\z' +cd .. + + +# After removing 'git' from $PATH, 'go build -buildvcs' should fail... + +env PATH= +env path= +! go build -buildvcs -o example.exe . +stderr 'go: missing Git command\. See https://golang\.org/s/gogetcmd$' + +# ...but by default we should omit VCS metadata when the tool is missing. + +go build -o example.exe . +go version -m example.exe +! stdout '^\tbuild\tvcs' + +# The default behavior can be explicitly set with '-buildvcs=auto'. + +go build -buildvcs=auto -o example.exe . +go version -m example.exe +! stdout '^\tbuild\tvcs' + +# Other flag values should be rejected with a useful error message. + +! go build -buildvcs=hg -o example.exe . +stderr '\Ainvalid boolean value "hg" for -buildvcs: value is neither ''auto'' nor a valid bool\nusage: go build .*\nRun ''go help build'' for details.\n\z' + + +-- go.mod -- +module example + +go 1.18 +-- example.go -- +package main + +func main() {} +-- sub/sub.go -- +package main + +func main() {} diff --git a/src/cmd/go/testdata/script/build_trimpath_goroot.txt b/src/cmd/go/testdata/script/build_trimpath_goroot.txt index 7b870ab739..a26cfd23be 100644 --- a/src/cmd/go/testdata/script/build_trimpath_goroot.txt +++ b/src/cmd/go/testdata/script/build_trimpath_goroot.txt @@ -8,23 +8,51 @@ # TODO(#51483): when runtime.GOROOT() returns the empty string, # go/build should default to 'go env GOROOT' instead. -env GOROOT= env GOROOT_FINAL= +[trimpath] env GOROOT= [trimpath] ! go env GOROOT [trimpath] stderr '^go: cannot find GOROOT directory: ''go'' binary is trimmed and GOROOT is not set$' +[trimpath] env GOROOT=$TESTGO_GOROOT + +[short] stop + +# With GOROOT still set but GOROOT_FINAL unset, 'go build' and 'go test -c' +# should cause runtime.GOROOT() to report either the correct GOROOT +# (without -trimpath) or no GOROOT at all (with -trimpath). + +go build -o example.exe . +go build -trimpath -o example-trimpath.exe . +go test -c -o example.test.exe . +go test -trimpath -c -o example.test-trimpath.exe . + +env GOROOT= + +exec ./example.exe +stdout '^GOROOT '$TESTGO_GOROOT'$' +stdout '^runtime '$TESTGO_GOROOT${/}src${/}runtime'$' + +! exec ./example-trimpath.exe +stdout '^GOROOT $' +stderr 'cannot find package "runtime" in any of:\n\t\(\$GOROOT not set\)\n\t'$WORK${/}gopath${/}src${/}runtime' \(from \$GOPATH\)\n\z' + +exec ./example.test.exe -test.v +stdout '^GOROOT '$TESTGO_GOROOT'$' +stdout '^runtime '$TESTGO_GOROOT${/}src${/}runtime'$' + +! exec ./example.test-trimpath.exe -test.v +stdout '^GOROOT $' +stderr 'cannot find package "runtime" in any of:\n\t\(\$GOROOT not set\)\n\t'$WORK${/}gopath${/}src${/}runtime' \(from \$GOPATH\)$' + +# If a correct GOROOT is baked in to the 'go' command itself, 'go run' and +# 'go test' should not implicitly set GOROOT in the process environment +# (because that could mask an unexpected production dependency on the GOROOT +# environment variable), but 'go generate' should (because the generator may +# reasonably expect to be able to locate the GOROOT for which it is generating +# code). + [trimpath] stop - - -[short] skip - -go run . -stdout '^GOROOT '$TESTGO_GOROOT'$' -stdout '^runtime '$TESTGO_GOROOT${/}src${/}runtime'$' - -go test -v . -stdout '^GOROOT '$TESTGO_GOROOT'$' -stdout '^runtime '$TESTGO_GOROOT${/}src${/}runtime'$' +[mismatched-goroot] stop ! go run -trimpath . stdout '^GOROOT $' @@ -34,6 +62,11 @@ stderr 'cannot find package "runtime" in any of:\n\t\(\$GOROOT not set\)\n\t'$WO stdout '^GOROOT $' stdout 'cannot find package "runtime" in any of:\n\t\(\$GOROOT not set\)\n\t'$WORK${/}gopath${/}src${/}runtime' \(from \$GOPATH\)$' +env GOFLAGS=-trimpath +go generate . +stdout '^GOROOT '$TESTGO_GOROOT'$' +stdout '^runtime '$TESTGO_GOROOT${/}src${/}runtime'$' + -- go.mod -- module example @@ -41,6 +74,8 @@ go 1.19 -- main.go -- package main +//go:generate go run . + import ( "fmt" "go/build" diff --git a/src/cmd/go/testdata/script/test_buildvcs.txt b/src/cmd/go/testdata/script/test_buildvcs.txt index a0689195e8..a669966036 100644 --- a/src/cmd/go/testdata/script/test_buildvcs.txt +++ b/src/cmd/go/testdata/script/test_buildvcs.txt @@ -5,6 +5,8 @@ [short] skip [!exec:git] skip +env GOFLAGS=-buildvcs # override default -buildvcs=auto in GOFLAGS, as a user might + exec git init # The test binaries should not have VCS settings stamped. 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 a09e85b972..e61c4f9d04 100644 --- a/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt +++ b/src/cmd/go/testdata/script/test_fuzz_minimize_interesting.txt @@ -18,30 +18,27 @@ env GOCACHE=$WORK/gocache exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinCache -test.fuzztime=1000x go run check_cache/check_cache.go $GOCACHE/fuzz/FuzzMinCache -go test -c -fuzz=. # Build using shared build cache for speed. -env GOCACHE=$WORK/gocache - # 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. -! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.run=FuzzMinimizerCrashInMinimization -test.fuzztime=10000x -test.parallel=1 +! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerCrashInMinimization -test.run=XXX -test.fuzztime=10000x -test.parallel=1 ! stdout '^ok' stdout -count=1 'got the minimum size!' -stdout -count=1 'flaky failure' +stdout -count=1 'bad input' stdout FAIL # Check that the input written to testdata will reproduce the error, and is the # smallest possible. -go run check_testdata/check_testdata.go FuzzMinimizerCrashInMinimization 50 +go run check_testdata/check_testdata.go FuzzMinimizerCrashInMinimization 1 # Test that a nonrecoverable error that occurs while minimizing an interesting # input is reported correctly. -! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerNonrecoverableCrashInMinimization -test.run=FuzzMinimizerNonrecoverableCrashInMinimization -test.fuzztime=10000x -test.parallel=1 +! exec ./fuzz.test$GOEXE -test.fuzzcachedir=$GOCACHE/fuzz -test.fuzz=FuzzMinimizerNonrecoverableCrashInMinimization -test.run=XXX -test.fuzztime=10000x -test.parallel=1 ! stdout '^ok' stdout -count=1 'fuzzing process hung or terminated unexpectedly while minimizing' stdout -count=1 'EOF' stdout FAIL # Check that the input written to testdata will reproduce the error. -go run check_testdata/check_testdata.go FuzzMinimizerNonrecoverableCrashInMinimization 100 +go run check_testdata/check_testdata.go FuzzMinimizerNonrecoverableCrashInMinimization 1 -- go.mod -- module fuzz @@ -65,57 +62,34 @@ package fuzz import ( "bytes" - "io" "os" - "strings" "testing" - "unicode/utf8" ) func FuzzMinimizerCrashInMinimization(f *testing.F) { - seed := strings.Repeat("A", 1000) + seed := bytes.Repeat([]byte{255}, 100) f.Add(seed) - i := 3 - f.Fuzz(func(t *testing.T, s string) { - if len(s) < 50 || len(s) > 1100 { - // Make sure that b is large enough that it can be minimized + f.Fuzz(func(t *testing.T, b []byte) { + if bytes.Equal(seed, b) { return } - if s != seed { - // This should hit a new edge, and the interesting input - // should attempt minimization - Y(io.Discard, s) - } - if i > 0 { - // Don't let it fail right away. - i-- - } else if utf8.RuneCountInString(s) == len(s) && len(s) <= 100 { - // Make sure this only fails if the number of bytes in the - // marshaled string is the same as the unmarshaled string, - // so that we can check the length of the testdata file. - t.Error("flaky failure") - if len(s) == 50 { - t.Error("got the minimum size!") - } + t.Error("bad input") + if len(b) == 1 { + t.Error("got the minimum size!") } }) } +var fuzzing bool + func FuzzMinimizerNonrecoverableCrashInMinimization(f *testing.F) { - seed := strings.Repeat("A", 1000) + seed := bytes.Repeat([]byte{255}, 100) f.Add(seed) - i := 3 - f.Fuzz(func(t *testing.T, s string) { - if len(s) < 50 || len(s) > 1100 { + f.Fuzz(func(t *testing.T, b []byte) { + if bytes.Equal(seed, b) { return - } - if s != seed { - Y(io.Discard, s) - } - if i > 0 { - i-- - } else if utf8.RuneCountInString(s) == len(s) && len(s) <= 100 { - os.Exit(19) + } else if len(b) == 1 { + os.Exit(1) } }) } @@ -138,10 +112,12 @@ func FuzzMinCache(f *testing.F) { package main import ( + "bytes" "fmt" "io/ioutil" "os" "path/filepath" + "regexp" "strconv" ) @@ -165,22 +141,36 @@ func main() { os.Exit(1) } - fname := files[0].Name() - contents, err := ioutil.ReadFile(filepath.Join(dir, fname)) - if err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) + for _, f := range files { + data, err := ioutil.ReadFile(filepath.Join(dir, f.Name())) + if err != nil { + panic(err) + } + var containsVal bool + for _, line := range bytes.Split(data, []byte("\n")) { + m := valRe.FindSubmatch(line) + if m == nil { + continue + } + containsVal = true + s, err := strconv.Unquote(string(m[1])) + if err != nil { + panic(err) + } + if len(s) != wantLen { + fmt.Fprintf(os.Stderr, "expect length %d, got %d (%q)\n", wantLen, len(s), line) + os.Exit(1) + } + } + if !containsVal { + fmt.Fprintln(os.Stderr, "corpus file contained no values") + os.Exit(1) + } } - contentsLen := len(contents) - len(`go test fuzz v1 -string("") -`) - if got, want := contentsLen, wantLen; got > want { - fmt.Fprintf(os.Stderr, "expect length <= %d, got %d\n", want, got) - os.Exit(1) - } - fmt.Fprintf(os.Stderr, "%s\n", contents) } +var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`) + -- check_cache/check_cache.go -- //go:build ignore // +build ignore diff --git a/src/cmd/go/testdata/script/version_buildvcs_nested.txt b/src/cmd/go/testdata/script/version_buildvcs_nested.txt index 08d4c92baf..a0c69f9c12 100644 --- a/src/cmd/go/testdata/script/version_buildvcs_nested.txt +++ b/src/cmd/go/testdata/script/version_buildvcs_nested.txt @@ -1,7 +1,7 @@ [!exec:git] skip [!exec:hg] skip [short] skip -env GOFLAGS=-n +env GOFLAGS='-n -buildvcs' # Create a root module in a root Git repository. mkdir root diff --git a/src/cmd/gofmt/doc.go b/src/cmd/gofmt/doc.go index e340665594..8ac9c6a931 100644 --- a/src/cmd/gofmt/doc.go +++ b/src/cmd/gofmt/doc.go @@ -13,9 +13,11 @@ that directory, recursively. (Files starting with a period are ignored.) By default, gofmt prints the reformatted sources to standard output. Usage: + gofmt [flags] [path ...] The flags are: + -d Do not print reformatted sources to standard output. If a file's formatting is different than gofmt's, print diffs @@ -37,10 +39,10 @@ The flags are: the original file is restored from an automatic backup. Debugging support: + -cpuprofile filename Write cpu profile to the specified file. - The rewrite rule specified with the -r flag must be a string of the form: pattern -> replacement @@ -57,7 +59,7 @@ such a fragment, gofmt preserves leading indentation as well as leading and trailing spaces, so that individual sections of a Go program can be formatted by piping them through gofmt. -Examples +# Examples To check files for unnecessary parentheses: @@ -71,7 +73,7 @@ To convert the package tree from explicit slice upper bounds to implicit ones: gofmt -r 'α[β:len(α)] -> α[β:]' -w $GOROOT/src -The simplify command +# The simplify command When invoked with -s gofmt will make the following source transformations where possible. diff --git a/src/cmd/internal/bio/buf_mmap.go b/src/cmd/internal/bio/buf_mmap.go index b9755c7e50..89ae39f736 100644 --- a/src/cmd/internal/bio/buf_mmap.go +++ b/src/cmd/internal/bio/buf_mmap.go @@ -18,12 +18,12 @@ import ( // because some operating systems place a limit on the number of // distinct mapped regions per process. As of this writing: // -// Darwin unlimited -// DragonFly 1000000 (vm.max_proc_mmap) -// FreeBSD unlimited -// Linux 65530 (vm.max_map_count) // TODO: query /proc/sys/vm/max_map_count? -// NetBSD unlimited -// OpenBSD unlimited +// Darwin unlimited +// DragonFly 1000000 (vm.max_proc_mmap) +// FreeBSD unlimited +// Linux 65530 (vm.max_map_count) // TODO: query /proc/sys/vm/max_map_count? +// NetBSD unlimited +// OpenBSD unlimited var mmapLimit int32 = 1<<31 - 1 func init() { diff --git a/src/cmd/internal/gcprog/gcprog.go b/src/cmd/internal/gcprog/gcprog.go index c8bf206468..eeea53daf4 100644 --- a/src/cmd/internal/gcprog/gcprog.go +++ b/src/cmd/internal/gcprog/gcprog.go @@ -5,7 +5,7 @@ // Package gcprog implements an encoder for packed GC pointer bitmaps, // known as GC programs. // -// Program Format +// # Program Format // // The GC program encodes a sequence of 0 and 1 bits indicating scalar or pointer words in an object. // The encoding is a simple Lempel-Ziv program, with codes to emit literal bits and to repeat the @@ -20,7 +20,6 @@ // // The numbers n and c, when they follow a code, are encoded as varints // using the same encoding as encoding/binary's Uvarint. -// package gcprog import ( diff --git a/src/cmd/internal/goobj/objfile.go b/src/cmd/internal/goobj/objfile.go index af2a0df338..3e36c461fa 100644 --- a/src/cmd/internal/goobj/objfile.go +++ b/src/cmd/internal/goobj/objfile.go @@ -264,15 +264,16 @@ func (p *ImportedPkg) Write(w *Writer) { // Symbol definition. // // Serialized format: -// Sym struct { -// Name string -// ABI uint16 -// Type uint8 -// Flag uint8 -// Flag2 uint8 -// Siz uint32 -// Align uint32 -// } +// +// Sym struct { +// Name string +// ABI uint16 +// Type uint8 +// Flag uint8 +// Flag2 uint8 +// Siz uint32 +// Align uint32 +// } type Sym [SymSize]byte const SymSize = stringRefSize + 2 + 1 + 1 + 1 + 4 + 4 @@ -371,13 +372,14 @@ const HashSize = sha1.Size // Relocation. // // Serialized format: -// Reloc struct { -// Off int32 -// Siz uint8 -// Type uint16 -// Add int64 -// Sym SymRef -// } +// +// Reloc struct { +// Off int32 +// Siz uint8 +// Type uint16 +// Add int64 +// Sym SymRef +// } type Reloc [RelocSize]byte const RelocSize = 4 + 1 + 2 + 8 + 8 @@ -415,10 +417,11 @@ func (r *Reloc) fromBytes(b []byte) { copy(r[:], b) } // Aux symbol info. // // Serialized format: -// Aux struct { -// Type uint8 -// Sym SymRef -// } +// +// Aux struct { +// Type uint8 +// Sym SymRef +// } type Aux [AuxSize]byte const AuxSize = 1 + 8 @@ -458,11 +461,12 @@ func (a *Aux) fromBytes(b []byte) { copy(a[:], b) } // Referenced symbol flags. // // Serialized format: -// RefFlags struct { -// Sym symRef -// Flag uint8 -// Flag2 uint8 -// } +// +// RefFlags struct { +// Sym symRef +// Flag uint8 +// Flag2 uint8 +// } type RefFlags [RefFlagsSize]byte const RefFlagsSize = 8 + 1 + 1 @@ -490,10 +494,11 @@ const huge = (1<<31 - 1) / RelocSize // Referenced symbol name. // // Serialized format: -// RefName struct { -// Sym symRef -// Name string -// } +// +// RefName struct { +// Sym symRef +// Name string +// } type RefName [RefNameSize]byte const RefNameSize = 8 + stringRefSize diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go index 72c4cd48ed..57d4e7a8d3 100644 --- a/src/cmd/internal/obj/arm64/asm7.go +++ b/src/cmd/internal/obj/arm64/asm7.go @@ -3977,7 +3977,7 @@ func (c *ctxt7) asmout(p *obj.Prog, o *Optab, out []uint32) { if (o1&S64) == 0 && s >= 2 { c.ctxt.Diag("illegal bit position\n%v", p) } - if ((d >> uint(s*16)) >> 16) != 0 { + if ((uint64(d) >> uint(s*16)) >> 16) != 0 { c.ctxt.Diag("requires uimm16\n%v", p) } rt := int(p.To.Reg) diff --git a/src/cmd/internal/obj/arm64/asm_arm64_test.go b/src/cmd/internal/obj/arm64/asm_arm64_test.go index c6a00f5b94..f468b6b0fe 100644 --- a/src/cmd/internal/obj/arm64/asm_arm64_test.go +++ b/src/cmd/internal/obj/arm64/asm_arm64_test.go @@ -160,3 +160,14 @@ func TestVMOVQ(t *testing.T) { t.Errorf("TestVMOVQ got: a=0x%x, b=0x%x, want: a=0x7040201008040201, b=0x3040201008040201", a, b) } } + +func testmovk() uint64 + +// TestMOVK makes sure MOVK with a very large constant works. See issue 52261. +func TestMOVK(t *testing.T) { + x := testmovk() + want := uint64(40000 << 48) + if x != want { + t.Errorf("TestMOVK got %x want %x\n", x, want) + } +} diff --git a/src/cmd/internal/obj/arm64/asm_arm64_test.s b/src/cmd/internal/obj/arm64/asm_arm64_test.s index 9d337a4fd1..f85433c6e3 100644 --- a/src/cmd/internal/obj/arm64/asm_arm64_test.s +++ b/src/cmd/internal/obj/arm64/asm_arm64_test.s @@ -12,3 +12,10 @@ TEXT ·testvmovq(SB), NOSPLIT, $0-16 MOVD R0, r1+0(FP) MOVD R1, r2+8(FP) RET + +// testmovk() uint64 +TEXT ·testmovk(SB), NOSPLIT, $0-8 + MOVD $0, R0 + MOVK $(40000<<48), R0 + MOVD R0, ret+0(FP) + RET diff --git a/src/cmd/internal/obj/arm64/doc.go b/src/cmd/internal/obj/arm64/doc.go index 2763cf4139..c12f618e93 100644 --- a/src/cmd/internal/obj/arm64/doc.go +++ b/src/cmd/internal/obj/arm64/doc.go @@ -6,24 +6,26 @@ Package arm64 implements an ARM64 assembler. Go assembly syntax is different from GNU ARM64 syntax, but we can still follow the general rules to map between them. -Instructions mnemonics mapping rules +# Instructions mnemonics mapping rules 1. Most instructions use width suffixes of instruction names to indicate operand width rather than using different register names. Examples: - ADC R24, R14, R12 <=> adc x12, x24 - ADDW R26->24, R21, R15 <=> add w15, w21, w26, asr #24 - FCMPS F2, F3 <=> fcmp s3, s2 - FCMPD F2, F3 <=> fcmp d3, d2 - FCVTDH F2, F3 <=> fcvt h3, d2 + + ADC R24, R14, R12 <=> adc x12, x24 + ADDW R26->24, R21, R15 <=> add w15, w21, w26, asr #24 + FCMPS F2, F3 <=> fcmp s3, s2 + FCMPD F2, F3 <=> fcmp d3, d2 + FCVTDH F2, F3 <=> fcvt h3, d2 2. Go uses .P and .W suffixes to indicate post-increment and pre-increment. Examples: - MOVD.P -8(R10), R8 <=> ldr x8, [x10],#-8 - MOVB.W 16(R16), R10 <=> ldrsb x10, [x16,#16]! - MOVBU.W 16(R16), R10 <=> ldrb x10, [x16,#16]! + + MOVD.P -8(R10), R8 <=> ldr x8, [x10],#-8 + MOVB.W 16(R16), R10 <=> ldrsb x10, [x16,#16]! + MOVBU.W 16(R16), R10 <=> ldrb x10, [x16,#16]! 3. Go uses a series of MOV instructions as load and store. @@ -40,11 +42,12 @@ ldrsh, sturh, strh => MOVH. instructions and floating-point(scalar) instructions. Examples: - VADD V5.H8, V18.H8, V9.H8 <=> add v9.8h, v18.8h, v5.8h - VLD1.P (R6)(R11), [V31.D1] <=> ld1 {v31.1d}, [x6], x11 - VFMLA V29.S2, V20.S2, V14.S2 <=> fmla v14.2s, v20.2s, v29.2s - AESD V22.B16, V19.B16 <=> aesd v19.16b, v22.16b - SCVTFWS R3, F16 <=> scvtf s17, w6 + + VADD V5.H8, V18.H8, V9.H8 <=> add v9.8h, v18.8h, v5.8h + VLD1.P (R6)(R11), [V31.D1] <=> ld1 {v31.1d}, [x6], x11 + VFMLA V29.S2, V20.S2, V14.S2 <=> fmla v14.2s, v20.2s, v29.2s + AESD V22.B16, V19.B16 <=> aesd v19.16b, v22.16b + SCVTFWS R3, F16 <=> scvtf s17, w6 6. Align directive @@ -53,10 +56,11 @@ to a specified boundary by padding with NOOP instruction. The alignment value su must be a power of 2 and in the range of [8, 2048]. Examples: - PCALIGN $16 - MOVD $2, R0 // This instruction is aligned with 16 bytes. - PCALIGN $1024 - MOVD $3, R1 // This instruction is aligned with 1024 bytes. + + PCALIGN $16 + MOVD $2, R0 // This instruction is aligned with 16 bytes. + PCALIGN $1024 + MOVD $3, R1 // This instruction is aligned with 1024 bytes. PCALIGN also changes the function alignment. If a function has one or more PCALIGN directives, its address will be aligned to the same or coarser boundary, which is the maximum of all the @@ -65,13 +69,14 @@ alignment values. In the following example, the function Add is aligned with 128 bytes. Examples: - TEXT ·Add(SB),$40-16 - MOVD $2, R0 - PCALIGN $32 - MOVD $4, R1 - PCALIGN $128 - MOVD $8, R2 - RET + + TEXT ·Add(SB),$40-16 + MOVD $2, R0 + PCALIGN $32 + MOVD $4, R1 + PCALIGN $128 + MOVD $8, R2 + RET On arm64, functions in Go are aligned to 16 bytes by default, we can also use PCALGIN to set the function alignment. The functions that need to be aligned are preferably using NOFRAME and NOSPLIT @@ -81,11 +86,12 @@ have the same alignment as the first hand-written instruction. In the following example, PCALIGN at the entry of the function Add will align its address to 2048 bytes. Examples: - TEXT ·Add(SB),NOSPLIT|NOFRAME,$0 - PCALIGN $2048 - MOVD $1, R0 - MOVD $1, R1 - RET + + TEXT ·Add(SB),NOSPLIT|NOFRAME,$0 + PCALIGN $2048 + MOVD $1, R0 + MOVD $1, R1 + RET 7. Move large constants to vector registers. @@ -93,9 +99,10 @@ Go asm uses VMOVQ/VMOVD/VMOVS to move 128-bit, 64-bit and 32-bit constants into And for a 128-bit interger, it take two 64-bit operands, for the low and high parts separately. Examples: - VMOVS $0x11223344, V0 - VMOVD $0x1122334455667788, V1 - VMOVQ $0x1122334455667788, $0x99aabbccddeeff00, V2 // V2=0x99aabbccddeeff001122334455667788 + + VMOVS $0x11223344, V0 + VMOVD $0x1122334455667788, V1 + VMOVQ $0x1122334455667788, $0x99aabbccddeeff00, V2 // V2=0x99aabbccddeeff001122334455667788 8. Move an optionally-shifted 16-bit immediate value to a register. @@ -106,9 +113,10 @@ is the 16-bit unsigned immediate, in the range 0 to 65535; For the 32-bit varian The current Go assembler does not accept zero shifts, such as "op $0, Rd" and "op $(0<<(16|32|48)), Rd" instructions. Examples: - MOVK $(10<<32), R20 <=> movk x20, #10, lsl #32 - MOVZW $(20<<16), R8 <=> movz w8, #20, lsl #16 - MOVK $(0<<16), R10 will be reported as an error by the assembler. + + MOVK $(10<<32), R20 <=> movk x20, #10, lsl #32 + MOVZW $(20<<16), R8 <=> movz w8, #20, lsl #16 + MOVK $(0<<16), R10 will be reported as an error by the assembler. Special Cases. @@ -123,15 +131,15 @@ related to real ARM64 instruction. NOOP serves for the hardware nop instruction. HINT $0. Examples: - VMOV V13.B[1], R20 <=> mov x20, v13.b[1] - VMOV V13.H[1], R20 <=> mov w20, v13.h[1] - JMP (R3) <=> br x3 - CALL (R17) <=> blr x17 - LDAXRB (R19), R16 <=> ldaxrb w16, [x19] - NOOP <=> nop + VMOV V13.B[1], R20 <=> mov x20, v13.b[1] + VMOV V13.H[1], R20 <=> mov w20, v13.h[1] + JMP (R3) <=> br x3 + CALL (R17) <=> blr x17 + LDAXRB (R19), R16 <=> ldaxrb w16, [x19] + NOOP <=> nop -Register mapping rules +# Register mapping rules 1. All basic register names are written as Rn. @@ -140,16 +148,16 @@ Register mapping rules 3. Bn, Hn, Dn, Sn and Qn instructions are written as Fn in floating-point instructions and as Vn in SIMD instructions. - -Argument mapping rules +# Argument mapping rules 1. The operands appear in left-to-right assignment order. Go reverses the arguments of most instructions. Examples: - ADD R11.SXTB<<1, RSP, R25 <=> add x25, sp, w11, sxtb #1 - VADD V16, V19, V14 <=> add d14, d19, d16 + + ADD R11.SXTB<<1, RSP, R25 <=> add x25, sp, w11, sxtb #1 + VADD V16, V19, V14 <=> add d14, d19, d16 Special Cases. @@ -157,70 +165,79 @@ Special Cases. such as str, stur, strb, sturb, strh, sturh stlr, stlrb. stlrh, st1. Examples: - MOVD R29, 384(R19) <=> str x29, [x19,#384] - MOVB.P R30, 30(R4) <=> strb w30, [x4],#30 - STLRH R21, (R19) <=> stlrh w21, [x19] + + MOVD R29, 384(R19) <=> str x29, [x19,#384] + MOVB.P R30, 30(R4) <=> strb w30, [x4],#30 + STLRH R21, (R19) <=> stlrh w21, [x19] (2) MADD, MADDW, MSUB, MSUBW, SMADDL, SMSUBL, UMADDL, UMSUBL , , , Examples: - MADD R2, R30, R22, R6 <=> madd x6, x22, x2, x30 - SMSUBL R10, R3, R17, R27 <=> smsubl x27, w17, w10, x3 + + MADD R2, R30, R22, R6 <=> madd x6, x22, x2, x30 + SMSUBL R10, R3, R17, R27 <=> smsubl x27, w17, w10, x3 (3) FMADDD, FMADDS, FMSUBD, FMSUBS, FNMADDD, FNMADDS, FNMSUBD, FNMSUBS , , , Examples: - FMADDD F30, F20, F3, F29 <=> fmadd d29, d3, d30, d20 - FNMSUBS F7, F25, F7, F22 <=> fnmsub s22, s7, s7, s25 + + FMADDD F30, F20, F3, F29 <=> fmadd d29, d3, d30, d20 + FNMSUBS F7, F25, F7, F22 <=> fnmsub s22, s7, s7, s25 (4) BFI, BFXIL, SBFIZ, SBFX, UBFIZ, UBFX $, , $, Examples: - BFIW $16, R20, $6, R0 <=> bfi w0, w20, #16, #6 - UBFIZ $34, R26, $5, R20 <=> ubfiz x20, x26, #34, #5 + + BFIW $16, R20, $6, R0 <=> bfi w0, w20, #16, #6 + UBFIZ $34, R26, $5, R20 <=> ubfiz x20, x26, #34, #5 (5) FCCMPD, FCCMPS, FCCMPED, FCCMPES , Fm. Fn, $ Examples: - FCCMPD AL, F8, F26, $0 <=> fccmp d26, d8, #0x0, al - FCCMPS VS, F29, F4, $4 <=> fccmp s4, s29, #0x4, vs - FCCMPED LE, F20, F5, $13 <=> fccmpe d5, d20, #0xd, le - FCCMPES NE, F26, F10, $0 <=> fccmpe s10, s26, #0x0, ne + + FCCMPD AL, F8, F26, $0 <=> fccmp d26, d8, #0x0, al + FCCMPS VS, F29, F4, $4 <=> fccmp s4, s29, #0x4, vs + FCCMPED LE, F20, F5, $13 <=> fccmpe d5, d20, #0xd, le + FCCMPES NE, F26, F10, $0 <=> fccmpe s10, s26, #0x0, ne (6) CCMN, CCMNW, CCMP, CCMPW , , $, $ Examples: - CCMP MI, R22, $12, $13 <=> ccmp x22, #0xc, #0xd, mi - CCMNW AL, R1, $11, $8 <=> ccmn w1, #0xb, #0x8, al + + CCMP MI, R22, $12, $13 <=> ccmp x22, #0xc, #0xd, mi + CCMNW AL, R1, $11, $8 <=> ccmn w1, #0xb, #0x8, al (7) CCMN, CCMNW, CCMP, CCMPW , , , $ Examples: - CCMN VS, R13, R22, $10 <=> ccmn x13, x22, #0xa, vs - CCMPW HS, R19, R14, $11 <=> ccmp w19, w14, #0xb, cs + + CCMN VS, R13, R22, $10 <=> ccmn x13, x22, #0xa, vs + CCMPW HS, R19, R14, $11 <=> ccmp w19, w14, #0xb, cs (9) CSEL, CSELW, CSNEG, CSNEGW, CSINC, CSINCW , , , ; FCSELD, FCSELS , , , Examples: - CSEL GT, R0, R19, R1 <=> csel x1, x0, x19, gt - CSNEGW GT, R7, R17, R8 <=> csneg w8, w7, w17, gt - FCSELD EQ, F15, F18, F16 <=> fcsel d16, d15, d18, eq + + CSEL GT, R0, R19, R1 <=> csel x1, x0, x19, gt + CSNEGW GT, R7, R17, R8 <=> csneg w8, w7, w17, gt + FCSELD EQ, F15, F18, F16 <=> fcsel d16, d15, d18, eq (10) TBNZ, TBZ $, ,

\n") - html_endp = []byte("

\n") - html_pre = []byte("
")
-	html_endpre = []byte("
\n") - html_h = []byte(`

`) - html_endh = []byte("

\n") -) - -// Emphasize and escape a line of text for HTML. URLs are converted into links; -// if the URL also appears in the words map, the link is taken from the map (if -// the corresponding map value is the empty string, the URL is not converted -// into a link). Go identifiers that appear in the words map are italicized; if -// the corresponding map value is not the empty string, it is considered a URL -// and the word is converted into a link. If nice is set, the remaining text's -// appearance is improved where it makes sense, such as replacing: -// -// `` -> “ -// '' -> ” -func emphasize(w io.Writer, line string, words map[string]string, nice bool) { - for { - m := matchRx.FindStringSubmatchIndex(line) - if m == nil { - break - } - // m >= 6 (two parenthesized sub-regexps in matchRx, 1st one is urlRx) - - // write text before match - commentEscape(w, line[0:m[0]], nice) - - // adjust match for URLs - match := line[m[0]:m[1]] - if strings.Contains(match, "://") { - m0, m1 := m[0], m[1] - for _, s := range []string{"()", "{}", "[]"} { - open, close := s[:1], s[1:] // E.g., "(" and ")" - // require opening parentheses before closing parentheses (#22285) - if i := strings.Index(match, close); i >= 0 && i < strings.Index(match, open) { - m1 = m0 + i - match = line[m0:m1] - } - // require balanced pairs of parentheses (#5043) - for i := 0; strings.Count(match, open) != strings.Count(match, close) && i < 10; i++ { - m1 = strings.LastIndexAny(line[:m1], s) - match = line[m0:m1] - } - } - if m1 != m[1] { - // redo matching with shortened line for correct indices - m = matchRx.FindStringSubmatchIndex(line[:m[0]+len(match)]) - } - } - - // analyze match - url := "" - italics := false - if words != nil { - url, italics = words[match] - } - if m[2] >= 0 { - // match against first parenthesized sub-regexp; must be match against urlRx - if !italics { - // no alternative URL in words list, use match instead - url = match - } - italics = false // don't italicize URLs - } - - // write match - if len(url) > 0 { - w.Write(html_a) - template.HTMLEscape(w, []byte(url)) - w.Write(html_aq) - } - if italics { - w.Write(html_i) - } - commentEscape(w, match, nice) - if italics { - w.Write(html_endi) - } - if len(url) > 0 { - w.Write(html_enda) - } - - // advance - line = line[m[1]:] - } - commentEscape(w, line, nice) -} - -func indentLen(s string) int { - i := 0 - for i < len(s) && (s[i] == ' ' || s[i] == '\t') { - i++ - } - return i -} - -func isBlank(s string) bool { - return len(s) == 0 || (len(s) == 1 && s[0] == '\n') -} - -func commonPrefix(a, b string) string { - i := 0 - for i < len(a) && i < len(b) && a[i] == b[i] { - i++ - } - return a[0:i] -} - -func unindent(block []string) { - if len(block) == 0 { - return - } - - // compute maximum common white prefix - prefix := block[0][0:indentLen(block[0])] - for _, line := range block { - if !isBlank(line) { - prefix = commonPrefix(prefix, line[0:indentLen(line)]) - } - } - n := len(prefix) - - // remove - for i, line := range block { - if !isBlank(line) { - block[i] = line[n:] - } - } -} - -// heading returns the trimmed line if it passes as a section heading; -// otherwise it returns the empty string. -func heading(line string) string { - line = strings.TrimSpace(line) - if len(line) == 0 { - return "" - } - - // a heading must start with an uppercase letter - r, _ := utf8.DecodeRuneInString(line) - if !unicode.IsLetter(r) || !unicode.IsUpper(r) { - return "" - } - - // it must end in a letter or digit: - r, _ = utf8.DecodeLastRuneInString(line) - if !unicode.IsLetter(r) && !unicode.IsDigit(r) { - return "" - } - - // exclude lines with illegal characters. we allow "()," - if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") { - return "" - } - - // allow "'" for possessive "'s" only - for b := line; ; { - var ok bool - if _, b, ok = strings.Cut(b, "'"); !ok { - break - } - if b != "s" && !strings.HasPrefix(b, "s ") { - return "" // ' not followed by s and then end-of-word - } - } - - // allow "." when followed by non-space - for b := line; ; { - var ok bool - if _, b, ok = strings.Cut(b, "."); !ok { - break - } - if b == "" || strings.HasPrefix(b, " ") { - return "" // not followed by non-space - } - } - - return line -} - -type op int - -const ( - opPara op = iota - opHead - opPre -) - -type block struct { - op op - lines []string -} - -var nonAlphaNumRx = lazyregexp.New(`[^a-zA-Z0-9]`) - -func anchorID(line string) string { - // Add a "hdr-" prefix to avoid conflicting with IDs used for package symbols. - return "hdr-" + nonAlphaNumRx.ReplaceAllString(line, "_") -} - // ToHTML converts comment text to formatted HTML. -// The comment was prepared by DocReader, -// so it is known not to have leading, trailing blank lines -// nor to have trailing spaces at the end of lines. -// The comment markers have already been removed. // -// Each span of unindented non-blank lines is converted into -// a single paragraph. There is one exception to the rule: a span that -// consists of a single line, is followed by another paragraph span, -// begins with a capital letter, and contains no punctuation -// other than parentheses and commas is formatted as a heading. +// Deprecated: ToHTML cannot identify documentation links +// in the doc comment, because they depend on knowing what +// package the text came from, which is not included in this API. // -// A span of indented lines is converted into a
 block,
-// with the common indent prefix removed.
+// Given the *[doc.Package] p where text was found,
+// ToHTML(w, text, nil) can be replaced by:
 //
-// URLs in the comment text are converted into links; if the URL also appears
-// in the words map, the link is taken from the map (if the corresponding map
-// value is the empty string, the URL is not converted into a link).
+//	w.Write(p.HTML(text))
 //
-// A pair of (consecutive) backticks (`) is converted to a unicode left quote (“), and a pair of (consecutive)
-// single quotes (') is converted to a unicode right quote (”).
+// which is in turn shorthand for:
 //
-// Go identifiers that appear in the words map are italicized; if the corresponding
-// map value is not the empty string, it is considered a URL and the word is converted
-// into a link.
+//	w.Write(p.Printer().HTML(p.Parser().Parse(text)))
+//
+// If words may be non-nil, the longer replacement is:
+//
+//	parser := p.Parser()
+//	parser.Words = words
+//	w.Write(p.Printer().HTML(parser.Parse(d)))
 func ToHTML(w io.Writer, text string, words map[string]string) {
-	for _, b := range blocks(text) {
-		switch b.op {
-		case opPara:
-			w.Write(html_p)
-			for _, line := range b.lines {
-				emphasize(w, line, words, true)
-			}
-			w.Write(html_endp)
-		case opHead:
-			w.Write(html_h)
-			id := ""
-			for _, line := range b.lines {
-				if id == "" {
-					id = anchorID(line)
-					w.Write([]byte(id))
-					w.Write(html_hq)
-				}
-				commentEscape(w, line, true)
-			}
-			if id == "" {
-				w.Write(html_hq)
-			}
-			w.Write(html_endh)
-		case opPre:
-			w.Write(html_pre)
-			for _, line := range b.lines {
-				emphasize(w, line, nil, false)
-			}
-			w.Write(html_endpre)
-		}
-	}
+	p := new(Package).Parser()
+	p.Words = words
+	d := p.Parse(text)
+	pr := new(comment.Printer)
+	w.Write(pr.HTML(d))
 }
 
-func blocks(text string) []block {
-	var (
-		out  []block
-		para []string
-
-		lastWasBlank   = false
-		lastWasHeading = false
-	)
-
-	close := func() {
-		if para != nil {
-			out = append(out, block{opPara, para})
-			para = nil
-		}
-	}
-
-	lines := strings.SplitAfter(text, "\n")
-	unindent(lines)
-	for i := 0; i < len(lines); {
-		line := lines[i]
-		if isBlank(line) {
-			// close paragraph
-			close()
-			i++
-			lastWasBlank = true
-			continue
-		}
-		if indentLen(line) > 0 {
-			// close paragraph
-			close()
-
-			// count indented or blank lines
-			j := i + 1
-			for j < len(lines) && (isBlank(lines[j]) || indentLen(lines[j]) > 0) {
-				j++
-			}
-			// but not trailing blank lines
-			for j > i && isBlank(lines[j-1]) {
-				j--
-			}
-			pre := lines[i:j]
-			i = j
-
-			unindent(pre)
-
-			// put those lines in a pre block
-			out = append(out, block{opPre, pre})
-			lastWasHeading = false
-			continue
-		}
-
-		if lastWasBlank && !lastWasHeading && i+2 < len(lines) &&
-			isBlank(lines[i+1]) && !isBlank(lines[i+2]) && indentLen(lines[i+2]) == 0 {
-			// current line is non-blank, surrounded by blank lines
-			// and the next non-blank line is not indented: this
-			// might be a heading.
-			if head := heading(line); head != "" {
-				close()
-				out = append(out, block{opHead, []string{head}})
-				i += 2
-				lastWasHeading = true
-				continue
-			}
-		}
-
-		// open paragraph
-		lastWasBlank = false
-		lastWasHeading = false
-		para = append(para, lines[i])
-		i++
-	}
-	close()
-
-	return out
-}
-
-// ToText prepares comment text for presentation in textual output.
-// It wraps paragraphs of text to width or fewer Unicode code points
-// and then prefixes each line with the indent. In preformatted sections
-// (such as program text), it prefixes each non-blank line with preIndent.
+// ToText converts comment text to formatted text.
 //
-// A pair of (consecutive) backticks (`) is converted to a unicode left quote (“), and a pair of (consecutive)
-// single quotes (') is converted to a unicode right quote (”).
-func ToText(w io.Writer, text string, indent, preIndent string, width int) {
-	l := lineWrapper{
-		out:    w,
-		width:  width,
-		indent: indent,
-	}
-	for _, b := range blocks(text) {
-		switch b.op {
-		case opPara:
-			// l.write will add leading newline if required
-			for _, line := range b.lines {
-				line = convertQuotes(line)
-				l.write(line)
-			}
-			l.flush()
-		case opHead:
-			w.Write(nl)
-			for _, line := range b.lines {
-				line = convertQuotes(line)
-				l.write(line + "\n")
-			}
-			l.flush()
-		case opPre:
-			w.Write(nl)
-			for _, line := range b.lines {
-				if isBlank(line) {
-					w.Write([]byte("\n"))
-				} else {
-					w.Write([]byte(preIndent))
-					w.Write([]byte(line))
-				}
-			}
-		}
+// Deprecated: ToText cannot identify documentation links
+// in the doc comment, because they depend on knowing what
+// package the text came from, which is not included in this API.
+//
+// Given the *[doc.Package] p where text was found,
+// ToText(w, text, "", "\t", 80) can be replaced by:
+//
+//	w.Write(p.Text(text))
+//
+// In the general case, ToText(w, text, prefix, codePrefix, width)
+// can be replaced by:
+//
+//	d := p.Parser().Parse(text)
+//	pr := p.Printer()
+//	pr.TextPrefix = prefix
+//	pr.TextCodePrefix = codePrefix
+//	pr.TextWidth = width
+//	w.Write(pr.Text(d))
+//
+// See the documentation for [Package.Text] and [comment.Printer.Text]
+// for more details.
+func ToText(w io.Writer, text string, prefix, codePrefix string, width int) {
+	d := new(Package).Parser().Parse(text)
+	pr := &comment.Printer{
+		TextPrefix:     prefix,
+		TextCodePrefix: codePrefix,
+		TextWidth:      width,
 	}
-}
-
-type lineWrapper struct {
-	out       io.Writer
-	printed   bool
-	width     int
-	indent    string
-	n         int
-	pendSpace int
-}
-
-var nl = []byte("\n")
-var space = []byte(" ")
-var prefix = []byte("// ")
-
-func (l *lineWrapper) write(text string) {
-	if l.n == 0 && l.printed {
-		l.out.Write(nl) // blank line before new paragraph
-	}
-	l.printed = true
-
-	needsPrefix := false
-	isComment := strings.HasPrefix(text, "//")
-	for _, f := range strings.Fields(text) {
-		w := utf8.RuneCountInString(f)
-		// wrap if line is too long
-		if l.n > 0 && l.n+l.pendSpace+w > l.width {
-			l.out.Write(nl)
-			l.n = 0
-			l.pendSpace = 0
-			needsPrefix = isComment && !strings.HasPrefix(f, "//")
-		}
-		if l.n == 0 {
-			l.out.Write([]byte(l.indent))
-		}
-		if needsPrefix {
-			l.out.Write(prefix)
-			needsPrefix = false
-		}
-		l.out.Write(space[:l.pendSpace])
-		l.out.Write([]byte(f))
-		l.n += l.pendSpace + w
-		l.pendSpace = 1
-	}
-}
-
-func (l *lineWrapper) flush() {
-	if l.n == 0 {
-		return
-	}
-	l.out.Write(nl)
-	l.pendSpace = 0
-	l.n = 0
+	w.Write(pr.Text(d))
 }
diff --git a/src/go/doc/comment/html.go b/src/go/doc/comment/html.go
new file mode 100644
index 0000000000..bc076f6a58
--- /dev/null
+++ b/src/go/doc/comment/html.go
@@ -0,0 +1,169 @@
+// Copyright 2022 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 comment
+
+import (
+	"bytes"
+	"fmt"
+	"strconv"
+)
+
+// An htmlPrinter holds the state needed for printing a Doc as HTML.
+type htmlPrinter struct {
+	*Printer
+	tight bool
+}
+
+// HTML returns an HTML formatting of the Doc.
+// See the [Printer] documentation for ways to customize the HTML output.
+func (p *Printer) HTML(d *Doc) []byte {
+	hp := &htmlPrinter{Printer: p}
+	var out bytes.Buffer
+	for _, x := range d.Content {
+		hp.block(&out, x)
+	}
+	return out.Bytes()
+}
+
+// block prints the block x to out.
+func (p *htmlPrinter) block(out *bytes.Buffer, x Block) {
+	switch x := x.(type) {
+	default:
+		fmt.Fprintf(out, "?%T", x)
+
+	case *Paragraph:
+		if !p.tight {
+			out.WriteString("

") + } + p.text(out, x.Text) + out.WriteString("\n") + + case *Heading: + out.WriteString("") + p.text(out, x.Text) + out.WriteString("\n") + + case *Code: + out.WriteString("

")
+		p.escape(out, x.Text)
+		out.WriteString("
\n") + + case *List: + kind := "ol>\n" + if x.Items[0].Number == "" { + kind = "ul>\n" + } + out.WriteString("<") + out.WriteString(kind) + next := "1" + for _, item := range x.Items { + out.WriteString("") + p.tight = !x.BlankBetween() + for _, blk := range item.Content { + p.block(out, blk) + } + p.tight = false + } + out.WriteString("= 0; i-- { + if b[i] < '9' { + b[i]++ + return string(b) + } + b[i] = '0' + } + return "1" + string(b) +} + +// text prints the text sequence x to out. +func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) { + for _, t := range x { + switch t := t.(type) { + case Plain: + p.escape(out, string(t)) + case Italic: + out.WriteString("") + p.escape(out, string(t)) + out.WriteString("") + case *Link: + out.WriteString(``) + p.text(out, t.Text) + out.WriteString("") + case *DocLink: + url := p.docLinkURL(t) + if url != "" { + out.WriteString(``) + } + p.text(out, t.Text) + if url != "" { + out.WriteString("") + } + } + } +} + +// escape prints s to out as plain text, +// escaping < & " ' and > to avoid being misinterpreted +// in larger HTML constructs. +func (p *htmlPrinter) escape(out *bytes.Buffer, s string) { + start := 0 + for i := 0; i < len(s); i++ { + switch s[i] { + case '<': + out.WriteString(s[start:i]) + out.WriteString("<") + start = i + 1 + case '&': + out.WriteString(s[start:i]) + out.WriteString("&") + start = i + 1 + case '"': + out.WriteString(s[start:i]) + out.WriteString(""") + start = i + 1 + case '\'': + out.WriteString(s[start:i]) + out.WriteString("'") + start = i + 1 + case '>': + out.WriteString(s[start:i]) + out.WriteString(">") + start = i + 1 + } + } + out.WriteString(s[start:]) +} diff --git a/src/go/doc/comment/markdown.go b/src/go/doc/comment/markdown.go new file mode 100644 index 0000000000..d8550f2e39 --- /dev/null +++ b/src/go/doc/comment/markdown.go @@ -0,0 +1,188 @@ +// Copyright 2022 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 comment + +import ( + "bytes" + "fmt" + "strings" +) + +// An mdPrinter holds the state needed for printing a Doc as Markdown. +type mdPrinter struct { + *Printer + headingPrefix string + raw bytes.Buffer +} + +// Markdown returns a Markdown formatting of the Doc. +// See the [Printer] documentation for ways to customize the Markdown output. +func (p *Printer) Markdown(d *Doc) []byte { + mp := &mdPrinter{ + Printer: p, + headingPrefix: strings.Repeat("#", p.headingLevel()) + " ", + } + + var out bytes.Buffer + for i, x := range d.Content { + if i > 0 { + out.WriteByte('\n') + } + mp.block(&out, x) + } + return out.Bytes() +} + +// block prints the block x to out. +func (p *mdPrinter) block(out *bytes.Buffer, x Block) { + switch x := x.(type) { + default: + fmt.Fprintf(out, "?%T", x) + + case *Paragraph: + p.text(out, x.Text) + out.WriteString("\n") + + case *Heading: + out.WriteString(p.headingPrefix) + p.text(out, x.Text) + if id := p.headingID(x); id != "" { + out.WriteString(" {#") + out.WriteString(id) + out.WriteString("}") + } + out.WriteString("\n") + + case *Code: + md := x.Text + for md != "" { + var line string + line, md, _ = strings.Cut(md, "\n") + if line != "" { + out.WriteString("\t") + out.WriteString(line) + } + out.WriteString("\n") + } + + case *List: + loose := x.BlankBetween() + for i, item := range x.Items { + if i > 0 && loose { + out.WriteString("\n") + } + if n := item.Number; n != "" { + out.WriteString(" ") + out.WriteString(n) + out.WriteString(". ") + } else { + out.WriteString(" - ") // SP SP - SP + } + for i, blk := range item.Content { + const fourSpace = " " + if i > 0 { + out.WriteString("\n" + fourSpace) + } + p.text(out, blk.(*Paragraph).Text) + out.WriteString("\n") + } + } + } +} + +// text prints the text sequence x to out. +func (p *mdPrinter) text(out *bytes.Buffer, x []Text) { + p.raw.Reset() + p.rawText(&p.raw, x) + line := bytes.TrimSpace(p.raw.Bytes()) + if len(line) == 0 { + return + } + switch line[0] { + case '+', '-', '*', '#': + // Escape what would be the start of an unordered list or heading. + out.WriteByte('\\') + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i := 1 + for i < len(line) && '0' <= line[i] && line[i] <= '9' { + i++ + } + if i < len(line) && (line[i] == '.' || line[i] == ')') { + // Escape what would be the start of an ordered list. + out.Write(line[:i]) + out.WriteByte('\\') + line = line[i:] + } + } + out.Write(line) +} + +// rawText prints the text sequence x to out, +// without worrying about escaping characters +// that have special meaning at the start of a Markdown line. +func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) { + for _, t := range x { + switch t := t.(type) { + case Plain: + p.escape(out, string(t)) + case Italic: + out.WriteString("*") + p.escape(out, string(t)) + out.WriteString("*") + case *Link: + out.WriteString("[") + p.rawText(out, t.Text) + out.WriteString("](") + out.WriteString(t.URL) + out.WriteString(")") + case *DocLink: + url := p.docLinkURL(t) + if url != "" { + out.WriteString("[") + } + p.rawText(out, t.Text) + if url != "" { + out.WriteString("](") + url = strings.ReplaceAll(url, "(", "%28") + url = strings.ReplaceAll(url, ")", "%29") + out.WriteString(url) + out.WriteString(")") + } + } + } +} + +// escape prints s to out as plain text, +// escaping special characters to avoid being misinterpreted +// as Markdown markup sequences. +func (p *mdPrinter) escape(out *bytes.Buffer, s string) { + start := 0 + for i := 0; i < len(s); i++ { + switch s[i] { + case '\n': + // Turn all \n into spaces, for a few reasons: + // - Avoid introducing paragraph breaks accidentally. + // - Avoid the need to reindent after the newline. + // - Avoid problems with Markdown renderers treating + // every mid-paragraph newline as a
. + out.WriteString(s[start:i]) + out.WriteByte(' ') + start = i + 1 + continue + case '`', '_', '*', '[', '<', '\\': + // Not all of these need to be escaped all the time, + // but is valid and easy to do so. + // We assume the Markdown is being passed to a + // Markdown renderer, not edited by a person, + // so it's fine to have escapes that are not strictly + // necessary in some cases. + out.WriteString(s[start:i]) + out.WriteByte('\\') + out.WriteByte(s[i]) + start = i + 1 + } + } + out.WriteString(s[start:]) +} diff --git a/src/go/doc/comment/mkstd.sh b/src/go/doc/comment/mkstd.sh new file mode 100755 index 0000000000..c9dee8c55e --- /dev/null +++ b/src/go/doc/comment/mkstd.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Copyright 2022 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 could be a good use for embed but go/doc/comment +# is built into the bootstrap go command, so it can't use embed. +# Also not using embed lets us emit a string array directly +# and avoid init-time work. + +( +echo "// Copyright 2022 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. + +// Code generated by 'go generate' DO NOT EDIT. +//go:generate ./mkstd.sh + +package comment + +var stdPkgs = []string{" +go list std | grep -v / | sort | sed 's/.*/"&",/' +echo "}" +) | gofmt >std.go.tmp && mv std.go.tmp std.go diff --git a/src/go/doc/comment/old_test.go b/src/go/doc/comment/old_test.go new file mode 100644 index 0000000000..944f94d16d --- /dev/null +++ b/src/go/doc/comment/old_test.go @@ -0,0 +1,80 @@ +// Copyright 2011 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. + +// These tests are carried forward from the old go/doc implementation. + +package comment + +import "testing" + +var oldHeadingTests = []struct { + line string + ok bool +}{ + {"Section", true}, + {"A typical usage", true}, + {"ΔΛΞ is Greek", true}, + {"Foo 42", true}, + {"", false}, + {"section", false}, + {"A typical usage:", false}, + {"This code:", false}, + {"δ is Greek", false}, + {"Foo §", false}, + {"Fermat's Last Sentence", true}, + {"Fermat's", true}, + {"'sX", false}, + {"Ted 'Too' Bar", false}, + {"Use n+m", false}, + {"Scanning:", false}, + {"N:M", false}, +} + +func TestIsOldHeading(t *testing.T) { + for _, tt := range oldHeadingTests { + if isOldHeading(tt.line, []string{"Text.", "", tt.line, "", "Text."}, 2) != tt.ok { + t.Errorf("isOldHeading(%q) = %v, want %v", tt.line, !tt.ok, tt.ok) + } + } +} + +var autoURLTests = []struct { + in, out string +}{ + {"", ""}, + {"http://[::1]:8080/foo.txt", "http://[::1]:8080/foo.txt"}, + {"https://www.google.com) after", "https://www.google.com"}, + {"https://www.google.com:30/x/y/z:b::c. After", "https://www.google.com:30/x/y/z:b::c"}, + {"http://www.google.com/path/:;!-/?query=%34b#093124", "http://www.google.com/path/:;!-/?query=%34b#093124"}, + {"http://www.google.com/path/:;!-/?query=%34bar#093124", "http://www.google.com/path/:;!-/?query=%34bar#093124"}, + {"http://www.google.com/index.html! After", "http://www.google.com/index.html"}, + {"http://www.google.com/", "http://www.google.com/"}, + {"https://www.google.com/", "https://www.google.com/"}, + {"http://www.google.com/path.", "http://www.google.com/path"}, + {"http://en.wikipedia.org/wiki/Camellia_(cipher)", "http://en.wikipedia.org/wiki/Camellia_(cipher)"}, + {"http://www.google.com/)", "http://www.google.com/"}, + {"http://gmail.com)", "http://gmail.com"}, + {"http://gmail.com))", "http://gmail.com"}, + {"http://gmail.com ((http://gmail.com)) ()", "http://gmail.com"}, + {"http://example.com/ quux!", "http://example.com/"}, + {"http://example.com/%2f/ /world.", "http://example.com/%2f/"}, + {"http: ipsum //host/path", ""}, + {"javascript://is/not/linked", ""}, + {"http://foo", "http://foo"}, + {"https://www.example.com/person/][Person Name]]", "https://www.example.com/person/"}, + {"http://golang.org/)", "http://golang.org/"}, + {"http://golang.org/hello())", "http://golang.org/hello()"}, + {"http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD", "http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD"}, + {"https://foo.bar/bal/x(])", "https://foo.bar/bal/x"}, // inner ] causes (]) to be cut off from URL + {"http://bar(])", "http://bar"}, // same +} + +func TestAutoURL(t *testing.T) { + for _, tt := range autoURLTests { + url, ok := autoURL(tt.in) + if url != tt.out || ok != (tt.out != "") { + t.Errorf("autoURL(%q) = %q, %v, want %q, %v", tt.in, url, ok, tt.out, tt.out != "") + } + } +} diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go new file mode 100644 index 0000000000..83b37c32c5 --- /dev/null +++ b/src/go/doc/comment/parse.go @@ -0,0 +1,1151 @@ +// Copyright 2022 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 comment + +import ( + "sort" + "strings" + "unicode" + "unicode/utf8" +) + +// A Doc is a parsed Go doc comment. +type Doc struct { + // Content is the sequence of content blocks in the comment. + Content []Block + + // Links is the link definitions in the comment. + Links []*LinkDef +} + +// A LinkDef is a single link definition. +type LinkDef struct { + Text string // the link text + URL string // the link URL + Used bool // whether the comment uses the definition +} + +// A Block is block-level content in a doc comment, +// one of [*Code], [*Heading], [*List], or [*Paragraph]. +type Block interface { + block() +} + +// A Heading is a doc comment heading. +type Heading struct { + Text []Text // the heading text +} + +func (*Heading) block() {} + +// A List is a numbered or bullet list. +// Lists are always non-empty: len(Items) > 0. +// In a numbered list, every Items[i].Number is a non-empty string. +// In a bullet list, every Items[i].Number is an empty string. +type List struct { + // Items is the list items. + Items []*ListItem + + // ForceBlankBefore indicates that the list must be + // preceded by a blank line when reformatting the comment, + // overriding the usual conditions. See the BlankBefore method. + // + // The comment parser sets ForceBlankBefore for any list + // that is preceded by a blank line, to make sure + // the blank line is preserved when printing. + ForceBlankBefore bool + + // ForceBlankBetween indicates that list items must be + // separated by blank lines when reformatting the comment, + // overriding the usual conditions. See the BlankBetween method. + // + // The comment parser sets ForceBlankBetween for any list + // that has a blank line between any two of its items, to make sure + // the blank lines are preserved when printing. + ForceBlankBetween bool +} + +func (*List) block() {} + +// BlankBefore reports whether a reformatting of the comment +// should include a blank line before the list. +// The default rule is the same as for [BlankBetween]: +// if the list item content contains any blank lines +// (meaning at least one item has multiple paragraphs) +// then the list itself must be preceded by a blank line. +// A preceding blank line can be forced by setting [List].ForceBlankBefore. +func (l *List) BlankBefore() bool { + return l.ForceBlankBefore || l.BlankBetween() +} + +// BlankBetween reports whether a reformatting of the comment +// should include a blank line between each pair of list items. +// The default rule is that if the list item content contains any blank lines +// (meaning at least one item has multiple paragraphs) +// then list items must themselves be separated by blank lines. +// Blank line separators can be forced by setting [List].ForceBlankBetween. +func (l *List) BlankBetween() bool { + if l.ForceBlankBetween { + return true + } + for _, item := range l.Items { + if len(item.Content) != 1 { + // Unreachable for parsed comments today, + // since the only way to get multiple item.Content + // is multiple paragraphs, which must have been + // separated by a blank line. + return true + } + } + return false +} + +// A ListItem is a single item in a numbered or bullet list. +type ListItem struct { + // Number is a decimal string in a numbered list + // or an empty string in a bullet list. + Number string // "1", "2", ...; "" for bullet list + + // Content is the list content. + // Currently, restrictions in the parser and printer + // require every element of Content to be a *Paragraph. + Content []Block // Content of this item. +} + +// A Paragraph is a paragraph of text. +type Paragraph struct { + Text []Text +} + +func (*Paragraph) block() {} + +// A Code is a preformatted code block. +type Code struct { + // Text is the preformatted text, ending with a newline character. + // It may be multiple lines, each of which ends with a newline character. + // It is never empty, nor does it start or end with a blank line. + Text string +} + +func (*Code) block() {} + +// A Text is text-level content in a doc comment, +// one of [Plain], [Italic], [*Link], or [*DocLink]. +type Text interface { + text() +} + +// A Plain is a string rendered as plain text (not italicized). +type Plain string + +func (Plain) text() {} + +// An Italic is a string rendered as italicized text. +type Italic string + +func (Italic) text() {} + +// A Link is a link to a specific URL. +type Link struct { + Auto bool // is this an automatic (implicit) link of a literal URL? + Text []Text // text of link + URL string // target URL of link +} + +func (*Link) text() {} + +// A DocLink is a link to documentation for a Go package or symbol. +type DocLink struct { + Text []Text // text of link + + // ImportPath, Recv, and Name identify the Go package or symbol + // that is the link target. The potential combinations of + // non-empty fields are: + // - ImportPath: a link to another package + // - ImportPath, Name: a link to a const, func, type, or var in another package + // - ImportPath, Recv, Name: a link to a method in another package + // - Name: a link to a const, func, type, or var in this package + // - Recv, Name: a link to a method in this package + ImportPath string // import path + Recv string // receiver type, without any pointer star, for methods + Name string // const, func, type, var, or method name +} + +func (*DocLink) text() {} + +// A Parser is a doc comment parser. +// The fields in the struct can be filled in before calling Parse +// in order to customize the details of the parsing process. +type Parser struct { + // Words is a map of Go identifier words that + // should be italicized and potentially linked. + // If Words[w] is the empty string, then the word w + // is only italicized. Otherwise it is linked, using + // Words[w] as the link target. + // Words corresponds to the [go/doc.ToHTML] words parameter. + Words map[string]string + + // LookupPackage resolves a package name to an import path. + // + // If LookupPackage(name) returns ok == true, then [name] + // (or [name.Sym] or [name.Sym.Method]) + // is considered a documentation link to importPath's package docs. + // It is valid to return "", true, in which case name is considered + // to refer to the current package. + // + // If LookupPackage(name) returns ok == false, + // then [name] (or [name.Sym] or [name.Sym.Method]) + // will not be considered a documentation link, + // except in the case where name is the full (but single-element) import path + // of a package in the standard library, such as in [math] or [io.Reader]. + // LookupPackage is still called for such names, + // in order to permit references to imports of other packages + // with the same package names. + // + // Setting LookupPackage to nil is equivalent to setting it to + // a function that always returns "", false. + LookupPackage func(name string) (importPath string, ok bool) + + // LookupSym reports whether a symbol name or method name + // exists in the current package. + // + // If LookupSym("", "Name") returns true, then [Name] + // is considered a documentation link for a const, func, type, or var. + // + // Similarly, if LookupSym("Recv", "Name") returns true, + // then [Recv.Name] is considered a documentation link for + // type Recv's method Name. + // + // Setting LookupSym to nil is equivalent to setting it to a function + // that always returns false. + LookupSym func(recv, name string) (ok bool) +} + +// parseDoc is parsing state for a single doc comment. +type parseDoc struct { + *Parser + *Doc + links map[string]*LinkDef + lines []string + lookupSym func(recv, name string) bool +} + +// lookupPkg is called to look up the pkg in [pkg], [pkg.Name], and [pkg.Name.Recv]. +// If pkg has a slash, it is assumed to be the full import path and is returned with ok = true. +// +// Otherwise, pkg is probably a simple package name like "rand" (not "crypto/rand" or "math/rand"). +// d.LookupPackage provides a way for the caller to allow resolving such names with reference +// to the imports in the surrounding package. +// +// There is one collision between these two cases: single-element standard library names +// like "math" are full import paths but don't contain slashes. We let d.LookupPackage have +// the first chance to resolve it, in case there's a different package imported as math, +// and otherwise we refer to a built-in list of single-element standard library package names. +func (d *parseDoc) lookupPkg(pkg string) (importPath string, ok bool) { + if strings.Contains(pkg, "/") { // assume a full import path + if validImportPath(pkg) { + return pkg, true + } + return "", false + } + if d.LookupPackage != nil { + // Give LookupPackage a chance. + if path, ok := d.LookupPackage(pkg); ok { + return path, true + } + } + return DefaultLookupPackage(pkg) +} + +func isStdPkg(path string) bool { + // TODO(rsc): Use sort.Find. + i := sort.Search(len(stdPkgs), func(i int) bool { return stdPkgs[i] >= path }) + return i < len(stdPkgs) && stdPkgs[i] == path +} + +// DefaultLookupPackage is the default package lookup +// function, used when [Parser].LookupPackage is nil. +// It recognizes names of the packages from the standard +// library with single-element import paths, such as math, +// which would otherwise be impossible to name. +// +// Note that the go/doc package provides a more sophisticated +// lookup based on the imports used in the current package. +func DefaultLookupPackage(name string) (importPath string, ok bool) { + if isStdPkg(name) { + return name, true + } + return "", false +} + +// Parse parses the doc comment text and returns the *Doc form. +// Comment markers (/* // and */) in the text must have already been removed. +func (p *Parser) Parse(text string) *Doc { + lines := unindent(strings.Split(text, "\n")) + d := &parseDoc{ + Parser: p, + Doc: new(Doc), + links: make(map[string]*LinkDef), + lines: lines, + lookupSym: func(recv, name string) bool { return false }, + } + if p.LookupSym != nil { + d.lookupSym = p.LookupSym + } + + // First pass: break into block structure and collect known links. + // The text is all recorded as Plain for now. + // TODO: Break into actual block structure. + didHeading := false + all := lines + for len(lines) > 0 { + line := lines[0] + n := len(lines) + var b Block + + switch { + case line == "": + // emit nothing + + case isList(line): + prevWasBlank := len(lines) < len(all) && all[len(all)-len(lines)-1] == "" + b, lines = d.list(lines, prevWasBlank) + + case isIndented(line): + b, lines = d.code(lines) + + case (len(lines) == 1 || lines[1] == "") && !didHeading && isOldHeading(line, all, len(all)-n): + b = d.oldHeading(line) + didHeading = true + + case (len(lines) == 1 || lines[1] == "") && isHeading(line): + b = d.heading(line) + didHeading = true + + default: + b, lines = d.paragraph(lines) + didHeading = false + } + + if b != nil { + d.Content = append(d.Content, b) + } + if len(lines) == n { + lines = lines[1:] + } + } + + // Second pass: interpret all the Plain text now that we know the links. + for _, b := range d.Content { + switch b := b.(type) { + case *Paragraph: + b.Text = d.parseLinkedText(string(b.Text[0].(Plain))) + } + } + + return d.Doc +} + +// unindent removes any common space/tab prefix +// from each line in lines, returning a copy of lines in which +// those prefixes have been trimmed from each line. +func unindent(lines []string) []string { + // Trim leading and trailing blank lines. + for len(lines) > 0 && isBlank(lines[0]) { + lines = lines[1:] + } + for len(lines) > 0 && isBlank(lines[len(lines)-1]) { + lines = lines[:len(lines)-1] + } + if len(lines) == 0 { + return nil + } + + // Compute and remove common indentation. + prefix := leadingSpace(lines[0]) + for _, line := range lines[1:] { + if !isBlank(line) { + prefix = commonPrefix(prefix, leadingSpace(line)) + } + } + + out := make([]string, len(lines)) + for i, line := range lines { + line = strings.TrimPrefix(line, prefix) + if strings.TrimSpace(line) == "" { + line = "" + } + out[i] = line + } + for len(out) > 0 && out[0] == "" { + out = out[1:] + } + for len(out) > 0 && out[len(out)-1] == "" { + out = out[:len(out)-1] + } + return out +} + +// isBlank reports whether s is a blank line. +func isBlank(s string) bool { + return len(s) == 0 || (len(s) == 1 && s[0] == '\n') +} + +// commonPrefix returns the longest common prefix of a and b. +func commonPrefix(a, b string) string { + i := 0 + for i < len(a) && i < len(b) && a[i] == b[i] { + i++ + } + return a[0:i] +} + +// leadingSpace returns the longest prefix of s consisting of spaces and tabs. +func leadingSpace(s string) string { + i := 0 + for i < len(s) && (s[i] == ' ' || s[i] == '\t') { + i++ + } + return s[:i] +} + +// isOldHeading reports whether line is an old-style section heading. +// line is all[off]. +func isOldHeading(line string, all []string, off int) bool { + if off <= 0 || all[off-1] != "" || off+2 >= len(all) || all[off+1] != "" || leadingSpace(all[off+2]) != "" { + return false + } + + line = strings.TrimSpace(line) + + // a heading must start with an uppercase letter + r, _ := utf8.DecodeRuneInString(line) + if !unicode.IsLetter(r) || !unicode.IsUpper(r) { + return false + } + + // it must end in a letter or digit: + r, _ = utf8.DecodeLastRuneInString(line) + if !unicode.IsLetter(r) && !unicode.IsDigit(r) { + return false + } + + // exclude lines with illegal characters. we allow "()," + if strings.ContainsAny(line, ";:!?+*/=[]{}_^°&§~%#@<\">\\") { + return false + } + + // allow "'" for possessive "'s" only + for b := line; ; { + var ok bool + if _, b, ok = strings.Cut(b, "'"); !ok { + break + } + if b != "s" && !strings.HasPrefix(b, "s ") { + return false // ' not followed by s and then end-of-word + } + } + + // allow "." when followed by non-space + for b := line; ; { + var ok bool + if _, b, ok = strings.Cut(b, "."); !ok { + break + } + if b == "" || strings.HasPrefix(b, " ") { + return false // not followed by non-space + } + } + + return true +} + +// oldHeading returns the *Heading for the given old-style section heading line. +func (d *parseDoc) oldHeading(line string) Block { + return &Heading{Text: []Text{Plain(strings.TrimSpace(line))}} +} + +// isHeading reports whether line is a new-style section heading. +func isHeading(line string) bool { + return len(line) >= 2 && + line[0] == '#' && + (line[1] == ' ' || line[1] == '\t') && + strings.TrimSpace(line) != "#" +} + +// heading returns the *Heading for the given new-style section heading line. +func (d *parseDoc) heading(line string) Block { + return &Heading{Text: []Text{Plain(strings.TrimSpace(line[1:]))}} +} + +// code returns a code block built from the indented text +// at the start of lines, along with the remainder of the lines. +// If there is no indented text at the start, or if the indented +// text consists only of empty lines, code returns a nil Block. +func (d *parseDoc) code(lines []string) (b Block, rest []string) { + lines, rest = indented(lines) + body := unindent(lines) + if len(body) == 0 { + return nil, rest + } + body = append(body, "") // to get final \n from Join + return &Code{Text: strings.Join(body, "\n")}, rest +} + +// isIndented reports whether the line is indented, +// meaning it starts with a space or tab. +func isIndented(line string) bool { + return line != "" && (line[0] == ' ' || line[0] == '\t') +} + +// indented splits lines into an initial indented section +// and the remaining lines, returning the two halves. +func indented(lines []string) (indented, rest []string) { + // Blank lines mid-run are OK, but not at the end. + i := 0 + for i < len(lines) && (isIndented(lines[i]) || lines[i] == "") { + i++ + } + for i > 0 && lines[i-1] == "" { + i-- + } + return lines[:i], lines[i:] +} + +// paragraph returns a paragraph block built from the +// unindented text at the start of lines, along with the remainder of the lines. +// If there is no unindented text at the start of lines, +// then paragraph returns a nil Block. +func (d *parseDoc) paragraph(lines []string) (b Block, rest []string) { + // Paragraph is interrupted by any indented line, + // which is either a list or a code block, + // and of course by a blank line. + // It is not interrupted by a # line - headings must stand alone. + i := 0 + for i < len(lines) && lines[i] != "" && !isIndented(lines[i]) { + i++ + } + lines, rest = lines[:i], lines[i:] + if len(lines) == 0 { + return nil, rest + } + + // Is this a block of known links? Handle. + var defs []*LinkDef + for _, line := range lines { + def, ok := parseLink(line) + if !ok { + goto NoDefs + } + defs = append(defs, def) + } + for _, def := range defs { + d.Links = append(d.Links, def) + if d.links[def.Text] == nil { + d.links[def.Text] = def + } + } + return nil, rest +NoDefs: + + return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}}, rest +} + +// parseLink parses a single link definition line: +// +// [text]: url +// +// It returns the link definition and whether the line was well formed. +func parseLink(line string) (*LinkDef, bool) { + if line == "" || line[0] != '[' { + return nil, false + } + i := strings.Index(line, "]:") + if i < 0 || i+3 >= len(line) || (line[i+2] != ' ' && line[i+2] != '\t') { + return nil, false + } + + text := line[1:i] + url := strings.TrimSpace(line[i+3:]) + j := strings.Index(url, "://") + if j < 0 || !isScheme(url[:j]) { + return nil, false + } + + // Line has right form and has valid scheme://. + // That's good enough for us - we are not as picky + // about the characters beyond the :// as we are + // when extracting inline URLs from text. + return &LinkDef{Text: text, URL: url}, true +} + +// list returns a list built from the indented text at the start of lines, +// using forceBlankBefore as the value of the List's ForceBlankBefore field. +// The caller is responsible for ensuring that the first line of lines +// satisfies isList. +// list returns the *List as a Block along with the remaining lines. +func (d *parseDoc) list(lines []string, forceBlankBefore bool) (b Block, rest []string) { + lines, rest = indented(lines) + + num, _, _ := listMarker(lines[0]) + var ( + list *List = &List{ForceBlankBefore: forceBlankBefore} + item *ListItem + text []string + ) + flush := func() { + if item != nil { + if para, _ := d.paragraph(text); para != nil { + item.Content = append(item.Content, para) + } + } + text = nil + } + + for _, line := range lines { + if n, after, ok := listMarker(line); ok && (n != "") == (num != "") { + // start new list item + flush() + + item = &ListItem{Number: n} + list.Items = append(list.Items, item) + line = after + } + line = strings.TrimSpace(line) + if line == "" { + list.ForceBlankBetween = true + flush() + continue + } + text = append(text, strings.TrimSpace(line)) + } + flush() + return list, rest +} + +// listMarker parses the line as an indented line beginning with a list marker. +// If it can do that, it returns the numeric marker ("" for a bullet list), +// the rest of the line, and ok == true. +// Otherwise, it returns "", "", false. +func listMarker(line string) (num, rest string, ok bool) { + if !isIndented(line) { + return "", "", false + } + line = strings.TrimSpace(line) + if line == "" { + return "", "", false + } + + // Can we find a marker? + if r, n := utf8.DecodeRuneInString(line); r == '•' || r == '*' || r == '+' || r == '-' { + num, rest = "", line[n:] + } else if '0' <= line[0] && line[0] <= '9' { + n := 1 + for n < len(line) && '0' <= line[n] && line[n] <= '9' { + n++ + } + if n >= len(line) || (line[n] != '.' && line[n] != ')') { + return "", "", false + } + num, rest = line[:n], line[n+1:] + } else { + return "", "", false + } + + if !isIndented(rest) || strings.TrimSpace(rest) == "" { + return "", "", false + } + + return num, rest, true +} + +// isList reports whether the line is the first line of a list, +// meaning is indented and starts with a list marker. +func isList(line string) bool { + _, _, ok := listMarker(line) + return ok +} + +// parseLinkedText parses text that is allowed to contain explicit links, +// such as [math.Sin] or [Go home page], into a slice of Text items. +// +// A “pkg” is only assumed to be a full import path if it starts with +// a domain name (a path element with a dot) or is one of the packages +// from the standard library (“[os]”, “[encoding/json]”, and so on). +// To avoid problems with maps, generics, and array types, doc links +// must be both preceded and followed by punctuation, spaces, tabs, +// or the start or end of a line. An example problem would be treating +// map[ast.Expr]TypeAndValue as containing a link. +func (d *parseDoc) parseLinkedText(text string) []Text { + var out []Text + wrote := 0 + flush := func(i int) { + if wrote < i { + out = d.parseText(out, text[wrote:i], true) + wrote = i + } + } + + start := -1 + var buf []byte + for i := 0; i < len(text); i++ { + c := text[i] + if c == '\n' || c == '\t' { + c = ' ' + } + switch c { + case '[': + start = i + case ']': + if start >= 0 { + if def, ok := d.links[string(buf)]; ok { + def.Used = true + flush(start) + out = append(out, &Link{ + Text: d.parseText(nil, text[start+1:i], false), + URL: def.URL, + }) + wrote = i + 1 + } else if link, ok := d.docLink(text[start+1:i], text[:start], text[i+1:]); ok { + flush(start) + link.Text = d.parseText(nil, text[start+1:i], false) + out = append(out, link) + wrote = i + 1 + } + } + start = -1 + buf = buf[:0] + } + if start >= 0 && i != start { + buf = append(buf, c) + } + } + + flush(len(text)) + return out +} + +// docLink parses text, which was found inside [ ] brackets, +// as a doc link if possible, returning the DocLink and ok == true +// or else nil, false. +// The before and after strings are the text before the [ and after the ] +// on the same line. Doc links must be preceded and followed by +// punctuation, spaces, tabs, or the start or end of a line. +func (d *parseDoc) docLink(text, before, after string) (link *DocLink, ok bool) { + if before != "" { + r, _ := utf8.DecodeLastRuneInString(before) + if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' { + return nil, false + } + } + if after != "" { + r, _ := utf8.DecodeRuneInString(after) + if !unicode.IsPunct(r) && r != ' ' && r != '\t' && r != '\n' { + return nil, false + } + } + if strings.HasPrefix(text, "*") { + text = text[1:] + } + pkg, name, ok := splitDocName(text) + var recv string + if ok { + pkg, recv, _ = splitDocName(pkg) + } + if pkg != "" { + if pkg, ok = d.lookupPkg(pkg); !ok { + return nil, false + } + } else { + if ok = d.lookupSym(recv, name); !ok { + return nil, false + } + } + link = &DocLink{ + ImportPath: pkg, + Recv: recv, + Name: name, + } + return link, true +} + +// If text is of the form before.Name, where Name is a capitalized Go identifier, +// then splitDocName returns before, name, true. +// Otherwise it returns text, "", false. +func splitDocName(text string) (before, name string, foundDot bool) { + i := strings.LastIndex(text, ".") + name = text[i+1:] + if !isName(name) { + return text, "", false + } + if i >= 0 { + before = text[:i] + } + return before, name, true +} + +// parseText parses s as text and returns the result of appending +// those parsed Text elements to out. +// parseText does not handle explicit links like [math.Sin] or [Go home page]: +// those are handled by parseLinkedText. +// If autoLink is true, then parseText recognizes URLs and words from d.Words +// and converts those to links as appropriate. +func (d *parseDoc) parseText(out []Text, s string, autoLink bool) []Text { + var w strings.Builder + wrote := 0 + writeUntil := func(i int) { + w.WriteString(s[wrote:i]) + wrote = i + } + flush := func(i int) { + writeUntil(i) + if w.Len() > 0 { + out = append(out, Plain(w.String())) + w.Reset() + } + } + for i := 0; i < len(s); { + t := s[i:] + if autoLink { + if url, ok := autoURL(t); ok { + flush(i) + // Note: The old comment parser would look up the URL in words + // and replace the target with words[URL] if it was non-empty. + // That would allow creating links that display as one URL but + // when clicked go to a different URL. Not sure what the point + // of that is, so we're not doing that lookup here. + out = append(out, &Link{Auto: true, Text: []Text{Plain(url)}, URL: url}) + i += len(url) + wrote = i + continue + } + if id, ok := ident(t); ok { + url, italics := d.Words[id] + if !italics { + i += len(id) + continue + } + flush(i) + if url == "" { + out = append(out, Italic(id)) + } else { + out = append(out, &Link{Auto: true, Text: []Text{Italic(id)}, URL: url}) + } + i += len(id) + wrote = i + continue + } + } + switch { + case strings.HasPrefix(t, "``"): + writeUntil(i) + w.WriteRune('“') + i += 2 + wrote = i + case strings.HasPrefix(t, "''"): + writeUntil(i) + w.WriteRune('”') + i += 2 + wrote = i + default: + i++ + } + } + flush(len(s)) + return out +} + +// autoURL checks whether s begins with a URL that should be hyperlinked. +// If so, it returns the URL, which is a prefix of s, and ok == true. +// Otherwise it returns "", false. +// The caller should skip over the first len(url) bytes of s +// before further processing. +func autoURL(s string) (url string, ok bool) { + // Find the ://. Fast path to pick off non-URL, + // since we call this at every position in the string. + // The shortest possible URL is ftp://x, 7 bytes. + var i int + switch { + case len(s) < 7: + return "", false + case s[3] == ':': + i = 3 + case s[4] == ':': + i = 4 + case s[5] == ':': + i = 5 + case s[6] == ':': + i = 6 + default: + return "", false + } + if i+3 > len(s) || s[i:i+3] != "://" { + return "", false + } + + // Check valid scheme. + if !isScheme(s[:i]) { + return "", false + } + + // Scan host part. Must have at least one byte, + // and must start and end in non-punctuation. + i += 3 + if i >= len(s) || !isHost(s[i]) || isPunct(s[i]) { + return "", false + } + i++ + end := i + for i < len(s) && isHost(s[i]) { + if !isPunct(s[i]) { + end = i + 1 + } + i++ + } + i = end + + // At this point we are definitely returning a URL (scheme://host). + // We just have to find the longest path we can add to it. + // Heuristics abound. + // We allow parens, braces, and brackets, + // but only if they match (#5043, #22285). + // We allow .,:;?! in the path but not at the end, + // to avoid end-of-sentence punctuation (#18139, #16565). + stk := []byte{} + end = i +Path: + for ; i < len(s); i++ { + if isPunct(s[i]) { + continue + } + if !isPath(s[i]) { + break + } + switch s[i] { + case '(': + stk = append(stk, ')') + case '{': + stk = append(stk, '}') + case '[': + stk = append(stk, ']') + case ')', '}', ']': + if len(stk) == 0 || stk[len(stk)-1] != s[i] { + break Path + } + stk = stk[:len(stk)-1] + } + if len(stk) == 0 { + end = i + 1 + } + } + + return s[:end], true +} + +// isScheme reports whether s is a recognized URL scheme. +// Note that if strings of new length (beyond 3-7) +// are added here, the fast path at the top of autoURL will need updating. +func isScheme(s string) bool { + switch s { + case "file", + "ftp", + "gopher", + "http", + "https", + "mailto", + "nntp": + return true + } + return false +} + +// isHost reports whether c is a byte that can appear in a URL host, +// like www.example.com or user@[::1]:8080 +func isHost(c byte) bool { + // mask is a 128-bit bitmap with 1s for allowed bytes, + // so that the byte c can be tested with a shift and an and. + // If c > 128, then 1<>64)) != 0 +} + +// isPunct reports whether c is a punctuation byte that can appear +// inside a path but not at the end. +func isPunct(c byte) bool { + // mask is a 128-bit bitmap with 1s for allowed bytes, + // so that the byte c can be tested with a shift and an and. + // If c > 128, then 1<>64)) != 0 +} + +// isPath reports whether c is a (non-punctuation) path byte. +func isPath(c byte) bool { + // mask is a 128-bit bitmap with 1s for allowed bytes, + // so that the byte c can be tested with a shift and an and. + // If c > 128, then 1<>64)) != 0 +} + +// isName reports whether s is a capitalized Go identifier (like Name). +func isName(s string) bool { + t, ok := ident(s) + if !ok || t != s { + return false + } + r, _ := utf8.DecodeRuneInString(s) + return unicode.IsUpper(r) +} + +// ident checks whether s begins with a Go identifier. +// If so, it returns the identifier, which is a prefix of s, and ok == true. +// Otherwise it returns "", false. +// The caller should skip over the first len(id) bytes of s +// before further processing. +func ident(s string) (id string, ok bool) { + // Scan [\pL_][\pL_0-9]* + n := 0 + for n < len(s) { + if c := s[n]; c < utf8.RuneSelf { + if isIdentASCII(c) && (n > 0 || c < '0' || c > '9') { + n++ + continue + } + break + } + r, nr := utf8.DecodeRuneInString(s[n:]) + if unicode.IsLetter(r) { + n += nr + continue + } + break + } + return s[:n], n > 0 +} + +// isIdentASCII reports whether c is an ASCII identifier byte. +func isIdentASCII(c byte) bool { + // mask is a 128-bit bitmap with 1s for allowed bytes, + // so that the byte c can be tested with a shift and an and. + // If c > 128, then 1<>64)) != 0 +} + +// validImportPath reports whether path is a valid import path. +// It is a lightly edited copy of golang.org/x/mod/module.CheckImportPath. +func validImportPath(path string) bool { + if !utf8.ValidString(path) { + return false + } + if path == "" { + return false + } + if path[0] == '-' { + return false + } + if strings.Contains(path, "//") { + return false + } + if path[len(path)-1] == '/' { + return false + } + elemStart := 0 + for i, r := range path { + if r == '/' { + if !validImportPathElem(path[elemStart:i]) { + return false + } + elemStart = i + 1 + } + } + return validImportPathElem(path[elemStart:]) +} + +func validImportPathElem(elem string) bool { + if elem == "" || elem[0] == '.' || elem[len(elem)-1] == '.' { + return false + } + for i := 0; i < len(elem); i++ { + if !importPathOK(elem[i]) { + return false + } + } + return true +} + +func importPathOK(c byte) bool { + // mask is a 128-bit bitmap with 1s for allowed bytes, + // so that the byte c can be tested with a shift and an and. + // If c > 128, then 1<>64)) != 0 +} diff --git a/src/go/doc/comment/parse_test.go b/src/go/doc/comment/parse_test.go new file mode 100644 index 0000000000..bce733eaae --- /dev/null +++ b/src/go/doc/comment/parse_test.go @@ -0,0 +1,12 @@ +// Copyright 2022 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 comment + +import "testing" + +// See https://golang.org/issue/52353 +func Test52353(t *testing.T) { + ident("𫕐ﯯ") +} diff --git a/src/go/doc/comment/print.go b/src/go/doc/comment/print.go new file mode 100644 index 0000000000..4e9da3d1e8 --- /dev/null +++ b/src/go/doc/comment/print.go @@ -0,0 +1,290 @@ +// Copyright 2022 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 comment + +import ( + "bytes" + "fmt" + "strings" +) + +// A Printer is a doc comment printer. +// The fields in the struct can be filled in before calling +// any of the printing methods +// in order to customize the details of the printing process. +type Printer struct { + // HeadingLevel is the nesting level used for + // HTML and Markdown headings. + // If HeadingLevel is zero, it defaults to level 3, + // meaning to use

and ###. + HeadingLevel int + + // HeadingID is a function that computes the heading ID + // (anchor tag) to use for the heading h when generating + // HTML and Markdown. If HeadingID returns an empty string, + // then the heading ID is omitted. + // If HeadingID is nil, h.DefaultID is used. + HeadingID func(h *Heading) string + + // DocLinkURL is a function that computes the URL for the given DocLink. + // If DocLinkURL is nil, then link.DefaultURL(p.DocLinkBaseURL) is used. + DocLinkURL func(link *DocLink) string + + // DocLinkBaseURL is used when DocLinkURL is nil, + // passed to [DocLink.DefaultURL] to construct a DocLink's URL. + // See that method's documentation for details. + DocLinkBaseURL string + + // TextPrefix is a prefix to print at the start of every line + // when generating text output using the Text method. + TextPrefix string + + // TextCodePrefix is the prefix to print at the start of each + // preformatted (code block) line when generating text output, + // instead of (not in addition to) TextPrefix. + // If TextCodePrefix is the empty string, it defaults to TextPrefix+"\t". + TextCodePrefix string + + // TextWidth is the maximum width text line to generate, + // measured in Unicode code points, + // excluding TextPrefix and the newline character. + // If TextWidth is zero, it defaults to 80 minus the number of code points in TextPrefix. + // If TextWidth is negative, there is no limit. + TextWidth int +} + +func (p *Printer) headingLevel() int { + if p.HeadingLevel <= 0 { + return 3 + } + return p.HeadingLevel +} + +func (p *Printer) headingID(h *Heading) string { + if p.HeadingID == nil { + return h.DefaultID() + } + return p.HeadingID(h) +} + +func (p *Printer) docLinkURL(link *DocLink) string { + if p.DocLinkURL != nil { + return p.DocLinkURL(link) + } + return link.DefaultURL(p.DocLinkBaseURL) +} + +// DefaultURL constructs and returns the documentation URL for l, +// using baseURL as a prefix for links to other packages. +// +// The possible forms returned by DefaultURL are: +// - baseURL/ImportPath, for a link to another package +// - baseURL/ImportPath#Name, for a link to a const, func, type, or var in another package +// - baseURL/ImportPath#Recv.Name, for a link to a method in another package +// - #Name, for a link to a const, func, type, or var in this package +// - #Recv.Name, for a link to a method in this package +// +// If baseURL ends in a trailing slash, then DefaultURL inserts +// a slash between ImportPath and # in the anchored forms. +// For example, here are some baseURL values and URLs they can generate: +// +// "/pkg/" → "/pkg/math/#Sqrt" +// "/pkg" → "/pkg/math#Sqrt" +// "/" → "/math/#Sqrt" +// "" → "/math#Sqrt" +func (l *DocLink) DefaultURL(baseURL string) string { + if l.ImportPath != "" { + slash := "" + if strings.HasSuffix(baseURL, "/") { + slash = "/" + } else { + baseURL += "/" + } + switch { + case l.Name == "": + return baseURL + l.ImportPath + slash + case l.Recv != "": + return baseURL + l.ImportPath + slash + "#" + l.Recv + "." + l.Name + default: + return baseURL + l.ImportPath + slash + "#" + l.Name + } + } + if l.Recv != "" { + return "#" + l.Recv + "." + l.Name + } + return "#" + l.Name +} + +// DefaultID returns the default anchor ID for the heading h. +// +// The default anchor ID is constructed by converting every +// rune that is not alphanumeric ASCII to an underscore +// and then adding the prefix “hdr-”. +// For example, if the heading text is “Go Doc Comments”, +// the default ID is “hdr-Go_Doc_Comments”. +func (h *Heading) DefaultID() string { + // Note: The “hdr-” prefix is important to avoid DOM clobbering attacks. + // See https://pkg.go.dev/github.com/google/safehtml#Identifier. + var out strings.Builder + var p textPrinter + p.oneLongLine(&out, h.Text) + s := strings.TrimSpace(out.String()) + if s == "" { + return "" + } + out.Reset() + out.WriteString("hdr-") + for _, r := range s { + if r < 0x80 && isIdentASCII(byte(r)) { + out.WriteByte(byte(r)) + } else { + out.WriteByte('_') + } + } + return out.String() +} + +type commentPrinter struct { + *Printer + headingPrefix string + needDoc map[string]bool +} + +// Comment returns the standard Go formatting of the Doc, +// without any comment markers. +func (p *Printer) Comment(d *Doc) []byte { + cp := &commentPrinter{Printer: p} + var out bytes.Buffer + for i, x := range d.Content { + if i > 0 && blankBefore(x) { + out.WriteString("\n") + } + cp.block(&out, x) + } + + // Print one block containing all the link definitions that were used, + // and then a second block containing all the unused ones. + // This makes it easy to clean up the unused ones: gofmt and + // delete the final block. And it's a nice visual signal without + // affecting the way the comment formats for users. + for i := 0; i < 2; i++ { + used := i == 0 + first := true + for _, def := range d.Links { + if def.Used == used { + if first { + out.WriteString("\n") + first = false + } + out.WriteString("[") + out.WriteString(def.Text) + out.WriteString("]: ") + out.WriteString(def.URL) + out.WriteString("\n") + } + } + } + + return out.Bytes() +} + +// blankBefore reports whether the block x requires a blank line before it. +// All blocks do, except for Lists that return false from x.BlankBefore(). +func blankBefore(x Block) bool { + if x, ok := x.(*List); ok { + return x.BlankBefore() + } + return true +} + +// block prints the block x to out. +func (p *commentPrinter) block(out *bytes.Buffer, x Block) { + switch x := x.(type) { + default: + fmt.Fprintf(out, "?%T", x) + + case *Paragraph: + p.text(out, "", x.Text) + out.WriteString("\n") + + case *Heading: + out.WriteString("# ") + p.text(out, "", x.Text) + out.WriteString("\n") + + case *Code: + md := x.Text + for md != "" { + var line string + line, md, _ = strings.Cut(md, "\n") + if line != "" { + out.WriteString("\t") + out.WriteString(line) + } + out.WriteString("\n") + } + + case *List: + loose := x.BlankBetween() + for i, item := range x.Items { + if i > 0 && loose { + out.WriteString("\n") + } + out.WriteString(" ") + if item.Number == "" { + out.WriteString(" - ") + } else { + out.WriteString(item.Number) + out.WriteString(". ") + } + for i, blk := range item.Content { + const fourSpace = " " + if i > 0 { + out.WriteString("\n" + fourSpace) + } + p.text(out, fourSpace, blk.(*Paragraph).Text) + out.WriteString("\n") + } + } + } +} + +// text prints the text sequence x to out. +func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { + for _, t := range x { + switch t := t.(type) { + case Plain: + p.indent(out, indent, string(t)) + case Italic: + p.indent(out, indent, string(t)) + case *Link: + if t.Auto { + p.text(out, indent, t.Text) + } else { + out.WriteString("[") + p.text(out, indent, t.Text) + out.WriteString("]") + } + case *DocLink: + out.WriteString("[") + p.text(out, indent, t.Text) + out.WriteString("]") + } + } +} + +// indent prints s to out, indenting with the indent string +// after each newline in s. +func (p *commentPrinter) indent(out *bytes.Buffer, indent, s string) { + for s != "" { + line, rest, ok := strings.Cut(s, "\n") + out.WriteString(line) + if ok { + out.WriteString("\n") + out.WriteString(indent) + } + s = rest + } +} diff --git a/src/go/doc/comment/std.go b/src/go/doc/comment/std.go new file mode 100644 index 0000000000..71f15f47b1 --- /dev/null +++ b/src/go/doc/comment/std.go @@ -0,0 +1,44 @@ +// Copyright 2022 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. + +// Code generated by 'go generate' DO NOT EDIT. +//go:generate ./mkstd.sh + +package comment + +var stdPkgs = []string{ + "bufio", + "bytes", + "context", + "crypto", + "embed", + "encoding", + "errors", + "expvar", + "flag", + "fmt", + "hash", + "html", + "image", + "io", + "log", + "math", + "mime", + "net", + "os", + "path", + "plugin", + "reflect", + "regexp", + "runtime", + "sort", + "strconv", + "strings", + "sync", + "syscall", + "testing", + "time", + "unicode", + "unsafe", +} diff --git a/src/go/doc/comment/std_test.go b/src/go/doc/comment/std_test.go new file mode 100644 index 0000000000..ae32dcd984 --- /dev/null +++ b/src/go/doc/comment/std_test.go @@ -0,0 +1,35 @@ +// Copyright 2022 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 comment + +import ( + "internal/diff" + "internal/testenv" + "os/exec" + "sort" + "strings" + "testing" +) + +func TestStd(t *testing.T) { + out, err := exec.Command(testenv.GoToolPath(t), "list", "std").CombinedOutput() + if err != nil { + t.Fatalf("%v\n%s", err, out) + } + + var list []string + for _, pkg := range strings.Fields(string(out)) { + if !strings.Contains(pkg, "/") { + list = append(list, pkg) + } + } + sort.Strings(list) + + have := strings.Join(stdPkgs, "\n") + "\n" + want := strings.Join(list, "\n") + "\n" + if have != want { + t.Errorf("stdPkgs is out of date: regenerate with 'go generate'\n%s", diff.Diff("stdPkgs", []byte(have), "want", []byte(want))) + } +} diff --git a/src/go/doc/comment/testdata/README.md b/src/go/doc/comment/testdata/README.md new file mode 100644 index 0000000000..d6f2c54960 --- /dev/null +++ b/src/go/doc/comment/testdata/README.md @@ -0,0 +1,42 @@ +This directory contains test files (*.txt) for the comment parser. + +The files are in [txtar format](https://pkg.go.dev/golang.org/x/tools/txtar). +Consider this example: + + -- input -- + Hello. + -- gofmt -- + Hello. + -- html -- +

Hello. + -- markdown -- + Hello. + -- text -- + Hello. + +Each `-- name --` line introduces a new file with the given name. +The file named “input” must be first and contains the input to +[comment.Parser](https://pkg.go.dev/go/doc/comment/#Parser). + +The remaining files contain the expected output for the named format generated by +[comment.Printer](https://pkg.go.dev/go/doc/comment/#Printer): +“gofmt” for Printer.Comment (Go comment format, as used by gofmt), +“html” for Printer.HTML, “markdown” for Printer.Markdown, and “text” for Printer.Text. +The format can also be “dump” for a textual dump of the raw data structures. + +The text before the `-- input --` line, if present, is JSON to be unmarshalled +to initialize a comment.Printer. For example, this test case sets the Printer's +TextWidth field to 20: + + {"TextWidth": 20} + -- input -- + Package gob manages streams of gobs - binary values exchanged between an + Encoder (transmitter) and a Decoder (receiver). + -- text -- + Package gob + manages streams + of gobs - binary + values exchanged + between an Encoder + (transmitter) and a + Decoder (receiver). diff --git a/src/go/doc/comment/testdata/blank.txt b/src/go/doc/comment/testdata/blank.txt new file mode 100644 index 0000000000..9049fde76e --- /dev/null +++ b/src/go/doc/comment/testdata/blank.txt @@ -0,0 +1,12 @@ +-- input -- + $ + Blank line at start and end. + $ +-- gofmt -- +Blank line at start and end. +-- text -- +Blank line at start and end. +-- markdown -- +Blank line at start and end. +-- html -- +

Blank line at start and end. diff --git a/src/go/doc/comment/testdata/code.txt b/src/go/doc/comment/testdata/code.txt new file mode 100644 index 0000000000..06b1519574 --- /dev/null +++ b/src/go/doc/comment/testdata/code.txt @@ -0,0 +1,94 @@ +-- input -- +Text. + A tab-indented + (no, not eight-space indented) + code block and haiku. +More text. + One space + is + enough + to + start + a + block. +More text. + + Blocks + can + + have + blank + lines. +-- gofmt -- +Text. + + A tab-indented + (no, not eight-space indented) + code block and haiku. + +More text. + + One space + is + enough + to + start + a + block. + +More text. + + Blocks + can + + have + blank + lines. +-- markdown -- +Text. + + A tab-indented + (no, not eight-space indented) + code block and haiku. + +More text. + + One space + is + enough + to + start + a + block. + +More text. + + Blocks + can + + have + blank + lines. +-- html -- +

Text. +

A tab-indented
+(no, not eight-space indented)
+code block and haiku.
+
+

More text. +

One space
+ is
+  enough
+   to
+    start
+     a
+      block.
+
+

More text. +

    Blocks
+  can
+
+have
+  blank
+    lines.
+
diff --git a/src/go/doc/comment/testdata/code2.txt b/src/go/doc/comment/testdata/code2.txt new file mode 100644 index 0000000000..0810bed41c --- /dev/null +++ b/src/go/doc/comment/testdata/code2.txt @@ -0,0 +1,31 @@ +-- input -- +Text. + + A tab-indented + (no, not eight-space indented) + code block and haiku. + +More text. +-- gofmt -- +Text. + + A tab-indented + (no, not eight-space indented) + code block and haiku. + +More text. +-- markdown -- +Text. + + A tab-indented + (no, not eight-space indented) + code block and haiku. + +More text. +-- html -- +

Text. +

A tab-indented
+(no, not eight-space indented)
+code block and haiku.
+
+

More text. diff --git a/src/go/doc/comment/testdata/code3.txt b/src/go/doc/comment/testdata/code3.txt new file mode 100644 index 0000000000..4a96a0e9ab --- /dev/null +++ b/src/go/doc/comment/testdata/code3.txt @@ -0,0 +1,33 @@ +-- input -- +Text. + + $ + A tab-indented + (surrounded by more blank lines) + code block and haiku. + $ + +More text. +-- gofmt -- +Text. + + A tab-indented + (surrounded by more blank lines) + code block and haiku. + +More text. +-- markdown -- +Text. + + A tab-indented + (surrounded by more blank lines) + code block and haiku. + +More text. +-- html -- +

Text. +

A tab-indented
+(surrounded by more blank lines)
+code block and haiku.
+
+

More text. diff --git a/src/go/doc/comment/testdata/doclink.txt b/src/go/doc/comment/testdata/doclink.txt new file mode 100644 index 0000000000..a9323471fd --- /dev/null +++ b/src/go/doc/comment/testdata/doclink.txt @@ -0,0 +1,21 @@ +-- input -- +In this package, see [Doc] and [Parser.Parse]. +There is no [Undef] or [Undef.Method]. +See also the [comment] package, +especially [comment.Doc] and [comment.Parser.Parse]. +-- gofmt -- +In this package, see [Doc] and [Parser.Parse]. +There is no [Undef] or [Undef.Method]. +See also the [comment] package, +especially [comment.Doc] and [comment.Parser.Parse]. +-- text -- +In this package, see Doc and Parser.Parse. There is no [Undef] or +[Undef.Method]. See also the comment package, especially comment.Doc and +comment.Parser.Parse. +-- markdown -- +In this package, see [Doc](#Doc) and [Parser.Parse](#Parser.Parse). There is no \[Undef] or \[Undef.Method]. See also the [comment](/go/doc/comment) package, especially [comment.Doc](/go/doc/comment#Doc) and [comment.Parser.Parse](/go/doc/comment#Parser.Parse). +-- html -- +

In this package, see Doc and Parser.Parse. +There is no [Undef] or [Undef.Method]. +See also the comment package, +especially comment.Doc and comment.Parser.Parse. diff --git a/src/go/doc/comment/testdata/doclink2.txt b/src/go/doc/comment/testdata/doclink2.txt new file mode 100644 index 0000000000..ecd8e4e0bc --- /dev/null +++ b/src/go/doc/comment/testdata/doclink2.txt @@ -0,0 +1,8 @@ +-- input -- +We use [io.Reader] a lot, and also a few map[io.Reader]string. + +Never [io.Reader]int or Slice[io.Reader] though. +-- markdown -- +We use [io.Reader](/io#Reader) a lot, and also a few map\[io.Reader]string. + +Never \[io.Reader]int or Slice\[io.Reader] though. diff --git a/src/go/doc/comment/testdata/doclink3.txt b/src/go/doc/comment/testdata/doclink3.txt new file mode 100644 index 0000000000..0ccfb3df70 --- /dev/null +++ b/src/go/doc/comment/testdata/doclink3.txt @@ -0,0 +1,8 @@ +-- input -- +[encoding/json.Marshal] is a doc link. + +[rot13.Marshal] is not. +-- markdown -- +[encoding/json.Marshal](/encoding/json#Marshal) is a doc link. + +\[rot13.Marshal] is not. diff --git a/src/go/doc/comment/testdata/doclink4.txt b/src/go/doc/comment/testdata/doclink4.txt new file mode 100644 index 0000000000..c7095276bf --- /dev/null +++ b/src/go/doc/comment/testdata/doclink4.txt @@ -0,0 +1,7 @@ +-- input -- +[io] at start of comment. +[io] at start of line. +At end of line: [io] +At end of comment: [io] +-- markdown -- +[io](/io) at start of comment. [io](/io) at start of line. At end of line: [io](/io) At end of comment: [io](/io) diff --git a/src/go/doc/comment/testdata/doclink5.txt b/src/go/doc/comment/testdata/doclink5.txt new file mode 100644 index 0000000000..ac7b3ae100 --- /dev/null +++ b/src/go/doc/comment/testdata/doclink5.txt @@ -0,0 +1,5 @@ +{"DocLinkBaseURL": "https://pkg.go.dev"} +-- input -- +[encoding/json.Marshal] is a doc link. +-- markdown -- +[encoding/json.Marshal](https://pkg.go.dev/encoding/json#Marshal) is a doc link. diff --git a/src/go/doc/comment/testdata/doclink6.txt b/src/go/doc/comment/testdata/doclink6.txt new file mode 100644 index 0000000000..1acd03b616 --- /dev/null +++ b/src/go/doc/comment/testdata/doclink6.txt @@ -0,0 +1,5 @@ +{"DocLinkBaseURL": "https://go.dev/pkg/"} +-- input -- +[encoding/json.Marshal] is a doc link, and so is [rsc.io/quote.NonExist]. +-- markdown -- +[encoding/json.Marshal](https://go.dev/pkg/encoding/json/#Marshal) is a doc link, and so is [rsc.io/quote.NonExist](https://go.dev/pkg/rsc.io/quote/#NonExist). diff --git a/src/go/doc/comment/testdata/doclink7.txt b/src/go/doc/comment/testdata/doclink7.txt new file mode 100644 index 0000000000..d34979a385 --- /dev/null +++ b/src/go/doc/comment/testdata/doclink7.txt @@ -0,0 +1,4 @@ +-- input -- +You see more [*bytes.Buffer] than [bytes.Buffer]. +-- markdown -- +You see more [\*bytes.Buffer](/bytes#Buffer) than [bytes.Buffer](/bytes#Buffer). diff --git a/src/go/doc/comment/testdata/escape.txt b/src/go/doc/comment/testdata/escape.txt new file mode 100644 index 0000000000..f54663f5c3 --- /dev/null +++ b/src/go/doc/comment/testdata/escape.txt @@ -0,0 +1,55 @@ +-- input -- +What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? + ++ Line + +- Line + +* Line + +999. Line + +## Line +-- gofmt -- +What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? + ++ Line + +- Line + +* Line + +999. Line + +## Line +-- text -- +What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? + ++ Line + +- Line + +* Line + +999. Line + +## Line +-- markdown -- +What the ~!@#$%^&\*()\_+-=\`{}|\[]\\:";',./\<>? + +\+ Line + +\- Line + +\* Line + +999\. Line + +\## Line +-- html -- +

What the ~!@#$%^&*()_+-=`{}|[]\:";',./<>? +

+ Line +

- Line +

* Line +

999. Line +

## Line diff --git a/src/go/doc/comment/testdata/head.txt b/src/go/doc/comment/testdata/head.txt new file mode 100644 index 0000000000..b99a8c59f3 --- /dev/null +++ b/src/go/doc/comment/testdata/head.txt @@ -0,0 +1,92 @@ +-- input -- +Some text. + +An Old Heading + +Not An Old Heading. + +And some text. + +# A New Heading. + +And some more text. + +# Not a heading, +because text follows it. + +Because text precedes it, +# not a heading. + +## Not a heading either. + +-- gofmt -- +Some text. + +# An Old Heading + +Not An Old Heading. + +And some text. + +# A New Heading. + +And some more text. + +# Not a heading, +because text follows it. + +Because text precedes it, +# not a heading. + +## Not a heading either. + +-- text -- +Some text. + +# An Old Heading + +Not An Old Heading. + +And some text. + +# A New Heading. + +And some more text. + +# Not a heading, because text follows it. + +Because text precedes it, # not a heading. + +## Not a heading either. + +-- markdown -- +Some text. + +### An Old Heading {#hdr-An_Old_Heading} + +Not An Old Heading. + +And some text. + +### A New Heading. {#hdr-A_New_Heading_} + +And some more text. + +\# Not a heading, because text follows it. + +Because text precedes it, # not a heading. + +\## Not a heading either. + +-- html -- +

Some text. +

An Old Heading

+

Not An Old Heading. +

And some text. +

A New Heading.

+

And some more text. +

# Not a heading, +because text follows it. +

Because text precedes it, +# not a heading. +

## Not a heading either. diff --git a/src/go/doc/comment/testdata/head2.txt b/src/go/doc/comment/testdata/head2.txt new file mode 100644 index 0000000000..d3576325e0 --- /dev/null +++ b/src/go/doc/comment/testdata/head2.txt @@ -0,0 +1,36 @@ +-- input -- +✦ + +Almost a+heading + +✦ + +Don't be a heading + +✦ + +A.b is a heading + +✦ + +A. b is not a heading + +✦ +-- gofmt -- +✦ + +Almost a+heading + +✦ + +Don't be a heading + +✦ + +# A.b is a heading + +✦ + +A. b is not a heading + +✦ diff --git a/src/go/doc/comment/testdata/head3.txt b/src/go/doc/comment/testdata/head3.txt new file mode 100644 index 0000000000..dbb7cb3ffb --- /dev/null +++ b/src/go/doc/comment/testdata/head3.txt @@ -0,0 +1,7 @@ +{"HeadingLevel": 5} +-- input -- +# Heading +-- markdown -- +##### Heading {#hdr-Heading} +-- html -- +

Heading
diff --git a/src/go/doc/comment/testdata/hello.txt b/src/go/doc/comment/testdata/hello.txt new file mode 100644 index 0000000000..fb07f1eb75 --- /dev/null +++ b/src/go/doc/comment/testdata/hello.txt @@ -0,0 +1,35 @@ +-- input -- + Hello, + world + + This is + a test. +-- dump -- +Doc + Paragraph + Plain + "Hello,\n" + "world" + Paragraph + Plain + "This is\n" + "a test." +-- gofmt -- +Hello, +world + +This is +a test. +-- html -- +

Hello, +world +

This is +a test. +-- markdown -- +Hello, world + +This is a test. +-- text -- +Hello, world + +This is a test. diff --git a/src/go/doc/comment/testdata/link.txt b/src/go/doc/comment/testdata/link.txt new file mode 100644 index 0000000000..551e3065ce --- /dev/null +++ b/src/go/doc/comment/testdata/link.txt @@ -0,0 +1,17 @@ +-- input -- +The Go home page is https://go.dev/. +It used to be https://golang.org. + +-- gofmt -- +The Go home page is https://go.dev/. +It used to be https://golang.org. + +-- text -- +The Go home page is https://go.dev/. It used to be https://golang.org. + +-- markdown -- +The Go home page is [https://go.dev/](https://go.dev/). It used to be [https://golang.org](https://golang.org). + +-- html -- +

The Go home page is https://go.dev/. +It used to be https://golang.org. diff --git a/src/go/doc/comment/testdata/link2.txt b/src/go/doc/comment/testdata/link2.txt new file mode 100644 index 0000000000..8637a32f01 --- /dev/null +++ b/src/go/doc/comment/testdata/link2.txt @@ -0,0 +1,31 @@ +-- input -- +The Go home page is https://go.dev/. +It used to be https://golang.org. +https:// is not a link. +Nor is https:// +https://☺ is not a link. +https://:80 is not a link. + +-- gofmt -- +The Go home page is https://go.dev/. +It used to be https://golang.org. +https:// is not a link. +Nor is https:// +https://☺ is not a link. +https://:80 is not a link. + +-- text -- +The Go home page is https://go.dev/. It used to be https://golang.org. https:// +is not a link. Nor is https:// https://☺ is not a link. https://:80 is not a +link. + +-- markdown -- +The Go home page is [https://go.dev/](https://go.dev/). It used to be [https://golang.org](https://golang.org). https:// is not a link. Nor is https:// https://☺ is not a link. https://:80 is not a link. + +-- html -- +

The Go home page is https://go.dev/. +It used to be https://golang.org. +https:// is not a link. +Nor is https:// +https://☺ is not a link. +https://:80 is not a link. diff --git a/src/go/doc/comment/testdata/link3.txt b/src/go/doc/comment/testdata/link3.txt new file mode 100644 index 0000000000..5a115b5cb7 --- /dev/null +++ b/src/go/doc/comment/testdata/link3.txt @@ -0,0 +1,14 @@ +-- input -- +Doc text. + +[Go home page]: https://go.dev +-- gofmt -- +Doc text. + +[Go home page]: https://go.dev +-- text -- +Doc text. +-- markdown -- +Doc text. +-- html -- +

Doc text. diff --git a/src/go/doc/comment/testdata/link4.txt b/src/go/doc/comment/testdata/link4.txt new file mode 100644 index 0000000000..75f194c845 --- /dev/null +++ b/src/go/doc/comment/testdata/link4.txt @@ -0,0 +1,77 @@ +-- input -- +These are not links. + +[x + +[x]: + +[x]:https://go.dev + +[x]https://go.dev + +[x]: surprise://go.dev + +[x]: surprise! + +But this is, with a tab (although it's unused). + +[z]: https://go.dev +-- gofmt -- +These are not links. + +[x + +[x]: + +[x]:https://go.dev + +[x]https://go.dev + +[x]: surprise://go.dev + +[x]: surprise! + +But this is, with a tab (although it's unused). + +[z]: https://go.dev +-- text -- +These are not links. + +[x + +[x]: + +[x]:https://go.dev + +[x]https://go.dev + +[x]: surprise://go.dev + +[x]: surprise! + +But this is, with a tab (although it's unused). +-- markdown -- +These are not links. + +\[x + +\[x]: + +\[x]:[https://go.dev](https://go.dev) + +\[x][https://go.dev](https://go.dev) + +\[x]: surprise://go.dev + +\[x]: surprise! + +But this is, with a tab (although it's unused). +-- html -- +

These are not links. +

[x +

[x]: +

[x]:https://go.dev +

[x]https://go.dev +

[x]: surprise://go.dev +

[x]: surprise! +

But this is, with a tab (although it's unused). diff --git a/src/go/doc/comment/testdata/link5.txt b/src/go/doc/comment/testdata/link5.txt new file mode 100644 index 0000000000..b4fb5889f4 --- /dev/null +++ b/src/go/doc/comment/testdata/link5.txt @@ -0,0 +1,36 @@ +-- input -- +See the [Go home page] and the [pkg +site]. + +[Go home page]: https://go.dev/ +[pkg site]: https://pkg.go.dev +[Go home page]: https://duplicate.ignored + +They're really great! + +-- gofmt -- +See the [Go home page] and the [pkg +site]. + +They're really great! + +[Go home page]: https://go.dev/ +[pkg site]: https://pkg.go.dev + +[Go home page]: https://duplicate.ignored + +-- text -- +See the Go home page and the pkg site. + +They're really great! + +[Go home page]: https://go.dev/ +[pkg site]: https://pkg.go.dev +-- markdown -- +See the [Go home page](https://go.dev/) and the [pkg site](https://pkg.go.dev). + +They're really great! +-- html -- +

See the Go home page and the pkg +site. +

They're really great! diff --git a/src/go/doc/comment/testdata/link6.txt b/src/go/doc/comment/testdata/link6.txt new file mode 100644 index 0000000000..ff629b4573 --- /dev/null +++ b/src/go/doc/comment/testdata/link6.txt @@ -0,0 +1,50 @@ +-- input -- +URLs with punctuation are hard. +We don't want to consume the end-of-sentence punctuation. + +For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). +And https://example.com/[foo]/bar{. +And https://example.com/(foo)/bar! +And https://example.com/{foo}/bar{. +And https://example.com/)baz{foo}. + +[And https://example.com/]. + +-- gofmt -- +URLs with punctuation are hard. +We don't want to consume the end-of-sentence punctuation. + +For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). +And https://example.com/[foo]/bar{. +And https://example.com/(foo)/bar! +And https://example.com/{foo}/bar{. +And https://example.com/)baz{foo}. + +[And https://example.com/]. + +-- text -- +URLs with punctuation are hard. We don't want to consume the end-of-sentence +punctuation. + +For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). +And https://example.com/[foo]/bar{. And https://example.com/(foo)/bar! And +https://example.com/{foo}/bar{. And https://example.com/)baz{foo}. + +[And https://example.com/]. + +-- markdown -- +URLs with punctuation are hard. We don't want to consume the end-of-sentence punctuation. + +For example, [https://en.wikipedia.org/wiki/John\_Adams\_(miniseries)](https://en.wikipedia.org/wiki/John_Adams_(miniseries)). And [https://example.com/\[foo]/bar](https://example.com/[foo]/bar){. And [https://example.com/(foo)/bar](https://example.com/(foo)/bar)! And [https://example.com/{foo}/bar](https://example.com/{foo}/bar){. And [https://example.com/](https://example.com/))baz{foo}. + +\[And [https://example.com/](https://example.com/)]. + +-- html -- +

URLs with punctuation are hard. +We don't want to consume the end-of-sentence punctuation. +

For example, https://en.wikipedia.org/wiki/John_Adams_(miniseries). +And https://example.com/[foo]/bar{. +And https://example.com/(foo)/bar! +And https://example.com/{foo}/bar{. +And https://example.com/)baz{foo}. +

[And https://example.com/]. diff --git a/src/go/doc/comment/testdata/link7.txt b/src/go/doc/comment/testdata/link7.txt new file mode 100644 index 0000000000..89a8b3170e --- /dev/null +++ b/src/go/doc/comment/testdata/link7.txt @@ -0,0 +1,25 @@ +-- input -- +[math] is a package but this is not a doc link. + +[io] is a doc link. + +[math]: https://example.com +-- gofmt -- +[math] is a package but this is not a doc link. + +[io] is a doc link. + +[math]: https://example.com +-- text -- +math is a package but this is not a doc link. + +io is a doc link. + +[math]: https://example.com +-- markdown -- +[math](https://example.com) is a package but this is not a doc link. + +[io](/io) is a doc link. +-- html -- +

math is a package but this is not a doc link. +

io is a doc link. diff --git a/src/go/doc/comment/testdata/list.txt b/src/go/doc/comment/testdata/list.txt new file mode 100644 index 0000000000..455782f864 --- /dev/null +++ b/src/go/doc/comment/testdata/list.txt @@ -0,0 +1,48 @@ +-- input -- +Text. +- Not a list. + - Here is the list. + • Using multiple bullets. + * Indentation does not matter. + + Lots of bullets. +More text. + +-- gofmt -- +Text. +- Not a list. + - Here is the list. + - Using multiple bullets. + - Indentation does not matter. + - Lots of bullets. + +More text. + +-- text -- +Text. - Not a list. + - Here is the list. + - Using multiple bullets. + - Indentation does not matter. + - Lots of bullets. + +More text. + +-- markdown -- +Text. - Not a list. + + - Here is the list. + - Using multiple bullets. + - Indentation does not matter. + - Lots of bullets. + +More text. + +-- html -- +

Text. +- Not a list. +

    +
  • Here is the list. +
  • Using multiple bullets. +
  • Indentation does not matter. +
  • Lots of bullets. +
+

More text. diff --git a/src/go/doc/comment/testdata/list2.txt b/src/go/doc/comment/testdata/list2.txt new file mode 100644 index 0000000000..c390b3d59a --- /dev/null +++ b/src/go/doc/comment/testdata/list2.txt @@ -0,0 +1,57 @@ +-- input -- +Text. + 1. Uno + 2) Dos + 3. Tres + 5. Cinco + 7. Siete + 11. Once + 12. Doce + 13. Trece. + +-- gofmt -- +Text. + 1. Uno + 2. Dos + 3. Tres + 5. Cinco + 7. Siete + 11. Once + 12. Doce + 13. Trece. + +-- text -- +Text. + 1. Uno + 2. Dos + 3. Tres + 5. Cinco + 7. Siete + 11. Once + 12. Doce + 13. Trece. + +-- markdown -- +Text. + + 1. Uno + 2. Dos + 3. Tres + 5. Cinco + 7. Siete + 11. Once + 12. Doce + 13. Trece. + +-- html -- +

Text. +

    +
  1. Uno +
  2. Dos +
  3. Tres +
  4. Cinco +
  5. Siete +
  6. Once +
  7. Doce +
  8. Trece. +
diff --git a/src/go/doc/comment/testdata/list3.txt b/src/go/doc/comment/testdata/list3.txt new file mode 100644 index 0000000000..d7d345d2d3 --- /dev/null +++ b/src/go/doc/comment/testdata/list3.txt @@ -0,0 +1,32 @@ +-- input -- +Text. + + 1. Uno + 1. Dos + 1. Tres + 1. Quatro + +-- gofmt -- +Text. + + 1. Uno + 1. Dos + 1. Tres + 1. Quatro + +-- markdown -- +Text. + + 1. Uno + 1. Dos + 1. Tres + 1. Quatro + +-- html -- +

Text. +

    +
  1. Uno +
  2. Dos +
  3. Tres +
  4. Quatro +
diff --git a/src/go/doc/comment/testdata/list4.txt b/src/go/doc/comment/testdata/list4.txt new file mode 100644 index 0000000000..9c28d65b6c --- /dev/null +++ b/src/go/doc/comment/testdata/list4.txt @@ -0,0 +1,38 @@ +-- input -- +Text. + 1. List +2. Not indented, not a list. + 3. Another list. + +-- gofmt -- +Text. + 1. List + +2. Not indented, not a list. + 3. Another list. + +-- text -- +Text. + 1. List + +2. Not indented, not a list. + 3. Another list. + +-- markdown -- +Text. + + 1. List + +2\. Not indented, not a list. + + 3. Another list. + +-- html -- +

Text. +

    +
  1. List +
+

2. Not indented, not a list. +

    +
  1. Another list. +
diff --git a/src/go/doc/comment/testdata/list5.txt b/src/go/doc/comment/testdata/list5.txt new file mode 100644 index 0000000000..a5128e5b7c --- /dev/null +++ b/src/go/doc/comment/testdata/list5.txt @@ -0,0 +1,40 @@ +-- input -- +Text. + + 1. One + 999999999999999999999. Big + 1000000000000000000000. Bigger + 1000000000000000000001. Biggest + +-- gofmt -- +Text. + + 1. One + 999999999999999999999. Big + 1000000000000000000000. Bigger + 1000000000000000000001. Biggest + +-- text -- +Text. + + 1. One + 999999999999999999999. Big + 1000000000000000000000. Bigger + 1000000000000000000001. Biggest + +-- markdown -- +Text. + + 1. One + 999999999999999999999. Big + 1000000000000000000000. Bigger + 1000000000000000000001. Biggest + +-- html -- +

Text. +

    +
  1. One +
  2. Big +
  3. Bigger +
  4. Biggest +
diff --git a/src/go/doc/comment/testdata/list6.txt b/src/go/doc/comment/testdata/list6.txt new file mode 100644 index 0000000000..ffc0122f52 --- /dev/null +++ b/src/go/doc/comment/testdata/list6.txt @@ -0,0 +1,129 @@ +-- input -- +Text. + - List immediately after. + - Another. + +More text. + + - List after blank line. + - Another. + +Even more text. + - List immediately after. + + - Blank line between items. + +Yet more text. + + - Another list after blank line. + + - Blank line between items. + +Still more text. + - One list item. + + Multiple paragraphs. +-- dump -- +Doc + Paragraph + Plain "Text." + List ForceBlankBefore=false ForceBlankBetween=false + Item Number="" + Paragraph + Plain "List immediately after." + Item Number="" + Paragraph + Plain "Another." + Paragraph + Plain "More text." + List ForceBlankBefore=true ForceBlankBetween=false + Item Number="" + Paragraph + Plain "List after blank line." + Item Number="" + Paragraph + Plain "Another." + Paragraph + Plain "Even more text." + List ForceBlankBefore=false ForceBlankBetween=true + Item Number="" + Paragraph + Plain "List immediately after." + Item Number="" + Paragraph + Plain "Blank line between items." + Paragraph + Plain "Yet more text." + List ForceBlankBefore=true ForceBlankBetween=true + Item Number="" + Paragraph + Plain "Another list after blank line." + Item Number="" + Paragraph + Plain "Blank line between items." + Paragraph + Plain "Still more text." + List ForceBlankBefore=false ForceBlankBetween=true + Item Number="" + Paragraph + Plain "One list item." + Paragraph + Plain "Multiple paragraphs." + +-- gofmt -- +Text. + - List immediately after. + - Another. + +More text. + + - List after blank line. + - Another. + +Even more text. + + - List immediately after. + + - Blank line between items. + +Yet more text. + + - Another list after blank line. + + - Blank line between items. + +Still more text. + + - One list item. + + Multiple paragraphs. + +-- markdown -- +Text. + + - List immediately after. + - Another. + +More text. + + - List after blank line. + - Another. + +Even more text. + + - List immediately after. + + - Blank line between items. + +Yet more text. + + - Another list after blank line. + + - Blank line between items. + +Still more text. + + - One list item. + + Multiple paragraphs. + diff --git a/src/go/doc/comment/testdata/list7.txt b/src/go/doc/comment/testdata/list7.txt new file mode 100644 index 0000000000..446605061f --- /dev/null +++ b/src/go/doc/comment/testdata/list7.txt @@ -0,0 +1,98 @@ +-- input -- +Almost list markers (but not quite): + + - + +❦ + + - $ + +❦ + + - $ + +❦ + + $ + $ + +❦ + + 1! List. + +❦ +-- gofmt -- +Almost list markers (but not quite): + + - + +❦ + + - $ + +❦ + + - $ + +❦ + +❦ + + 1! List. + +❦ +-- text -- +Almost list markers (but not quite): + + - + +❦ + + - + +❦ + + - + +❦ + +❦ + + 1! List. + +❦ +-- markdown -- +Almost list markers (but not quite): + + - + +❦ + + - $ + +❦ + + - $ + +❦ + +❦ + + 1! List. + +❦ +-- html -- +

Almost list markers (but not quite): +

-
+
+

❦ +

- $
+
+

❦ +

- $
+
+

❦ +

❦ +

1! List.
+
+

❦ diff --git a/src/go/doc/comment/testdata/list8.txt b/src/go/doc/comment/testdata/list8.txt new file mode 100644 index 0000000000..fc46b0d835 --- /dev/null +++ b/src/go/doc/comment/testdata/list8.txt @@ -0,0 +1,56 @@ +-- input -- +Loose lists. + - A + + B + - C + D + - E + - F +-- gofmt -- +Loose lists. + + - A + + B + + - C + D + + - E + + - F +-- text -- +Loose lists. + + - A + + B + + - C D + + - E + + - F +-- markdown -- +Loose lists. + + - A + + B + + - C D + + - E + + - F +-- html -- +

Loose lists. +

    +
  • A +

    B +

  • C +D +

  • E +

  • F +

diff --git a/src/go/doc/comment/testdata/para.txt b/src/go/doc/comment/testdata/para.txt new file mode 100644 index 0000000000..2355fa8172 --- /dev/null +++ b/src/go/doc/comment/testdata/para.txt @@ -0,0 +1,17 @@ +-- input -- +Hello, world. +This is a paragraph. + +-- gofmt -- +Hello, world. +This is a paragraph. + +-- text -- +Hello, world. This is a paragraph. + +-- markdown -- +Hello, world. This is a paragraph. + +-- html -- +

Hello, world. +This is a paragraph. diff --git a/src/go/doc/comment/testdata/quote.txt b/src/go/doc/comment/testdata/quote.txt new file mode 100644 index 0000000000..799663af80 --- /dev/null +++ b/src/go/doc/comment/testdata/quote.txt @@ -0,0 +1,12 @@ +-- input -- +Doubled single quotes like `` and '' turn into Unicode double quotes, +but single quotes ` and ' do not. +-- gofmt -- +Doubled single quotes like “ and ” turn into Unicode double quotes, +but single quotes ` and ' do not. +-- text -- +Doubled single quotes like “ and ” turn into Unicode double quotes, but single +quotes ` and ' do not. +-- html -- +

Doubled single quotes like “ and ” turn into Unicode double quotes, +but single quotes ` and ' do not. diff --git a/src/go/doc/comment/testdata/text.txt b/src/go/doc/comment/testdata/text.txt new file mode 100644 index 0000000000..c4de6e20d2 --- /dev/null +++ b/src/go/doc/comment/testdata/text.txt @@ -0,0 +1,62 @@ +{"TextPrefix":"|", "TextCodePrefix": "@"} +-- input -- +Hello, world + Code block here. +More text. +Tight list + - one + - two + - three +Loose list + - one + + - two + + - three + +# Heading + +More text. +-- gofmt -- +Hello, world + + Code block here. + +More text. +Tight list + - one + - two + - three + +Loose list + + - one + + - two + + - three + +# Heading + +More text. +-- text -- +|Hello, world +| +@Code block here. +| +|More text. Tight list +| - one +| - two +| - three +| +|Loose list +| +| - one +| +| - two +| +| - three +| +|# Heading +| +|More text. diff --git a/src/go/doc/comment/testdata/text2.txt b/src/go/doc/comment/testdata/text2.txt new file mode 100644 index 0000000000..a099d0b8c6 --- /dev/null +++ b/src/go/doc/comment/testdata/text2.txt @@ -0,0 +1,14 @@ +{"TextWidth": -1} +-- input -- +Package gob manages streams of gobs - binary values exchanged between an +Encoder (transmitter) and a Decoder (receiver). A typical use is +transporting arguments and results of remote procedure calls (RPCs) such as +those provided by package "net/rpc". + +The implementation compiles a custom codec for each data type in the stream +and is most efficient when a single Encoder is used to transmit a stream of +values, amortizing the cost of compilation. +-- text -- +Package gob manages streams of gobs - binary values exchanged between an Encoder (transmitter) and a Decoder (receiver). A typical use is transporting arguments and results of remote procedure calls (RPCs) such as those provided by package "net/rpc". + +The implementation compiles a custom codec for each data type in the stream and is most efficient when a single Encoder is used to transmit a stream of values, amortizing the cost of compilation. diff --git a/src/go/doc/comment/testdata/text3.txt b/src/go/doc/comment/testdata/text3.txt new file mode 100644 index 0000000000..75d2c3765c --- /dev/null +++ b/src/go/doc/comment/testdata/text3.txt @@ -0,0 +1,28 @@ +{"TextWidth": 30} +-- input -- +Package gob manages streams of gobs - binary values exchanged between an +Encoder (transmitter) and a Decoder (receiver). A typical use is +transporting arguments and results of remote procedure calls (RPCs) such as +those provided by package "net/rpc". + +The implementation compiles a custom codec for each data type in the stream +and is most efficient when a single Encoder is used to transmit a stream of +values, amortizing the cost of compilation. +-- text -- +Package gob manages streams +of gobs - binary values +exchanged between an Encoder +(transmitter) and a Decoder +(receiver). A typical use is +transporting arguments and +results of remote procedure +calls (RPCs) such as those +provided by package "net/rpc". + +The implementation compiles +a custom codec for each data +type in the stream and is +most efficient when a single +Encoder is used to transmit a +stream of values, amortizing +the cost of compilation. diff --git a/src/go/doc/comment/testdata/text4.txt b/src/go/doc/comment/testdata/text4.txt new file mode 100644 index 0000000000..e429985077 --- /dev/null +++ b/src/go/doc/comment/testdata/text4.txt @@ -0,0 +1,29 @@ +{"TextWidth": 29} +-- input -- +Package gob manages streams of gobs - binary values exchanged between an +Encoder (transmitter) and a Decoder (receiver). A typical use is +transporting arguments and results of remote procedure calls (RPCs) such as +those provided by package "net/rpc". + +The implementation compiles a custom codec for each data type in the stream +and is most efficient when a single Encoder is used to transmit a stream of +values, amortizing the cost of compilation. +-- text -- +Package gob manages streams +of gobs - binary values +exchanged between an Encoder +(transmitter) and a Decoder +(receiver). A typical use +is transporting arguments +and results of remote +procedure calls (RPCs) such +as those provided by package +"net/rpc". + +The implementation compiles +a custom codec for each data +type in the stream and is +most efficient when a single +Encoder is used to transmit a +stream of values, amortizing +the cost of compilation. diff --git a/src/go/doc/comment/testdata/text5.txt b/src/go/doc/comment/testdata/text5.txt new file mode 100644 index 0000000000..2408fc559d --- /dev/null +++ b/src/go/doc/comment/testdata/text5.txt @@ -0,0 +1,38 @@ +{"TextWidth": 20} +-- input -- +Package gob manages streams of gobs - binary values exchanged between an +Encoder (transmitter) and a Decoder (receiver). A typical use is +transporting arguments and results of remote procedure calls (RPCs) such as +those provided by package "net/rpc". + +The implementation compiles a custom codec for each data type in the stream +and is most efficient when a single Encoder is used to transmit a stream of +values, amortizing the cost of compilation. +-- text -- +Package gob +manages streams +of gobs - binary +values exchanged +between an Encoder +(transmitter) and a +Decoder (receiver). +A typical use +is transporting +arguments and +results of remote +procedure calls +(RPCs) such as those +provided by package +"net/rpc". + +The implementation +compiles a custom +codec for each +data type in the +stream and is most +efficient when a +single Encoder is +used to transmit a +stream of values, +amortizing the cost +of compilation. diff --git a/src/go/doc/comment/testdata/text6.txt b/src/go/doc/comment/testdata/text6.txt new file mode 100644 index 0000000000..d6deff52cf --- /dev/null +++ b/src/go/doc/comment/testdata/text6.txt @@ -0,0 +1,18 @@ +-- input -- +Package gob manages streams of gobs - binary values exchanged between an +Encoder (transmitter) and a Decoder (receiver). A typical use is +transporting arguments and results of remote procedure calls (RPCs) such as +those provided by package "net/rpc". + +The implementation compiles a custom codec for each data type in the stream +and is most efficient when a single Encoder is used to transmit a stream of +values, amortizing the cost of compilation. +-- text -- +Package gob manages streams of gobs - binary values exchanged between an Encoder +(transmitter) and a Decoder (receiver). A typical use is transporting arguments +and results of remote procedure calls (RPCs) such as those provided by package +"net/rpc". + +The implementation compiles a custom codec for each data type in the stream and +is most efficient when a single Encoder is used to transmit a stream of values, +amortizing the cost of compilation. diff --git a/src/go/doc/comment/testdata/text7.txt b/src/go/doc/comment/testdata/text7.txt new file mode 100644 index 0000000000..c9fb6d3754 --- /dev/null +++ b/src/go/doc/comment/testdata/text7.txt @@ -0,0 +1,21 @@ +{"TextPrefix": " "} +-- input -- +Package gob manages streams of gobs - binary values exchanged between an +Encoder (transmitter) and a Decoder (receiver). A typical use is +transporting arguments and results of remote procedure calls (RPCs) such as +those provided by package "net/rpc". + +The implementation compiles a custom codec for each data type in the stream +and is most efficient when a single Encoder is used to transmit a stream of +values, amortizing the cost of compilation. +-- text -- + Package gob manages streams of gobs - binary values + exchanged between an Encoder (transmitter) and a Decoder + (receiver). A typical use is transporting arguments and + results of remote procedure calls (RPCs) such as those + provided by package "net/rpc". + + The implementation compiles a custom codec for each data + type in the stream and is most efficient when a single + Encoder is used to transmit a stream of values, amortizing + the cost of compilation. diff --git a/src/go/doc/comment/testdata/text8.txt b/src/go/doc/comment/testdata/text8.txt new file mode 100644 index 0000000000..560ac951c1 --- /dev/null +++ b/src/go/doc/comment/testdata/text8.txt @@ -0,0 +1,94 @@ +{"TextWidth": 40} +-- input -- +If the arguments have version suffixes (like @latest or @v1.0.0), "go install" +builds packages in module-aware mode, ignoring the go.mod file in the current +directory or any parent directory, if there is one. This is useful for +installing executables without affecting the dependencies of the main module. +To eliminate ambiguity about which module versions are used in the build, the +arguments must satisfy the following constraints: + + - Arguments must be package paths or package patterns (with "..." wildcards). + They must not be standard packages (like fmt), meta-patterns (std, cmd, + all), or relative or absolute file paths. + + - All arguments must have the same version suffix. Different queries are not + allowed, even if they refer to the same version. + + - All arguments must refer to packages in the same module at the same version. + + - Package path arguments must refer to main packages. Pattern arguments + will only match main packages. + + - No module is considered the "main" module. If the module containing + packages named on the command line has a go.mod file, it must not contain + directives (replace and exclude) that would cause it to be interpreted + differently than if it were the main module. The module must not require + a higher version of itself. + + - Vendor directories are not used in any module. (Vendor directories are not + included in the module zip files downloaded by 'go install'.) + +If the arguments don't have version suffixes, "go install" may run in +module-aware mode or GOPATH mode, depending on the GO111MODULE environment +variable and the presence of a go.mod file. See 'go help modules' for details. +If module-aware mode is enabled, "go install" runs in the context of the main +module. +-- text -- +If the arguments have version suffixes +(like @latest or @v1.0.0), "go install" +builds packages in module-aware mode, +ignoring the go.mod file in the current +directory or any parent directory, +if there is one. This is useful for +installing executables without affecting +the dependencies of the main module. +To eliminate ambiguity about which +module versions are used in the build, +the arguments must satisfy the following +constraints: + + - Arguments must be package paths + or package patterns (with "..." + wildcards). They must not be + standard packages (like fmt), + meta-patterns (std, cmd, all), + or relative or absolute file paths. + + - All arguments must have the same + version suffix. Different queries + are not allowed, even if they refer + to the same version. + + - All arguments must refer to packages + in the same module at the same + version. + + - Package path arguments must refer + to main packages. Pattern arguments + will only match main packages. + + - No module is considered the "main" + module. If the module containing + packages named on the command line + has a go.mod file, it must not + contain directives (replace and + exclude) that would cause it to be + interpreted differently than if it + were the main module. The module + must not require a higher version of + itself. + + - Vendor directories are not used in + any module. (Vendor directories are + not included in the module zip files + downloaded by 'go install'.) + +If the arguments don't have version +suffixes, "go install" may run in +module-aware mode or GOPATH mode, +depending on the GO111MODULE environment +variable and the presence of a go.mod +file. See 'go help modules' for details. +If module-aware mode is enabled, +"go install" runs in the context of the +main module. diff --git a/src/go/doc/comment/testdata/text9.txt b/src/go/doc/comment/testdata/text9.txt new file mode 100644 index 0000000000..07a64aa227 --- /dev/null +++ b/src/go/doc/comment/testdata/text9.txt @@ -0,0 +1,12 @@ +{"TextPrefix":"|", "TextCodePrefix": "@"} +-- input -- +Hello, world + Code block here. +-- gofmt -- +Hello, world + + Code block here. +-- text -- +|Hello, world +| +@Code block here. diff --git a/src/go/doc/comment/testdata/words.txt b/src/go/doc/comment/testdata/words.txt new file mode 100644 index 0000000000..63c7e1a1b2 --- /dev/null +++ b/src/go/doc/comment/testdata/words.txt @@ -0,0 +1,10 @@ +-- input -- +This is an italicword and a linkedword and Unicöde. +-- gofmt -- +This is an italicword and a linkedword and Unicöde. +-- text -- +This is an italicword and a linkedword and Unicöde. +-- markdown -- +This is an *italicword* and a [*linkedword*](https://example.com/linkedword) and Unicöde. +-- html -- +

This is an italicword and a linkedword and Unicöde. diff --git a/src/go/doc/comment/testdata_test.go b/src/go/doc/comment/testdata_test.go new file mode 100644 index 0000000000..0676d864b2 --- /dev/null +++ b/src/go/doc/comment/testdata_test.go @@ -0,0 +1,202 @@ +// Copyright 2022 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 comment + +import ( + "bytes" + "encoding/json" + "fmt" + "internal/diff" + "internal/txtar" + "path/filepath" + "strings" + "testing" +) + +func TestTestdata(t *testing.T) { + files, _ := filepath.Glob("testdata/*.txt") + if len(files) == 0 { + t.Fatalf("no testdata") + } + var p Parser + p.Words = map[string]string{ + "italicword": "", + "linkedword": "https://example.com/linkedword", + } + p.LookupPackage = func(name string) (importPath string, ok bool) { + if name == "comment" { + return "go/doc/comment", true + } + return DefaultLookupPackage(name) + } + p.LookupSym = func(recv, name string) (ok bool) { + if recv == "Parser" && name == "Parse" || + recv == "" && name == "Doc" || + recv == "" && name == "NoURL" { + return true + } + return false + } + + stripDollars := func(b []byte) []byte { + // Remove trailing $ on lines. + // They make it easier to see lines with trailing spaces, + // as well as turning them into lines without trailing spaces, + // in case editors remove trailing spaces. + return bytes.ReplaceAll(b, []byte("$\n"), []byte("\n")) + } + for _, file := range files { + t.Run(filepath.Base(file), func(t *testing.T) { + var pr Printer + a, err := txtar.ParseFile(file) + if err != nil { + t.Fatal(err) + } + if len(a.Comment) > 0 { + err := json.Unmarshal(a.Comment, &pr) + if err != nil { + t.Fatalf("unmarshalling top json: %v", err) + } + } + if len(a.Files) < 1 || a.Files[0].Name != "input" { + t.Fatalf("first file is not %q", "input") + } + d := p.Parse(string(stripDollars(a.Files[0].Data))) + for _, f := range a.Files[1:] { + want := stripDollars(f.Data) + for len(want) >= 2 && want[len(want)-1] == '\n' && want[len(want)-2] == '\n' { + want = want[:len(want)-1] + } + var out []byte + switch f.Name { + default: + t.Fatalf("unknown output file %q", f.Name) + case "dump": + out = dump(d) + case "gofmt": + out = pr.Comment(d) + case "html": + out = pr.HTML(d) + case "markdown": + out = pr.Markdown(d) + case "text": + out = pr.Text(d) + } + if string(out) != string(want) { + t.Errorf("%s: %s", file, diff.Diff(f.Name, want, "have", out)) + } + } + }) + } +} + +func dump(d *Doc) []byte { + var out bytes.Buffer + dumpTo(&out, 0, d) + return out.Bytes() +} + +func dumpTo(out *bytes.Buffer, indent int, x any) { + switch x := x.(type) { + default: + fmt.Fprintf(out, "?%T", x) + + case *Doc: + fmt.Fprintf(out, "Doc") + dumpTo(out, indent+1, x.Content) + if len(x.Links) > 0 { + dumpNL(out, indent+1) + fmt.Fprintf(out, "Links") + dumpTo(out, indent+2, x.Links) + } + fmt.Fprintf(out, "\n") + + case []*LinkDef: + for _, def := range x { + dumpNL(out, indent) + dumpTo(out, indent, def) + } + + case *LinkDef: + fmt.Fprintf(out, "LinkDef Used:%v Text:%q URL:%s", x.Used, x.Text, x.URL) + + case []Block: + for _, blk := range x { + dumpNL(out, indent) + dumpTo(out, indent, blk) + } + + case *Heading: + fmt.Fprintf(out, "Heading") + dumpTo(out, indent+1, x.Text) + + case *List: + fmt.Fprintf(out, "List ForceBlankBefore=%v ForceBlankBetween=%v", x.ForceBlankBefore, x.ForceBlankBetween) + dumpTo(out, indent+1, x.Items) + + case []*ListItem: + for _, item := range x { + dumpNL(out, indent) + dumpTo(out, indent, item) + } + + case *ListItem: + fmt.Fprintf(out, "Item Number=%q", x.Number) + dumpTo(out, indent+1, x.Content) + + case *Paragraph: + fmt.Fprintf(out, "Paragraph") + dumpTo(out, indent+1, x.Text) + + case *Code: + fmt.Fprintf(out, "Code") + dumpTo(out, indent+1, x.Text) + + case []Text: + for _, t := range x { + dumpNL(out, indent) + dumpTo(out, indent, t) + } + + case Plain: + if !strings.Contains(string(x), "\n") { + fmt.Fprintf(out, "Plain %q", string(x)) + } else { + fmt.Fprintf(out, "Plain") + dumpTo(out, indent+1, string(x)) + } + + case Italic: + if !strings.Contains(string(x), "\n") { + fmt.Fprintf(out, "Italic %q", string(x)) + } else { + fmt.Fprintf(out, "Italic") + dumpTo(out, indent+1, string(x)) + } + + case string: + for _, line := range strings.SplitAfter(x, "\n") { + if line != "" { + dumpNL(out, indent) + fmt.Fprintf(out, "%q", line) + } + } + + case *Link: + fmt.Fprintf(out, "Link %q", x.URL) + dumpTo(out, indent+1, x.Text) + + case *DocLink: + fmt.Fprintf(out, "DocLink pkg:%q, recv:%q, name:%q", x.ImportPath, x.Recv, x.Name) + dumpTo(out, indent+1, x.Text) + } +} + +func dumpNL(out *bytes.Buffer, n int) { + out.WriteByte('\n') + for i := 0; i < n; i++ { + out.WriteByte('\t') + } +} diff --git a/src/go/doc/comment/text.go b/src/go/doc/comment/text.go new file mode 100644 index 0000000000..e9684f066b --- /dev/null +++ b/src/go/doc/comment/text.go @@ -0,0 +1,338 @@ +// Copyright 2022 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 comment + +import ( + "bytes" + "fmt" + "sort" + "strings" + "unicode/utf8" +) + +// A textPrinter holds the state needed for printing a Doc as plain text. +type textPrinter struct { + *Printer + long strings.Builder + prefix string + codePrefix string + width int +} + +// Text returns a textual formatting of the Doc. +// See the [Printer] documentation for ways to customize the text output. +func (p *Printer) Text(d *Doc) []byte { + tp := &textPrinter{ + Printer: p, + prefix: p.TextPrefix, + codePrefix: p.TextCodePrefix, + width: p.TextWidth, + } + if tp.codePrefix == "" { + tp.codePrefix = p.TextPrefix + "\t" + } + if tp.width == 0 { + tp.width = 80 - utf8.RuneCountInString(tp.prefix) + } + + var out bytes.Buffer + for i, x := range d.Content { + if i > 0 && blankBefore(x) { + out.WriteString(tp.prefix) + writeNL(&out) + } + tp.block(&out, x) + } + anyUsed := false + for _, def := range d.Links { + if def.Used { + anyUsed = true + break + } + } + if anyUsed { + writeNL(&out) + for _, def := range d.Links { + if def.Used { + fmt.Fprintf(&out, "[%s]: %s\n", def.Text, def.URL) + } + } + } + return out.Bytes() +} + +// writeNL calls out.WriteByte('\n') +// but first trims trailing spaces on the previous line. +func writeNL(out *bytes.Buffer) { + // Trim trailing spaces. + data := out.Bytes() + n := 0 + for n < len(data) && (data[len(data)-n-1] == ' ' || data[len(data)-n-1] == '\t') { + n++ + } + if n > 0 { + out.Truncate(len(data) - n) + } + out.WriteByte('\n') +} + +// block prints the block x to out. +func (p *textPrinter) block(out *bytes.Buffer, x Block) { + switch x := x.(type) { + default: + fmt.Fprintf(out, "?%T\n", x) + + case *Paragraph: + out.WriteString(p.prefix) + p.text(out, "", x.Text) + + case *Heading: + out.WriteString(p.prefix) + out.WriteString("# ") + p.text(out, "", x.Text) + + case *Code: + text := x.Text + for text != "" { + var line string + line, text, _ = strings.Cut(text, "\n") + if line != "" { + out.WriteString(p.codePrefix) + out.WriteString(line) + } + writeNL(out) + } + + case *List: + loose := x.BlankBetween() + for i, item := range x.Items { + if i > 0 && loose { + out.WriteString(p.prefix) + writeNL(out) + } + out.WriteString(p.prefix) + out.WriteString(" ") + if item.Number == "" { + out.WriteString(" - ") + } else { + out.WriteString(item.Number) + out.WriteString(". ") + } + for i, blk := range item.Content { + const fourSpace = " " + if i > 0 { + writeNL(out) + out.WriteString(p.prefix) + out.WriteString(fourSpace) + } + p.text(out, fourSpace, blk.(*Paragraph).Text) + } + } + } +} + +// text prints the text sequence x to out. +// TODO: Wrap lines. +func (p *textPrinter) text(out *bytes.Buffer, indent string, x []Text) { + p.oneLongLine(&p.long, x) + words := strings.Fields(p.long.String()) + p.long.Reset() + + var seq []int + if p.width < 0 { + seq = []int{0, len(words)} // one long line + } else { + seq = wrap(words, p.width-utf8.RuneCountInString(indent)) + } + for i := 0; i+1 < len(seq); i++ { + if i > 0 { + out.WriteString(p.prefix) + out.WriteString(indent) + } + for j, w := range words[seq[i]:seq[i+1]] { + if j > 0 { + out.WriteString(" ") + } + out.WriteString(w) + } + writeNL(out) + } +} + +// oneLongLine prints the text sequence x to out as one long line, +// without worrying about line wrapping. +// Explicit links have the [ ] dropped to improve readability. +func (p *textPrinter) oneLongLine(out *strings.Builder, x []Text) { + for _, t := range x { + switch t := t.(type) { + case Plain: + out.WriteString(string(t)) + case Italic: + out.WriteString(string(t)) + case *Link: + p.oneLongLine(out, t.Text) + case *DocLink: + p.oneLongLine(out, t.Text) + } + } +} + +// wrap wraps words into lines of at most max runes, +// minimizing the sum of the squares of the leftover lengths +// at the end of each line (except the last, of course), +// with a preference for ending lines at punctuation (.,:;). +// +// The returned slice gives the indexes of the first words +// on each line in the wrapped text with a final entry of len(words). +// Thus the lines are words[seq[0]:seq[1]], words[seq[1]:seq[2]], +// ..., words[seq[len(seq)-2]:seq[len(seq)-1]]. +// +// The implementation runs in O(n log n) time, where n = len(words), +// using the algorithm described in D. S. Hirschberg and L. L. Larmore, +// “[The least weight subsequence problem],” FOCS 1985, pp. 137-143. +// +// [The least weight subsequence problem]: https://doi.org/10.1109/SFCS.1985.60 +func wrap(words []string, max int) (seq []int) { + // The algorithm requires that our scoring function be concave, + // meaning that for all i₀ ≤ i₁ < j₀ ≤ j₁, + // weight(i₀, j₀) + weight(i₁, j₁) ≤ weight(i₀, j₁) + weight(i₁, j₀). + // + // Our weights are two-element pairs [hi, lo] + // ordered by elementwise comparison. + // The hi entry counts the weight for lines that are longer than max, + // and the lo entry counts the weight for lines that are not. + // This forces the algorithm to first minimize the number of lines + // that are longer than max, which correspond to lines with + // single very long words. Having done that, it can move on to + // minimizing the lo score, which is more interesting. + // + // The lo score is the sum for each line of the square of the + // number of spaces remaining at the end of the line and a + // penalty of 64 given out for not ending the line in a + // punctuation character (.,:;). + // The penalty is somewhat arbitrarily chosen by trying + // different amounts and judging how nice the wrapped text looks. + // Roughly speaking, using 64 means that we are willing to + // end a line with eight blank spaces in order to end at a + // punctuation character, even if the next word would fit in + // those spaces. + // + // We care about ending in punctuation characters because + // it makes the text easier to skim if not too many sentences + // or phrases begin with a single word on the previous line. + + // A score is the score (also called weight) for a given line. + // add and cmp add and compare scores. + type score struct { + hi int64 + lo int64 + } + add := func(s, t score) score { return score{s.hi + t.hi, s.lo + t.lo} } + cmp := func(s, t score) int { + switch { + case s.hi < t.hi: + return -1 + case s.hi > t.hi: + return +1 + case s.lo < t.lo: + return -1 + case s.lo > t.lo: + return +1 + } + return 0 + } + + // total[j] is the total number of runes + // (including separating spaces) in words[:j]. + total := make([]int, len(words)+1) + total[0] = 0 + for i, s := range words { + total[1+i] = total[i] + utf8.RuneCountInString(s) + 1 + } + + // weight returns weight(i, j). + weight := func(i, j int) score { + // On the last line, there is zero weight for being too short. + n := total[j] - 1 - total[i] + if j == len(words) && n <= max { + return score{0, 0} + } + + // Otherwise the weight is the penalty plus the square of the number of + // characters remaining on the line or by which the line goes over. + // In the latter case, that value goes in the hi part of the score. + // (See note above.) + p := wrapPenalty(words[j-1]) + v := int64(max-n) * int64(max-n) + if n > max { + return score{v, p} + } + return score{0, v + p} + } + + // The rest of this function is “The Basic Algorithm” from + // Hirschberg and Larmore's conference paper, + // using the same names as in the paper. + f := []score{{0, 0}} + g := func(i, j int) score { return add(f[i], weight(i, j)) } + + bridge := func(a, b, c int) bool { + k := c + sort.Search(len(words)+1-c, func(k int) bool { + k += c + return cmp(g(a, k), g(b, k)) > 0 + }) + if k > len(words) { + return true + } + return cmp(g(c, k), g(b, k)) <= 0 + } + + // d is a one-ended deque implemented as a slice. + d := make([]int, 1, len(words)) + d[0] = 0 + bestleft := make([]int, 1, len(words)) + bestleft[0] = -1 + for m := 1; m < len(words); m++ { + f = append(f, g(d[0], m)) + bestleft = append(bestleft, d[0]) + for len(d) > 1 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 { + d = d[1:] // “Retire” + } + for len(d) > 1 && bridge(d[len(d)-2], d[len(d)-1], m) { + d = d[:len(d)-1] // “Fire” + } + if cmp(g(m, len(words)), g(d[len(d)-1], len(words))) < 0 { + d = append(d, m) // “Hire” + // The next few lines are not in the paper but are necessary + // to handle two-word inputs correctly. It appears to be + // just a bug in the paper's pseudocode. + if len(d) == 2 && cmp(g(d[1], m+1), g(d[0], m+1)) <= 0 { + d = d[1:] + } + } + } + bestleft = append(bestleft, d[0]) + + // Recover least weight sequence from bestleft. + n := 1 + for m := len(words); m > 0; m = bestleft[m] { + n++ + } + seq = make([]int, n) + for m := len(words); m > 0; m = bestleft[m] { + n-- + seq[n] = m + } + return seq +} + +// wrapPenalty is the penalty for inserting a line break after word s. +func wrapPenalty(s string) int64 { + switch s[len(s)-1] { + case '.', ',', ':', ';': + return 0 + } + return 64 +} diff --git a/src/go/doc/comment/wrap_test.go b/src/go/doc/comment/wrap_test.go new file mode 100644 index 0000000000..f9802c9c44 --- /dev/null +++ b/src/go/doc/comment/wrap_test.go @@ -0,0 +1,141 @@ +// Copyright 2022 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 comment + +import ( + "flag" + "fmt" + "math/rand" + "testing" + "time" + "unicode/utf8" +) + +var wrapSeed = flag.Int64("wrapseed", 0, "use `seed` for wrap test (default auto-seeds)") + +func TestWrap(t *testing.T) { + if *wrapSeed == 0 { + *wrapSeed = time.Now().UnixNano() + } + t.Logf("-wrapseed=%#x\n", *wrapSeed) + r := rand.New(rand.NewSource(*wrapSeed)) + + // Generate words of random length. + s := "1234567890αβcdefghijklmnopqrstuvwxyz" + sN := utf8.RuneCountInString(s) + var words []string + for i := 0; i < 100; i++ { + n := 1 + r.Intn(sN-1) + if n >= 12 { + n++ // extra byte for β + } + if n >= 11 { + n++ // extra byte for α + } + words = append(words, s[:n]) + } + + for n := 1; n <= len(words) && !t.Failed(); n++ { + t.Run(fmt.Sprint("n=", n), func(t *testing.T) { + words := words[:n] + t.Logf("words: %v", words) + for max := 1; max < 100 && !t.Failed(); max++ { + t.Run(fmt.Sprint("max=", max), func(t *testing.T) { + seq := wrap(words, max) + + // Compute score for seq. + start := 0 + score := int64(0) + if len(seq) == 0 { + t.Fatalf("wrap seq is empty") + } + if seq[0] != 0 { + t.Fatalf("wrap seq does not start with 0") + } + for _, n := range seq[1:] { + if n <= start { + t.Fatalf("wrap seq is non-increasing: %v", seq) + } + if n > len(words) { + t.Fatalf("wrap seq contains %d > %d: %v", n, len(words), seq) + } + size := -1 + for _, s := range words[start:n] { + size += 1 + utf8.RuneCountInString(s) + } + if n-start == 1 && size >= max { + // no score + } else if size > max { + t.Fatalf("wrap used overlong line %d:%d: %v", start, n, words[start:n]) + } else if n != len(words) { + score += int64(max-size)*int64(max-size) + wrapPenalty(words[n-1]) + } + start = n + } + if start != len(words) { + t.Fatalf("wrap seq does not use all words (%d < %d): %v", start, len(words), seq) + } + + // Check that score matches slow reference implementation. + slowSeq, slowScore := wrapSlow(words, max) + if score != slowScore { + t.Fatalf("wrap score = %d != wrapSlow score %d\nwrap: %v\nslow: %v", score, slowScore, seq, slowSeq) + } + }) + } + }) + } +} + +// wrapSlow is an O(n²) reference implementation for wrap. +// It returns a minimal-score sequence along with the score. +// It is OK if wrap returns a different sequence as long as that +// sequence has the same score. +func wrapSlow(words []string, max int) (seq []int, score int64) { + // Quadratic dynamic programming algorithm for line wrapping problem. + // best[i] tracks the best score possible for words[:i], + // assuming that for i < len(words) the line breaks after those words. + // bestleft[i] tracks the previous line break for best[i]. + best := make([]int64, len(words)+1) + bestleft := make([]int, len(words)+1) + best[0] = 0 + for i, w := range words { + if utf8.RuneCountInString(w) >= max { + // Overlong word must appear on line by itself. No effect on score. + best[i+1] = best[i] + continue + } + best[i+1] = 1e18 + p := wrapPenalty(w) + n := -1 + for j := i; j >= 0; j-- { + n += 1 + utf8.RuneCountInString(words[j]) + if n > max { + break + } + line := int64(n-max)*int64(n-max) + p + if i == len(words)-1 { + line = 0 // no score for final line being too short + } + s := best[j] + line + if best[i+1] > s { + best[i+1] = s + bestleft[i+1] = j + } + } + } + + // Recover least weight sequence from bestleft. + n := 1 + for m := len(words); m > 0; m = bestleft[m] { + n++ + } + seq = make([]int, n) + for m := len(words); m > 0; m = bestleft[m] { + n-- + seq[n] = m + } + return seq, best[len(words)] +} diff --git a/src/go/doc/comment_test.go b/src/go/doc/comment_test.go index 6d1b209e1e..e1e5f15bdf 100644 --- a/src/go/doc/comment_test.go +++ b/src/go/doc/comment_test.go @@ -1,4 +1,4 @@ -// Copyright 2011 The Go Authors. All rights reserved. +// Copyright 2022 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. @@ -6,242 +6,62 @@ package doc import ( "bytes" - "reflect" - "strings" + "go/parser" + "go/token" + "internal/diff" "testing" ) -var headingTests = []struct { - line string - ok bool -}{ - {"Section", true}, - {"A typical usage", true}, - {"ΔΛΞ is Greek", true}, - {"Foo 42", true}, - {"", false}, - {"section", false}, - {"A typical usage:", false}, - {"This code:", false}, - {"δ is Greek", false}, - {"Foo §", false}, - {"Fermat's Last Sentence", true}, - {"Fermat's", true}, - {"'sX", false}, - {"Ted 'Too' Bar", false}, - {"Use n+m", false}, - {"Scanning:", false}, - {"N:M", false}, -} - -func TestIsHeading(t *testing.T) { - for _, tt := range headingTests { - if h := heading(tt.line); (len(h) > 0) != tt.ok { - t.Errorf("isHeading(%q) = %v, want %v", tt.line, h, tt.ok) - } +func TestComment(t *testing.T) { + fset := token.NewFileSet() + pkgs, err := parser.ParseDir(fset, "testdata/pkgdoc", nil, parser.ParseComments) + if err != nil { + t.Fatal(err) } -} - -var blocksTests = []struct { - in string - out []block - text string -}{ - { - in: `Para 1. -Para 1 line 2. - -Para 2. - -Section - -Para 3. - - pre - pre1 - -Para 4. - - pre - pre1 - - pre2 - -Para 5. - - - pre - - - pre1 - pre2 - -Para 6. - pre - pre2 -`, - out: []block{ - {opPara, []string{"Para 1.\n", "Para 1 line 2.\n"}}, - {opPara, []string{"Para 2.\n"}}, - {opHead, []string{"Section"}}, - {opPara, []string{"Para 3.\n"}}, - {opPre, []string{"pre\n", "pre1\n"}}, - {opPara, []string{"Para 4.\n"}}, - {opPre, []string{"pre\n", "pre1\n", "\n", "pre2\n"}}, - {opPara, []string{"Para 5.\n"}}, - {opPre, []string{"pre\n", "\n", "\n", "pre1\n", "pre2\n"}}, - {opPara, []string{"Para 6.\n"}}, - {opPre, []string{"pre\n", "pre2\n"}}, - }, - text: `. Para 1. Para 1 line 2. - -. Para 2. - - -. Section - -. Para 3. - -$ pre -$ pre1 - -. Para 4. - -$ pre -$ pre1 - -$ pre2 - -. Para 5. - -$ pre - - -$ pre1 -$ pre2 - -. Para 6. - -$ pre -$ pre2 -`, - }, - { - in: "Para.\n\tshould not be ``escaped''", - out: []block{ - {opPara, []string{"Para.\n"}}, - {opPre, []string{"should not be ``escaped''"}}, - }, - text: ". Para.\n\n$ should not be ``escaped''", - }, - { - in: "// A very long line of 46 char for line wrapping.", - out: []block{ - {opPara, []string{"// A very long line of 46 char for line wrapping."}}, - }, - text: `. // A very long line of 46 char for line -. // wrapping. -`, - }, - { - in: `/* A very long line of 46 char for line wrapping. -A very long line of 46 char for line wrapping. */`, - out: []block{ - {opPara, []string{"/* A very long line of 46 char for line wrapping.\n", "A very long line of 46 char for line wrapping. */"}}, - }, - text: `. /* A very long line of 46 char for line -. wrapping. A very long line of 46 char -. for line wrapping. */ -`, - }, - { - in: `A line of 36 char for line wrapping. -//Another line starting with //`, - out: []block{ - {opPara, []string{"A line of 36 char for line wrapping.\n", - "//Another line starting with //"}}, - }, - text: `. A line of 36 char for line wrapping. -. //Another line starting with // -`, - }, -} - -func TestBlocks(t *testing.T) { - for i, tt := range blocksTests { - b := blocks(tt.in) - if !reflect.DeepEqual(b, tt.out) { - t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, b, tt.out) - } + if pkgs["pkgdoc"] == nil { + t.Fatal("missing package pkgdoc") + } + pkg := New(pkgs["pkgdoc"], "testdata/pkgdoc", 0) + + var ( + input = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things.\n" + wantHTML = `

T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and crand.Reader are things.` + "\n" + wantOldHTML = "

[T] and [U] are types, and [T.M] is a method, but [V] is a broken link. [rand.Int] and [crand.Reader] are things.\n" + wantMarkdown = "[T](#T) and [U](#U) are types, and [T.M](#T.M) is a method, but \\[V] is a broken link. [rand.Int](/math/rand#Int) and [crand.Reader](/crypto/rand#Reader) are things.\n" + wantText = "T and U are types, and T.M is a method, but [V] is a broken link. rand.Int and\ncrand.Reader are things.\n" + wantOldText = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link.\n[rand.Int] and [crand.Reader] are things.\n" + wantSynopsis = "T and U are types, and T.M is a method, but [V] is a broken link." + wantOldSynopsis = "[T] and [U] are types, and [T.M] is a method, but [V] is a broken link." + ) + + if b := pkg.HTML(input); string(b) != wantHTML { + t.Errorf("%s", diff.Diff("pkg.HTML", b, "want", []byte(wantHTML))) + } + if b := pkg.Markdown(input); string(b) != wantMarkdown { + t.Errorf("%s", diff.Diff("pkg.Markdown", b, "want", []byte(wantMarkdown))) + } + if b := pkg.Text(input); string(b) != wantText { + t.Errorf("%s", diff.Diff("pkg.Text", b, "want", []byte(wantText))) + } + if b := pkg.Synopsis(input); b != wantSynopsis { + t.Errorf("%s", diff.Diff("pkg.Synopsis", []byte(b), "want", []byte(wantText))) } -} -func TestToText(t *testing.T) { var buf bytes.Buffer - for i, tt := range blocksTests { - ToText(&buf, tt.in, ". ", "$\t", 40) - if have := buf.String(); have != tt.text { - t.Errorf("#%d: mismatch\nhave: %s\nwant: %s\nhave vs want:\n%q\n%q", i, have, tt.text, have, tt.text) - } - buf.Reset() - } -} -var emphasizeTests = []struct { - in, out string -}{ - {"", ""}, - {"http://[::1]:8080/foo.txt", `http://[::1]:8080/foo.txt`}, - {"before (https://www.google.com) after", `before (https://www.google.com) after`}, - {"before https://www.google.com:30/x/y/z:b::c. After", `before https://www.google.com:30/x/y/z:b::c. After`}, - {"http://www.google.com/path/:;!-/?query=%34b#093124", `http://www.google.com/path/:;!-/?query=%34b#093124`}, - {"http://www.google.com/path/:;!-/?query=%34bar#093124", `http://www.google.com/path/:;!-/?query=%34bar#093124`}, - {"http://www.google.com/index.html! After", `http://www.google.com/index.html! After`}, - {"http://www.google.com/", `http://www.google.com/`}, - {"https://www.google.com/", `https://www.google.com/`}, - {"http://www.google.com/path.", `http://www.google.com/path.`}, - {"http://en.wikipedia.org/wiki/Camellia_(cipher)", `http://en.wikipedia.org/wiki/Camellia_(cipher)`}, - {"(http://www.google.com/)", `(http://www.google.com/)`}, - {"http://gmail.com)", `http://gmail.com)`}, - {"((http://gmail.com))", `((http://gmail.com))`}, - {"http://gmail.com ((http://gmail.com)) ()", `http://gmail.com ((http://gmail.com)) ()`}, - {"Foo bar http://example.com/ quux!", `Foo bar http://example.com/ quux!`}, - {"Hello http://example.com/%2f/ /world.", `Hello http://example.com/%2f/ /world.`}, - {"Lorem http: ipsum //host/path", "Lorem http: ipsum //host/path"}, - {"javascript://is/not/linked", "javascript://is/not/linked"}, - {"http://foo", `http://foo`}, - {"art by [[https://www.example.com/person/][Person Name]]", `art by [[https://www.example.com/person/][Person Name]]`}, - {"please visit (http://golang.org/)", `please visit (http://golang.org/)`}, - {"please visit http://golang.org/hello())", `please visit http://golang.org/hello())`}, - {"http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD", `http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD`}, - {"https://foo.bar/bal/x(])", `https://foo.bar/bal/x(])`}, // inner ] causes (]) to be cut off from URL - {"foo [ http://bar(])", `foo [ http://bar(])`}, // outer [ causes ]) to be cut off from URL -} + buf.Reset() + ToHTML(&buf, input, map[string]string{"types": ""}) + if b := buf.Bytes(); string(b) != wantOldHTML { + t.Errorf("%s", diff.Diff("ToHTML", b, "want", []byte(wantOldHTML))) + } -func TestEmphasize(t *testing.T) { - for i, tt := range emphasizeTests { - var buf bytes.Buffer - emphasize(&buf, tt.in, nil, true) - out := buf.String() - if out != tt.out { - t.Errorf("#%d: mismatch\nhave: %v\nwant: %v", i, out, tt.out) - } + buf.Reset() + ToText(&buf, input, "", "\t", 80) + if b := buf.Bytes(); string(b) != wantOldText { + t.Errorf("%s", diff.Diff("ToText", b, "want", []byte(wantOldText))) } -} -func TestCommentEscape(t *testing.T) { - commentTests := []struct { - in, out string - }{ - {"typically invoked as ``go tool asm'',", "typically invoked as " + ldquo + "go tool asm" + rdquo + ","}, - {"For more detail, run ``go help test'' and ``go help testflag''", "For more detail, run " + ldquo + "go help test" + rdquo + " and " + ldquo + "go help testflag" + rdquo}, - } - for i, tt := range commentTests { - var buf strings.Builder - commentEscape(&buf, tt.in, true) - out := buf.String() - if out != tt.out { - t.Errorf("#%d: mismatch\nhave: %q\nwant: %q", i, out, tt.out) - } + if b := Synopsis(input); b != wantOldSynopsis { + t.Errorf("%s", diff.Diff("Synopsis", []byte(b), "want", []byte(wantOldText))) } } diff --git a/src/go/doc/doc.go b/src/go/doc/doc.go index f0c1b5dd32..651a2c1f6c 100644 --- a/src/go/doc/doc.go +++ b/src/go/doc/doc.go @@ -8,6 +8,7 @@ package doc import ( "fmt" "go/ast" + "go/doc/comment" "go/token" "strings" ) @@ -35,6 +36,9 @@ type Package struct { // the package. Examples are extracted from _test.go files // provided to NewFromFiles. Examples []*Example + + importByName map[string]string + syms map[string]bool } // Value is the documentation for a (possibly grouped) var or const declaration. @@ -119,7 +123,7 @@ func New(pkg *ast.Package, importPath string, mode Mode) *Package { r.readPackage(pkg, mode) r.computeMethodSets() r.cleanupTypes() - return &Package{ + p := &Package{ Doc: r.doc, Name: pkg.Name, ImportPath: importPath, @@ -131,6 +135,48 @@ func New(pkg *ast.Package, importPath string, mode Mode) *Package { Types: sortedTypes(r.types, mode&AllMethods != 0), Vars: sortedValues(r.values, token.VAR), Funcs: sortedFuncs(r.funcs, true), + + importByName: r.importByName, + syms: make(map[string]bool), + } + + p.collectValues(p.Consts) + p.collectValues(p.Vars) + p.collectTypes(p.Types) + p.collectFuncs(p.Funcs) + + return p +} + +func (p *Package) collectValues(values []*Value) { + for _, v := range values { + for _, name := range v.Names { + p.syms[name] = true + } + } +} + +func (p *Package) collectTypes(types []*Type) { + for _, t := range types { + if p.syms[t.Name] { + // Shouldn't be any cycles but stop just in case. + continue + } + p.syms[t.Name] = true + p.collectValues(t.Consts) + p.collectValues(t.Vars) + p.collectFuncs(t.Funcs) + p.collectFuncs(t.Methods) + } +} + +func (p *Package) collectFuncs(funcs []*Func) { + for _, f := range funcs { + if f.Recv != "" { + p.syms[strings.TrimPrefix(f.Recv, "*")+"."+f.Name] = true + } else { + p.syms[f.Name] = true + } } } @@ -218,3 +264,87 @@ func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, e } return pkg, nil } + +// lookupSym reports whether the package has a given symbol or method. +// +// If recv == "", HasSym reports whether the package has a top-level +// const, func, type, or var named name. +// +// If recv != "", HasSym reports whether the package has a type +// named recv with a method named name. +func (p *Package) lookupSym(recv, name string) bool { + if recv != "" { + return p.syms[recv+"."+name] + } + return p.syms[name] +} + +// lookupPackage returns the import path identified by name +// in the given package. If name uniquely identifies a single import, +// then lookupPackage returns that import. +// If multiple packages are imported as name, importPath returns "", false. +// Otherwise, if name is the name of p itself, importPath returns "", true, +// to signal a reference to p. +// Otherwise, importPath returns "", false. +func (p *Package) lookupPackage(name string) (importPath string, ok bool) { + if path, ok := p.importByName[name]; ok { + if path == "" { + return "", false // multiple imports used the name + } + return path, true // found import + } + if p.Name == name { + return "", true // allow reference to this package + } + return "", false // unknown name +} + +// Parser returns a doc comment parser configured +// for parsing doc comments from package p. +// Each call returns a new parser, so that the caller may +// customize it before use. +func (p *Package) Parser() *comment.Parser { + return &comment.Parser{ + LookupPackage: p.lookupPackage, + LookupSym: p.lookupSym, + } +} + +// Printer returns a doc comment printer configured +// for printing doc comments from package p. +// Each call returns a new printer, so that the caller may +// customize it before use. +func (p *Package) Printer() *comment.Printer { + // No customization today, but having p.Printer() + // gives us flexibility in the future, and it is convenient for callers. + return &comment.Printer{} +} + +// HTML returns formatted HTML for the doc comment text. +// +// To customize details of the HTML, use [Package.Printer] +// to obtain a [comment.Printer], and configure it +// before calling its HTML method. +func (p *Package) HTML(text string) []byte { + return p.Printer().HTML(p.Parser().Parse(text)) +} + +// Markdown returns formatted Markdown for the doc comment text. +// +// To customize details of the Markdown, use [Package.Printer] +// to obtain a [comment.Printer], and configure it +// before calling its Markdown method. +func (p *Package) Markdown(text string) []byte { + return p.Printer().Markdown(p.Parser().Parse(text)) +} + +// Text returns formatted text for the doc comment text, +// wrapped to 80 Unicode code points and using tabs for +// code block indentation. +// +// To customize details of the formatting, use [Package.Printer] +// to obtain a [comment.Printer], and configure it +// before calling its Text method. +func (p *Package) Text(text string) []byte { + return p.Printer().Text(p.Parser().Parse(text)) +} diff --git a/src/go/doc/doc_test.go b/src/go/doc/doc_test.go index 5a5fbd8bf3..b79087e538 100644 --- a/src/go/doc/doc_test.go +++ b/src/go/doc/doc_test.go @@ -152,15 +152,6 @@ func Test(t *testing.T) { t.Run("AllMethods", func(t *testing.T) { test(t, AllMethods) }) } -func TestAnchorID(t *testing.T) { - const in = "Important Things 2 Know & Stuff" - const want = "hdr-Important_Things_2_Know___Stuff" - got := anchorID(in) - if got != want { - t.Errorf("anchorID(%q) = %q; want %q", in, got, want) - } -} - func TestFuncs(t *testing.T) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments) diff --git a/src/go/doc/example.go b/src/go/doc/example.go index fcd59e100a..d1b5224b37 100644 --- a/src/go/doc/example.go +++ b/src/go/doc/example.go @@ -456,10 +456,10 @@ func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.Comm // // The classification process is ambiguous in some cases: // -// - ExampleFoo_Bar matches a type named Foo_Bar -// or a method named Foo.Bar. -// - ExampleFoo_bar matches a type named Foo_bar -// or Foo (with a "bar" suffix). +// - ExampleFoo_Bar matches a type named Foo_Bar +// or a method named Foo.Bar. +// - ExampleFoo_bar matches a type named Foo_bar +// or Foo (with a "bar" suffix). // // Examples with malformed names are not associated with anything. func classifyExamples(p *Package, examples []*Example) { diff --git a/src/go/doc/reader.go b/src/go/doc/reader.go index c591059e5c..492e039703 100644 --- a/src/go/doc/reader.go +++ b/src/go/doc/reader.go @@ -9,9 +9,12 @@ import ( "go/ast" "go/token" "internal/lazyregexp" + "path" "sort" "strconv" "strings" + "unicode" + "unicode/utf8" ) // ---------------------------------------------------------------------------- @@ -178,13 +181,16 @@ type reader struct { filenames []string notes map[string][]*Note + // imports + imports map[string]int + hasDotImp bool // if set, package contains a dot import + importByName map[string]string + // declarations - imports map[string]int - hasDotImp bool // if set, package contains a dot import - values []*Value // consts and vars - order int // sort order of const and var declarations (when we can't use a name) - types map[string]*namedType - funcs methodSet + values []*Value // consts and vars + order int // sort order of const and var declarations (when we can't use a name) + types map[string]*namedType + funcs methodSet // support for package-local shadowing of predeclared types shadowedPredecl map[string]bool @@ -485,6 +491,28 @@ var ( noteCommentRx = lazyregexp.New(`^/[/*][ \t]*` + noteMarker) // MARKER(uid) at comment start ) +// clean replaces each sequence of space, \r, or \t characters +// with a single space and removes any trailing and leading spaces. +func clean(s string) string { + var b []byte + p := byte(' ') + for i := 0; i < len(s); i++ { + q := s[i] + if q == '\r' || q == '\t' { + q = ' ' + } + if q != ' ' || p != ' ' { + b = append(b, q) + p = q + } + } + // remove trailing blank, if any + if n := len(b); n > 0 && p == ' ' { + b = b[0 : n-1] + } + return string(b) +} + // readNote collects a single note from a sequence of comments. func (r *reader) readNote(list []*ast.Comment) { text := (&ast.CommentGroup{List: list}).Text() @@ -493,7 +521,7 @@ func (r *reader) readNote(list []*ast.Comment) { // We remove any formatting so that we don't // get spurious line breaks/indentation when // showing the TODO body. - body := clean(text[m[1]:], keepNL) + body := clean(text[m[1]:]) if body != "" { marker := text[m[2]:m[3]] r.notes[marker] = append(r.notes[marker], &Note{ @@ -550,8 +578,23 @@ func (r *reader) readFile(src *ast.File) { if s, ok := spec.(*ast.ImportSpec); ok { if import_, err := strconv.Unquote(s.Path.Value); err == nil { r.imports[import_] = 1 - if s.Name != nil && s.Name.Name == "." { - r.hasDotImp = true + var name string + if s.Name != nil { + name = s.Name.Name + if name == "." { + r.hasDotImp = true + } + } + if name != "." { + if name == "" { + name = assumedPackageName(import_) + } + old, ok := r.importByName[name] + if !ok { + r.importByName[name] = import_ + } else if old != import_ && old != "" { + r.importByName[name] = "" // ambiguous + } } } } @@ -611,6 +654,7 @@ func (r *reader) readPackage(pkg *ast.Package, mode Mode) { r.types = make(map[string]*namedType) r.funcs = make(methodSet) r.notes = make(map[string][]*Note) + r.importByName = make(map[string]string) // sort package files before reading them so that the // result does not depend on map iteration order @@ -630,6 +674,12 @@ func (r *reader) readPackage(pkg *ast.Package, mode Mode) { r.readFile(f) } + for name, path := range r.importByName { + if path == "" { + delete(r.importByName, name) + } + } + // process functions now that we have better type information for _, f := range pkg.Files { for _, decl := range f.Decls { @@ -950,3 +1000,30 @@ var predeclaredConstants = map[string]bool{ "nil": true, "true": true, } + +// assumedPackageName returns the assumed package name +// for a given import path. This is a copy of +// golang.org/x/tools/internal/imports.ImportPathToAssumedName. +func assumedPackageName(importPath string) string { + notIdentifier := func(ch rune) bool { + return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || + '0' <= ch && ch <= '9' || + ch == '_' || + ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) + } + + base := path.Base(importPath) + if strings.HasPrefix(base, "v") { + if _, err := strconv.Atoi(base[1:]); err == nil { + dir := path.Dir(importPath) + if dir != "." { + base = path.Base(dir) + } + } + } + base = strings.TrimPrefix(base, "go-") + if i := strings.IndexFunc(base, notIdentifier); i >= 0 { + base = base[:i] + } + return base +} diff --git a/src/go/doc/synopsis.go b/src/go/doc/synopsis.go index ca607cc4e5..3c9e7e9b9e 100644 --- a/src/go/doc/synopsis.go +++ b/src/go/doc/synopsis.go @@ -5,77 +5,74 @@ package doc import ( + "go/doc/comment" "strings" "unicode" ) -// firstSentenceLen returns the length of the first sentence in s. +// firstSentence returns the first sentence in s. // The sentence ends after the first period followed by space and // not preceded by exactly one uppercase letter. -func firstSentenceLen(s string) int { +func firstSentence(s string) string { var ppp, pp, p rune for i, q := range s { if q == '\n' || q == '\r' || q == '\t' { q = ' ' } if q == ' ' && p == '.' && (!unicode.IsUpper(pp) || unicode.IsUpper(ppp)) { - return i + return s[:i] } if p == '。' || p == '.' { - return i + return s[:i] } ppp, pp, p = pp, p, q } - return len(s) -} - -const ( - keepNL = 1 << iota -) - -// clean replaces each sequence of space, \n, \r, or \t characters -// with a single space and removes any trailing and leading spaces. -// If the keepNL flag is set, newline characters are passed through -// instead of being change to spaces. -func clean(s string, flags int) string { - var b []byte - p := byte(' ') - for i := 0; i < len(s); i++ { - q := s[i] - if (flags&keepNL) == 0 && q == '\n' || q == '\r' || q == '\t' { - q = ' ' - } - if q != ' ' || p != ' ' { - b = append(b, q) - p = q - } - } - // remove trailing blank, if any - if n := len(b); n > 0 && p == ' ' { - b = b[0 : n-1] - } - return string(b) -} - -// Synopsis returns a cleaned version of the first sentence in s. -// That sentence ends after the first period followed by space and -// not preceded by exactly one uppercase letter. The result string -// has no \n, \r, or \t characters and uses only single spaces between -// words. If s starts with any of the IllegalPrefixes, the result -// is the empty string. -func Synopsis(s string) string { - s = clean(s[0:firstSentenceLen(s)], 0) - for _, prefix := range IllegalPrefixes { - if strings.HasPrefix(strings.ToLower(s), prefix) { - return "" - } - } - s = convertQuotes(s) return s } +// Synopsis returns a cleaned version of the first sentence in text. +// +// Deprecated: New programs should use [Package.Synopsis] instead, +// which handles links in text properly. +func Synopsis(text string) string { + var p Package + return p.Synopsis(text) +} + +// IllegalPrefixes is a list of lower-case prefixes that identify +// a comment as not being a doc comment. +// This helps to avoid misinterpreting the common mistake +// of a copyright notice immediately before a package statement +// as being a doc comment. var IllegalPrefixes = []string{ "copyright", "all rights", "author", } + +// Synopsis returns a cleaned version of the first sentence in text. +// That sentence ends after the first period followed by space and not +// preceded by exactly one uppercase letter, or at the first paragraph break. +// The result string has no \n, \r, or \t characters and uses only single +// spaces between words. If text starts with any of the IllegalPrefixes, +// the result is the empty string. +func (p *Package) Synopsis(text string) string { + text = firstSentence(text) + lower := strings.ToLower(text) + for _, prefix := range IllegalPrefixes { + if strings.HasPrefix(lower, prefix) { + return "" + } + } + pr := p.Printer() + pr.TextWidth = -1 + d := p.Parser().Parse(text) + if len(d.Content) == 0 { + return "" + } + if _, ok := d.Content[0].(*comment.Paragraph); !ok { + return "" + } + d.Content = d.Content[:1] // might be blank lines, code blocks, etc in “first sentence” + return strings.TrimSpace(string(pr.Text(d))) +} diff --git a/src/go/doc/synopsis_test.go b/src/go/doc/synopsis_test.go index 3f443dc757..158c734bf0 100644 --- a/src/go/doc/synopsis_test.go +++ b/src/go/doc/synopsis_test.go @@ -18,8 +18,8 @@ var tests = []struct { {" foo. ", 6, "foo."}, {" foo\t bar.\n", 12, "foo bar."}, {" foo\t bar.\n", 12, "foo bar."}, - {"a b\n\nc\r\rd\t\t", 12, "a b c d"}, - {"a b\n\nc\r\rd\t\t . BLA", 15, "a b c d ."}, + {"a b\n\nc\r\rd\t\t", 12, "a b"}, + {"a b\n\nc\r\rd\t\t . BLA", 15, "a b"}, {"Package poems by T.S.Eliot. To rhyme...", 27, "Package poems by T.S.Eliot."}, {"Package poems by T. S. Eliot. To rhyme...", 29, "Package poems by T. S. Eliot."}, {"foo implements the foo ABI. The foo ABI is...", 27, "foo implements the foo ABI."}, @@ -35,18 +35,18 @@ var tests = []struct { {"All Rights reserved. Package foo does bar.", 20, ""}, {"All rights reserved. Package foo does bar.", 20, ""}, {"Authors: foo@bar.com. Package foo does bar.", 21, ""}, - {"typically invoked as ``go tool asm'',", 37, "typically invoked as " + ulquo + "go tool asm" + urquo + ","}, + {"typically invoked as ``go tool asm'',", 37, "typically invoked as “go tool asm”,"}, } func TestSynopsis(t *testing.T) { for _, e := range tests { - fsl := firstSentenceLen(e.txt) - if fsl != e.fsl { - t.Errorf("got fsl = %d; want %d for %q\n", fsl, e.fsl, e.txt) + fs := firstSentence(e.txt) + if fs != e.txt[:e.fsl] { + t.Errorf("firstSentence(%q) = %q, want %q", e.txt, fs, e.txt[:e.fsl]) } syn := Synopsis(e.txt) if syn != e.syn { - t.Errorf("got syn = %q; want %q for %q\n", syn, e.syn, e.txt) + t.Errorf("Synopsis(%q) = %q, want %q", e.txt, syn, e.syn) } } } diff --git a/src/go/doc/testdata/pkgdoc/doc.go b/src/go/doc/testdata/pkgdoc/doc.go new file mode 100644 index 0000000000..61bd4e32f9 --- /dev/null +++ b/src/go/doc/testdata/pkgdoc/doc.go @@ -0,0 +1,19 @@ +// Copyright 2022 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 pkgdoc + +import ( + crand "crypto/rand" + "math/rand" +) + +type T int + +type U int + +func (T) M() {} + +var _ = rand.Int +var _ = crand.Reader diff --git a/src/go/doc/testdata/testing.go b/src/go/doc/testdata/testing.go index 80238df283..6365ffceed 100644 --- a/src/go/doc/testdata/testing.go +++ b/src/go/doc/testdata/testing.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package testing provides support for automated testing of Go packages. -// It is intended to be used in concert with the ``go test'' utility, which automates +// It is intended to be used in concert with the “go test” utility, which automates // execution of any function of the form // func TestXxx(*testing.T) // where Xxx can be any alphanumeric string (but the first letter must not be in diff --git a/src/go/format/benchmark_test.go b/src/go/format/benchmark_test.go index d434d6f549..f42aac4c51 100644 --- a/src/go/format/benchmark_test.go +++ b/src/go/format/benchmark_test.go @@ -21,16 +21,17 @@ var debug = flag.Bool("debug", false, "write .src files containing formatting in // array1 generates an array literal with n elements of the form: // // var _ = [...]byte{ -// // 0 -// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, -// 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, -// 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, -// 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, -// 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, -// // 40 -// 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, -// 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, -// ... +// +// // 0 +// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, +// 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +// 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, +// 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, +// 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, +// // 40 +// 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, +// 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, +// ... func array1(buf *bytes.Buffer, n int) { buf.WriteString("var _ = [...]byte{\n") for i := 0; i < n; { diff --git a/src/go/internal/gccgoimporter/parser.go b/src/go/internal/gccgoimporter/parser.go index 10402fe43e..536083ae08 100644 --- a/src/go/internal/gccgoimporter/parser.go +++ b/src/go/internal/gccgoimporter/parser.go @@ -1115,9 +1115,10 @@ func (p *parser) maybeCreatePackage() { } // InitDataDirective = ( "v1" | "v2" | "v3" ) ";" | -// "priority" int ";" | -// "init" { PackageInit } ";" | -// "checksum" unquotedString ";" . +// +// "priority" int ";" | +// "init" { PackageInit } ";" | +// "checksum" unquotedString ";" . func (p *parser) parseInitDataDirective() { if p.tok != scanner.Ident { // unexpected token kind; panic @@ -1168,15 +1169,16 @@ func (p *parser) parseInitDataDirective() { } // Directive = InitDataDirective | -// "package" unquotedString [ unquotedString ] [ unquotedString ] ";" | -// "pkgpath" unquotedString ";" | -// "prefix" unquotedString ";" | -// "import" unquotedString unquotedString string ";" | -// "indirectimport" unquotedString unquotedstring ";" | -// "func" Func ";" | -// "type" Type ";" | -// "var" Var ";" | -// "const" Const ";" . +// +// "package" unquotedString [ unquotedString ] [ unquotedString ] ";" | +// "pkgpath" unquotedString ";" | +// "prefix" unquotedString ";" | +// "import" unquotedString unquotedString string ";" | +// "indirectimport" unquotedString unquotedstring ";" | +// "func" Func ";" | +// "type" Type ";" | +// "var" Var ";" | +// "const" Const ";" . func (p *parser) parseDirective() { if p.tok != scanner.Ident { // unexpected token kind; panic diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index 3eb00e9446..39ba9b33a7 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -13,7 +13,6 @@ // treated like an ordinary parameter list and thus may contain multiple // entries where the spec permits exactly one. Consequently, the corresponding // field in the AST (ast.FuncDecl.Recv) field is not restricted to one entry. -// package parser import ( diff --git a/src/go/printer/comment.go b/src/go/printer/comment.go new file mode 100644 index 0000000000..76dd31efc7 --- /dev/null +++ b/src/go/printer/comment.go @@ -0,0 +1,154 @@ +// Copyright 2022 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 printer + +import ( + "go/ast" + "go/doc/comment" + "strings" +) + +// formatDocComment reformats the doc comment list, +// returning the canonical formatting. +func formatDocComment(list []*ast.Comment) []*ast.Comment { + // Extract comment text (removing comment markers). + var kind, text string + var directives []*ast.Comment + if len(list) == 1 && strings.HasPrefix(list[0].Text, "/*") { + kind = "/*" + text = list[0].Text + if !strings.Contains(text, "\n") || allStars(text) { + // Single-line /* .. */ comment in doc comment position, + // or multiline old-style comment like + // /* + // * Comment + // * text here. + // */ + // Should not happen, since it will not work well as a + // doc comment, but if it does, just ignore: + // reformatting it will only make the situation worse. + return list + } + text = text[2 : len(text)-2] // cut /* and */ + } else if strings.HasPrefix(list[0].Text, "//") { + kind = "//" + var b strings.Builder + for _, c := range list { + if !strings.HasPrefix(c.Text, "//") { + return list + } + // Accumulate //go:build etc lines separately. + if isDirective(c.Text[2:]) { + directives = append(directives, c) + continue + } + b.WriteString(strings.TrimPrefix(c.Text[2:], " ")) + b.WriteString("\n") + } + text = b.String() + } else { + // Not sure what this is, so leave alone. + return list + } + + if text == "" { + return list + } + + // Parse comment and reformat as text. + var p comment.Parser + d := p.Parse(text) + + var pr comment.Printer + text = string(pr.Comment(d)) + + // For /* */ comment, return one big comment with text inside. + slash := list[0].Slash + if kind == "/*" { + c := &ast.Comment{ + Slash: slash, + Text: "/*\n" + text + "*/", + } + return []*ast.Comment{c} + } + + // For // comment, return sequence of // lines. + var out []*ast.Comment + for text != "" { + var line string + line, text, _ = strings.Cut(text, "\n") + if line == "" { + line = "//" + } else if strings.HasPrefix(line, "\t") { + line = "//" + line + } else { + line = "// " + line + } + out = append(out, &ast.Comment{ + Slash: slash, + Text: line, + }) + } + if len(directives) > 0 { + out = append(out, &ast.Comment{ + Slash: slash, + Text: "//", + }) + for _, c := range directives { + out = append(out, &ast.Comment{ + Slash: slash, + Text: c.Text, + }) + } + } + return out +} + +// isDirective reports whether c is a comment directive. +// See go.dev/issue/37974. +// This code is also in go/ast. +func isDirective(c string) bool { + // "//line " is a line directive. + // "//extern " is for gccgo. + // "//export " is for cgo. + // (The // has been removed.) + if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { + return true + } + + // "//[a-z0-9]+:[a-z0-9]" + // (The // has been removed.) + colon := strings.Index(c, ":") + if colon <= 0 || colon+1 >= len(c) { + return false + } + for i := 0; i <= colon+1; i++ { + if i == colon { + continue + } + b := c[i] + if !('a' <= b && b <= 'z' || '0' <= b && b <= '9') { + return false + } + } + return true +} + +// allStars reports whether text is the interior of an +// old-style /* */ comment with a star at the start of each line. +func allStars(text string) bool { + for i := 0; i < len(text); i++ { + if text[i] == '\n' { + j := i + 1 + for j < len(text) && (text[j] == ' ' || text[j] == '\t') { + j++ + } + if j < len(text) && text[j] != '*' { + return false + } + } + } + return true +} diff --git a/src/go/printer/nodes.go b/src/go/printer/nodes.go index f4fbde8ae6..0494f99d24 100644 --- a/src/go/printer/nodes.go +++ b/src/go/printer/nodes.go @@ -125,7 +125,8 @@ const filteredMsg = "contains filtered or unexported fields" // // TODO(gri) Consider rewriting this to be independent of []ast.Expr // so that we can use the algorithm for any kind of list -// (e.g., pass list via a channel over which to range). +// +// (e.g., pass list via a channel over which to range). func (p *printer) exprList(prev0 token.Pos, list []ast.Expr, depth int, mode exprListMode, next0 token.Pos, isIncomplete bool) { if len(list) == 0 { if isIncomplete { @@ -714,6 +715,7 @@ func reduceDepth(depth int) int { // (Algorithm suggestion by Russ Cox.) // // The precedences are: +// // 5 * / % << >> & &^ // 4 + - | ^ // 3 == != < <= > >= @@ -726,24 +728,24 @@ func reduceDepth(depth int) int { // To choose the cutoff, look at the whole expression but excluding primary // expressions (function calls, parenthesized exprs), and apply these rules: // -// 1) If there is a binary operator with a right side unary operand -// that would clash without a space, the cutoff must be (in order): +// 1. If there is a binary operator with a right side unary operand +// that would clash without a space, the cutoff must be (in order): // -// /* 6 -// && 6 -// &^ 6 -// ++ 5 -// -- 5 +// /* 6 +// && 6 +// &^ 6 +// ++ 5 +// -- 5 // -// (Comparison operators always have spaces around them.) +// (Comparison operators always have spaces around them.) // -// 2) If there is a mix of level 5 and level 4 operators, then the cutoff -// is 5 (use spaces to distinguish precedence) in Normal mode -// and 4 (never use spaces) in Compact mode. +// 2. If there is a mix of level 5 and level 4 operators, then the cutoff +// is 5 (use spaces to distinguish precedence) in Normal mode +// and 4 (never use spaces) in Compact mode. // -// 3) If there are no level 4 operators or no level 5 operators, then the -// cutoff is 6 (always use spaces) in Normal mode -// and 4 (never use spaces) in Compact mode. +// 3. If there are no level 4 operators or no level 5 operators, then the +// cutoff is 6 (always use spaces) in Normal mode +// and 4 (never use spaces) in Compact mode. func (p *printer) binaryExpr(x *ast.BinaryExpr, prec1, cutoff, depth int) { prec := x.Op.Precedence() if prec < prec1 { @@ -1483,23 +1485,23 @@ func (p *printer) stmt(stmt ast.Stmt, nextIsRBrace bool) { // // For example, the declaration: // -// const ( -// foobar int = 42 // comment -// x = 7 // comment -// foo -// bar = 991 -// ) +// const ( +// foobar int = 42 // comment +// x = 7 // comment +// foo +// bar = 991 +// ) // // leads to the type/values matrix below. A run of value columns (V) can // be moved into the type column if there is no type for any of the values // in that column (we only move entire columns so that they align properly). // -// matrix formatted result -// matrix -// T V -> T V -> true there is a T and so the type -// - V - V true column must be kept -// - - - - false -// - V V - false V is moved into T column +// matrix formatted result +// matrix +// T V -> T V -> true there is a T and so the type +// - V - V true column must be kept +// - - - - false +// - V V - false V is moved into T column func keepTypeColumn(specs []ast.Spec) []bool { m := make([]bool, len(specs)) diff --git a/src/go/printer/printer.go b/src/go/printer/printer.go index 5014f59ab5..25eec6bd75 100644 --- a/src/go/printer/printer.go +++ b/src/go/printer/printer.go @@ -738,11 +738,28 @@ func (p *printer) containsLinebreak() bool { func (p *printer) intersperseComments(next token.Position, tok token.Token) (wroteNewline, droppedFF bool) { var last *ast.Comment for p.commentBefore(next) { - for _, c := range p.comment.List { + list := p.comment.List + changed := false + if p.lastTok != token.IMPORT && // do not rewrite cgo's import "C" comments + p.posFor(p.comment.Pos()).Column == 1 && + p.posFor(p.comment.End()+1) == next { + // Unindented comment abutting next token position: + // a top-level doc comment. + list = formatDocComment(list) + changed = true + } + for _, c := range list { p.writeCommentPrefix(p.posFor(c.Pos()), next, last, tok) p.writeComment(c) last = c } + // In case list was rewritten, change print state to where + // the original list would have ended. + if len(p.comment.List) > 0 && changed { + last = p.comment.List[len(p.comment.List)-1] + p.pos = p.posFor(last.End()) + p.last = p.pos + } p.nextComment() } diff --git a/src/go/printer/printer_test.go b/src/go/printer/printer_test.go index ad2d86052a..cb62b3e4f3 100644 --- a/src/go/printer/printer_test.go +++ b/src/go/printer/printer_test.go @@ -6,6 +6,7 @@ package printer import ( "bytes" + "errors" "flag" "fmt" "go/ast" @@ -92,8 +93,7 @@ func checkEqual(aname, bname string, a, b []byte) error { if bytes.Equal(a, b) { return nil } - - return fmt.Errorf("diff %s %s\n%s", aname, bname, diff.Diff(aname, a, bname, b)) + return errors.New(string(diff.Diff(aname, a, bname, b))) } func runcheck(t *testing.T, source, golden string, mode checkMode) { diff --git a/src/go/printer/testdata/comments.golden b/src/go/printer/testdata/comments.golden index 1a21fff331..62f37ea091 100644 --- a/src/go/printer/testdata/comments.golden +++ b/src/go/printer/testdata/comments.golden @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. // This is a package for testing comment placement by go/printer. -// package main import "fmt" // fmt @@ -97,6 +96,13 @@ type S3 struct { f3 int // f3 is not exported } +// Here is a comment. +// Here is an accidentally unindented line. +// More comment. +// +//dir:ect ive +type directiveCheck struct{} + // This comment group should be separated // with a newline from the next comment // group. @@ -116,9 +122,7 @@ func f0() { x := pi } -// // This comment should be associated with f1, with one blank line before the comment. -// func f1() { f0() /* 1 */ @@ -688,9 +692,16 @@ func _() { } } +//extern foo +func foo() {} + +//export bar +func bar() {} + // Print line directives correctly. // The following is a legal line directive. +// //line foo:1 func _() { _ = 0 diff --git a/src/go/printer/testdata/comments.input b/src/go/printer/testdata/comments.input index aa428a2aa6..4bdafc3781 100644 --- a/src/go/printer/testdata/comments.input +++ b/src/go/printer/testdata/comments.input @@ -97,6 +97,12 @@ type S3 struct { f3 int // f3 is not exported } +// Here is a comment. +//Here is an accidentally unindented line. +//dir:ect ive +// More comment. +type directiveCheck struct{} + // This comment group should be separated // with a newline from the next comment // group. @@ -616,7 +622,7 @@ func _() { func _() { f(); f() f(); /* comment */ f() - f() /* comment */; f() + f() /* comment */; f() f(); /* a */ /* b */ f() f() /* a */ /* b */; f() f() /* a */; /* b */ f() @@ -663,7 +669,7 @@ func _() { // This way, commas interspersed in lists stay with the respective expression. func f(x/* comment */, y int, z int /* comment */, u, v, w int /* comment */) { f(x /* comment */, y) - f(x /* comment */, + f(x /* comment */, y) f( x /* comment */, @@ -685,6 +691,12 @@ func _() { } } +//extern foo +func foo() {} + +//export bar +func bar() {} + // Print line directives correctly. // The following is a legal line directive. @@ -718,10 +730,10 @@ var lflag bool // -l - disable line directives // Trailing white space in comments should be trimmed func _() { -// This comment has 4 blanks following that should be trimmed: -/* Each line of this comment has blanks or tabs following that should be trimmed: - line 2: - line 3: +// This comment has 4 blanks following that should be trimmed: +/* Each line of this comment has blanks or tabs following that should be trimmed: + line 2: + line 3: */ } diff --git a/src/go/printer/testdata/comments.x b/src/go/printer/testdata/comments.x index ae7729286e..5d088ab2c3 100644 --- a/src/go/printer/testdata/comments.x +++ b/src/go/printer/testdata/comments.x @@ -1,5 +1,4 @@ // This is a package for testing comment placement by go/printer. -// package main // The SZ struct; it is empty. diff --git a/src/go/printer/testdata/comments2.golden b/src/go/printer/testdata/comments2.golden index 8b3a94ddcd..83213d1a9d 100644 --- a/src/go/printer/testdata/comments2.golden +++ b/src/go/printer/testdata/comments2.golden @@ -3,7 +3,6 @@ // license that can be found in the LICENSE file. // This is a package for testing comment placement by go/printer. -// package main // Test cases for idempotent comment formatting (was issue 1835). diff --git a/src/go/printer/testdata/doc.golden b/src/go/printer/testdata/doc.golden new file mode 100644 index 0000000000..7ac241a4bb --- /dev/null +++ b/src/go/printer/testdata/doc.golden @@ -0,0 +1,21 @@ +package p + +/* +Doc comment. + + - List1. + + - List2. +*/ +var X int + +/* erroneous doc comment */ +var Y int + +/* + * Another erroneous + * doc comment. + */ +var Z int + + diff --git a/src/go/printer/testdata/doc.input b/src/go/printer/testdata/doc.input new file mode 100644 index 0000000000..5c057ed2c4 --- /dev/null +++ b/src/go/printer/testdata/doc.input @@ -0,0 +1,20 @@ +package p + +/* +Doc comment. + - List1. + + - List2. +*/ +var X int + +/* erroneous doc comment */ +var Y int + +/* + * Another erroneous + * doc comment. + */ +var Z int + + diff --git a/src/go/scanner/scanner.go b/src/go/scanner/scanner.go index b53de7a427..07e07581f7 100644 --- a/src/go/scanner/scanner.go +++ b/src/go/scanner/scanner.go @@ -5,7 +5,6 @@ // Package scanner implements a scanner for Go source text. // It takes a []byte as source which can then be tokenized // through repeated calls to the Scan method. -// package scanner import ( diff --git a/src/go/token/token.go b/src/go/token/token.go index b691883261..3ae10d823c 100644 --- a/src/go/token/token.go +++ b/src/go/token/token.go @@ -4,7 +4,6 @@ // Package token defines constants representing the lexical tokens of the Go // programming language and basic operations on tokens (printing, predicates). -// package token import ( diff --git a/src/go/types/api.go b/src/go/types/api.go index 04342bfac5..4f63d62713 100644 --- a/src/go/types/api.go +++ b/src/go/types/api.go @@ -23,7 +23,6 @@ // Use Info.Types[expr].Type for the results of type inference. // // For a tutorial, see https://golang.org/s/types-tutorial. -// package types import ( diff --git a/src/go/types/check_test.go b/src/go/types/check_test.go index 88622d6b0c..8765ef2e80 100644 --- a/src/go/types/check_test.go +++ b/src/go/types/check_test.go @@ -319,7 +319,7 @@ func testFiles(t *testing.T, sizes Sizes, filenames []string, srcs [][]byte, man // (and a separating "--"). For instance, to test the package made // of the files foo.go and bar.go, use: // -// go test -run Manual -- foo.go bar.go +// go test -run Manual -- foo.go bar.go // // If no source arguments are provided, the file testdata/manual.go // is used instead. diff --git a/src/go/types/gotype.go b/src/go/types/gotype.go index 5d27bb7a07..e8ff9658da 100644 --- a/src/go/types/gotype.go +++ b/src/go/types/gotype.go @@ -32,9 +32,11 @@ checking packages containing imports with relative import paths files to include for such packages. Usage: + gotype [flags] [path...] The flags are: + -t include local test files in a directory (ignored if -x is provided) -x @@ -47,6 +49,7 @@ The flags are: compiler used for installed packages (gc, gccgo, or source); default: source Flags controlling additional output: + -ast print AST -trace @@ -74,7 +77,6 @@ cmd/compile: To verify the output of a pipe: echo "package foo" | gotype - */ package main diff --git a/src/go/types/infer.go b/src/go/types/infer.go index 32ec5495ee..031850b8da 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -22,11 +22,11 @@ import ( // // Inference proceeds as follows: // -// Starting with given type arguments -// 1) apply FTI (function type inference) with typed arguments, -// 2) apply CTI (constraint type inference), -// 3) apply FTI with untyped function arguments, -// 4) apply CTI. +// Starting with given type arguments +// 1) apply FTI (function type inference) with typed arguments, +// 2) apply CTI (constraint type inference), +// 3) apply FTI with untyped function arguments, +// 4) apply CTI. // // The process stops as soon as all type arguments are known or an error occurs. func (check *Checker) infer(posn positioner, tparams []*TypeParam, targs []Type, params *Tuple, args []*operand) (result []Type) { diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index e38f56c956..70e211d082 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -25,9 +25,9 @@ import ( // The last index entry is the field or method index in the (possibly embedded) // type where the entry was found, either: // -// 1) the list of declared methods of a named type; or -// 2) the list of all methods (method set) of an interface type; or -// 3) the list of fields of a struct type. +// 1. the list of declared methods of a named type; or +// 2. the list of all methods (method set) of an interface type; or +// 3. the list of fields of a struct type. // // The earlier index entries are the indices of the embedded struct fields // traversed to get to the found entry, starting at depth 0. @@ -35,12 +35,12 @@ import ( // If no entry is found, a nil object is returned. In this case, the returned // index and indirect values have the following meaning: // -// - If index != nil, the index sequence points to an ambiguous entry -// (the same name appeared more than once at the same embedding level). +// - If index != nil, the index sequence points to an ambiguous entry +// (the same name appeared more than once at the same embedding level). // -// - If indirect is set, a method with a pointer receiver type was found -// but there was no pointer on the path from the actual receiver type to -// the method's formal receiver base type, nor was the receiver addressable. +// - If indirect is set, a method with a pointer receiver type was found +// but there was no pointer on the path from the actual receiver type to +// the method's formal receiver base type, nor was the receiver addressable. func LookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (obj Object, index []int, indirect bool) { if T == nil { panic("LookupFieldOrMethod on nil type") diff --git a/src/go/types/selection.go b/src/go/types/selection.go index 4e06ab16b4..09c304d378 100644 --- a/src/go/types/selection.go +++ b/src/go/types/selection.go @@ -92,9 +92,9 @@ func (s *Selection) Type() Type { // The last index entry is the field or method index of the type declaring f; // either: // -// 1) the list of declared methods of a named type; or -// 2) the list of methods of an interface type; or -// 3) the list of fields of a struct type. +// 1. the list of declared methods of a named type; or +// 2. the list of methods of an interface type; or +// 3. the list of fields of a struct type. // // The earlier index entries are the indices of the embedded fields implicitly // traversed to get from (the type of) x to f, starting at embedding depth 0. @@ -111,6 +111,7 @@ func (s *Selection) String() string { return SelectionString(s, nil) } // package-level objects, and may be nil. // // Examples: +// // "field (T) f int" // "method (T) f(X) Y" // "method expr (T) f(X) Y" diff --git a/src/go/types/sizes.go b/src/go/types/sizes.go index fd18fc5796..494e045477 100644 --- a/src/go/types/sizes.go +++ b/src/go/types/sizes.go @@ -24,19 +24,19 @@ type Sizes interface { // StdSizes is a convenience type for creating commonly used Sizes. // It makes the following simplifying assumptions: // -// - The size of explicitly sized basic types (int16, etc.) is the -// specified size. -// - The size of strings and interfaces is 2*WordSize. -// - The size of slices is 3*WordSize. -// - The size of an array of n elements corresponds to the size of -// a struct of n consecutive fields of the array's element type. -// - The size of a struct is the offset of the last field plus that -// field's size. As with all element types, if the struct is used -// in an array its size must first be aligned to a multiple of the -// struct's alignment. -// - All other types have size WordSize. -// - Arrays and structs are aligned per spec definition; all other -// types are naturally aligned with a maximum alignment MaxAlign. +// - The size of explicitly sized basic types (int16, etc.) is the +// specified size. +// - The size of strings and interfaces is 2*WordSize. +// - The size of slices is 3*WordSize. +// - The size of an array of n elements corresponds to the size of +// a struct of n consecutive fields of the array's element type. +// - The size of a struct is the offset of the last field plus that +// field's size. As with all element types, if the struct is used +// in an array its size must first be aligned to a multiple of the +// struct's alignment. +// - All other types have size WordSize. +// - Arrays and structs are aligned per spec definition; all other +// types are naturally aligned with a maximum alignment MaxAlign. // // *StdSizes implements Sizes. type StdSizes struct { diff --git a/src/go/types/typeterm.go b/src/go/types/typeterm.go index 13b6ce6d0d..a7b8969627 100644 --- a/src/go/types/typeterm.go +++ b/src/go/types/typeterm.go @@ -6,10 +6,10 @@ package types // A term describes elementary type sets: // -// ∅: (*term)(nil) == ∅ // set of no types (empty set) -// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) -// T: &term{false, T} == {T} // set of type T -// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t +// ∅: (*term)(nil) == ∅ // set of no types (empty set) +// 𝓤: &term{} == 𝓤 // set of all types (𝓤niverse) +// T: &term{false, T} == {T} // set of type T +// ~t: &term{true, t} == {t' | under(t') == t} // set of types with underlying type t type term struct { tilde bool // valid if typ != nil typ Type diff --git a/src/hash/adler32/adler32.go b/src/hash/adler32/adler32.go index e8783e4c39..38d644d1ee 100644 --- a/src/hash/adler32/adler32.go +++ b/src/hash/adler32/adler32.go @@ -5,6 +5,7 @@ // Package adler32 implements the Adler-32 checksum. // // It is defined in RFC 1950: +// // Adler-32 is composed of two sums accumulated per byte: s1 is // the sum of all bytes, s2 is the sum of all s1 values. Both sums // are done modulo 65521. s1 is initialized to 1, s2 to zero. The diff --git a/src/hash/crc32/crc32_amd64.go b/src/hash/crc32/crc32_amd64.go index 7017a89304..6be129f5dd 100644 --- a/src/hash/crc32/crc32_amd64.go +++ b/src/hash/crc32/crc32_amd64.go @@ -18,11 +18,13 @@ import ( // castagnoliSSE42 is defined in crc32_amd64.s and uses the SSE 4.2 CRC32 // instruction. +// //go:noescape func castagnoliSSE42(crc uint32, p []byte) uint32 // castagnoliSSE42Triple is defined in crc32_amd64.s and uses the SSE 4.2 CRC32 // instruction. +// //go:noescape func castagnoliSSE42Triple( crcA, crcB, crcC uint32, @@ -32,6 +34,7 @@ func castagnoliSSE42Triple( // ieeeCLMUL is defined in crc_amd64.s and uses the PCLMULQDQ // instruction as well as SSE 4.1. +// //go:noescape func ieeeCLMUL(crc uint32, p []byte) uint32 diff --git a/src/hash/crc32/crc32_ppc64le.go b/src/hash/crc32/crc32_ppc64le.go index 686722761d..dcd32351a5 100644 --- a/src/hash/crc32/crc32_ppc64le.go +++ b/src/hash/crc32/crc32_ppc64le.go @@ -19,6 +19,7 @@ const ( func ppc64SlicingUpdateBy8(crc uint32, table8 *slicing8Table, p []byte) uint32 // this function requires the buffer to be 16 byte aligned and > 16 bytes long +// //go:noescape func vectorCrc32(crc uint32, poly uint32, p []byte) uint32 diff --git a/src/hash/crc32/crc32_s390x.go b/src/hash/crc32/crc32_s390x.go index 3a98bd8799..4e50b56c6f 100644 --- a/src/hash/crc32/crc32_s390x.go +++ b/src/hash/crc32/crc32_s390x.go @@ -17,11 +17,13 @@ var hasVX = cpu.S390X.HasVX // vectorizedCastagnoli implements CRC32 using vector instructions. // It is defined in crc32_s390x.s. +// //go:noescape func vectorizedCastagnoli(crc uint32, p []byte) uint32 // vectorizedIEEE implements CRC32 using vector instructions. // It is defined in crc32_s390x.s. +// //go:noescape func vectorizedIEEE(crc uint32, p []byte) uint32 diff --git a/src/hash/maphash/maphash.go b/src/hash/maphash/maphash.go index d022d746a7..783690ea00 100644 --- a/src/hash/maphash/maphash.go +++ b/src/hash/maphash/maphash.go @@ -10,7 +10,6 @@ // // The hash functions are not cryptographically secure. // (See crypto/sha256 and crypto/sha512 for cryptographic use.) -// package maphash import ( @@ -33,6 +32,54 @@ type Seed struct { s uint64 } +// Bytes returns the hash of b with the given seed. +// +// Bytes is equivalent to, but more convenient and efficient than: +// +// var h Hash +// h.SetSeed(seed) +// h.Write(b) +// return h.Sum64() +func Bytes(seed Seed, b []byte) uint64 { + state := seed.s + if state == 0 { + panic("maphash: use of uninitialized Seed") + } + if len(b) == 0 { + return rthash(nil, 0, state) // avoid &b[0] index panic below + } + if len(b) > bufSize { + b = b[:len(b):len(b)] // merge len and cap calculations when reslicing + for len(b) > bufSize { + state = rthash(&b[0], bufSize, state) + b = b[bufSize:] + } + } + return rthash(&b[0], len(b), state) +} + +// String returns the hash of s with the given seed. +// +// String is equivalent to, but more convenient and efficient than: +// +// var h Hash +// h.SetSeed(seed) +// h.WriteString(s) +// return h.Sum64() +func String(seed Seed, s string) uint64 { + state := seed.s + if state == 0 { + panic("maphash: use of uninitialized Seed") + } + for len(s) > bufSize { + p := (*byte)((*unsafeheader.String)(unsafe.Pointer(&s)).Data) + state = rthash(p, bufSize, state) + s = s[bufSize:] + } + p := (*byte)((*unsafeheader.String)(unsafe.Pointer(&s)).Data) + return rthash(p, len(s), state) +} + // A Hash computes a seeded hash of a byte sequence. // // The zero Hash is a valid Hash ready to use. @@ -44,9 +91,9 @@ type Seed struct { // the sequence of bytes provided to the Hash object, not on the way // in which the bytes are provided. For example, the three sequences // -// h.Write([]byte{'f','o','o'}) -// h.WriteByte('f'); h.WriteByte('o'); h.WriteByte('o') -// h.WriteString("foo") +// h.Write([]byte{'f','o','o'}) +// h.WriteByte('f'); h.WriteByte('o'); h.WriteByte('o') +// h.WriteString("foo") // // all have the same effect. // diff --git a/src/hash/maphash/maphash_test.go b/src/hash/maphash/maphash_test.go index 78cdfc0e73..7526989073 100644 --- a/src/hash/maphash/maphash_test.go +++ b/src/hash/maphash/maphash_test.go @@ -6,6 +6,7 @@ package maphash import ( "bytes" + "fmt" "hash" "testing" ) @@ -87,6 +88,14 @@ func TestHashGrouping(t *testing.T) { t.Errorf("hash %d not identical to a single Write", i) } } + + if sum1 := Bytes(hh[0].Seed(), b); sum1 != hh[0].Sum64() { + t.Errorf("hash using Bytes not identical to a single Write") + } + + if sum1 := String(hh[0].Seed(), string(b)); sum1 != hh[0].Sum64() { + t.Errorf("hash using String not identical to a single Write") + } } func TestHashBytesVsString(t *testing.T) { @@ -208,28 +217,39 @@ var _ hash.Hash64 = &Hash{} func benchmarkSize(b *testing.B, size int) { h := &Hash{} buf := make([]byte, size) - b.SetBytes(int64(size)) - b.ResetTimer() + s := string(buf) - for i := 0; i < b.N; i++ { - h.Reset() - h.Write(buf) - h.Sum64() + b.Run("Write", func(b *testing.B) { + b.SetBytes(int64(size)) + for i := 0; i < b.N; i++ { + h.Reset() + h.Write(buf) + h.Sum64() + } + }) + + b.Run("Bytes", func(b *testing.B) { + b.SetBytes(int64(size)) + seed := h.Seed() + for i := 0; i < b.N; i++ { + Bytes(seed, buf) + } + }) + + b.Run("String", func(b *testing.B) { + b.SetBytes(int64(size)) + seed := h.Seed() + for i := 0; i < b.N; i++ { + String(seed, s) + } + }) +} + +func BenchmarkHash(b *testing.B) { + sizes := []int{4, 8, 16, 32, 64, 256, 320, 1024, 4096, 16384} + for _, size := range sizes { + b.Run(fmt.Sprint("n=", size), func(b *testing.B) { + benchmarkSize(b, size) + }) } } - -func BenchmarkHash8Bytes(b *testing.B) { - benchmarkSize(b, 8) -} - -func BenchmarkHash320Bytes(b *testing.B) { - benchmarkSize(b, 320) -} - -func BenchmarkHash1K(b *testing.B) { - benchmarkSize(b, 1024) -} - -func BenchmarkHash8K(b *testing.B) { - benchmarkSize(b, 8192) -} diff --git a/src/html/template/context.go b/src/html/template/context.go index aaa7d08359..a97c8be56f 100644 --- a/src/html/template/context.go +++ b/src/html/template/context.go @@ -79,7 +79,9 @@ func (c context) mangle(templateName string) string { // HTML5 parsing algorithm because a single token production in the HTML // grammar may contain embedded actions in a template. For instance, the quoted // HTML attribute produced by -//

+// +//
+// // is a single token in HTML's grammar but in a template spans several nodes. type state uint8 diff --git a/src/html/template/doc.go b/src/html/template/doc.go index 650e7147a3..5d1631b266 100644 --- a/src/html/template/doc.go +++ b/src/html/template/doc.go @@ -12,14 +12,14 @@ The documentation here focuses on the security features of the package. For information about how to program the templates themselves, see the documentation for text/template. -Introduction +# Introduction This package wraps package text/template so you can share its template API to parse and execute HTML templates safely. - tmpl, err := template.New("name").Parse(...) - // Error checking elided - err = tmpl.Execute(out, data) + tmpl, err := template.New("name").Parse(...) + // Error checking elided + err = tmpl.Execute(out, data) If successful, tmpl will now be injection-safe. Otherwise, err is an error defined in the docs for ErrorCode. @@ -34,38 +34,37 @@ provided below. Example - import "text/template" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "") + import "text/template" + ... + t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.ExecuteTemplate(out, "T", "") produces - Hello, ! + Hello, ! but the contextual autoescaping in html/template - import "html/template" - ... - t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) - err = t.ExecuteTemplate(out, "T", "") + import "html/template" + ... + t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) + err = t.ExecuteTemplate(out, "T", "") produces safe, escaped HTML output - Hello, <script>alert('you have been pwned')</script>! + Hello, <script>alert('you have been pwned')</script>! - -Contexts +# Contexts This package understands HTML, CSS, JavaScript, and URIs. It adds sanitizing functions to each simple action pipeline, so given the excerpt - {{.}} + {{.}} At parse time each {{.}} is overwritten to add escaping functions as necessary. In this case it becomes - {{. | htmlescaper}} + {{. | htmlescaper}} where urlescaper, attrescaper, and htmlescaper are aliases for internal escaping functions. @@ -73,117 +72,113 @@ functions. For these internal escaping functions, if an action pipeline evaluates to a nil interface value, it is treated as though it were an empty string. -Namespaced and data- attributes +# Namespaced and data- attributes Attributes with a namespace are treated as if they had no namespace. Given the excerpt - + At parse time the attribute will be treated as if it were just "href". So at parse time the template becomes: - + Similarly to attributes with namespaces, attributes with a "data-" prefix are treated as if they had no "data-" prefix. So given - + At parse time this becomes - + If an attribute has both a namespace and a "data-" prefix, only the namespace will be removed when determining the context. For example - + This is handled as if "my:data-href" was just "data-href" and not "href" as it would be if the "data-" prefix were to be ignored too. Thus at parse time this becomes just - + As a special case, attributes with the namespace "xmlns" are always treated as containing URLs. Given the excerpts - - - + + + At parse time they become: - - - + + + -Errors +# Errors See the documentation of ErrorCode for details. - -A fuller picture +# A fuller picture The rest of this package comment may be skipped on first reading; it includes details necessary to understand escaping contexts and error messages. Most users will not need to understand these details. - -Contexts +# Contexts Assuming {{.}} is `O'Reilly: How are you?`, the table below shows how {{.}} appears when used in the context to the left. - Context {{.}} After - {{.}} O'Reilly: How are <i>you</i>? - O'Reilly: How are you? - O'Reilly: How are %3ci%3eyou%3c/i%3e? - O'Reilly%3a%20How%20are%3ci%3e...%3f - O\x27Reilly: How are \x3ci\x3eyou...? - "O\x27Reilly: How are \x3ci\x3eyou...?" - O\x27Reilly: How are \x3ci\x3eyou...\x3f + Context {{.}} After + {{.}} O'Reilly: How are <i>you</i>? + O'Reilly: How are you? + O'Reilly: How are %3ci%3eyou%3c/i%3e? + O'Reilly%3a%20How%20are%3ci%3e...%3f + O\x27Reilly: How are \x3ci\x3eyou...? + "O\x27Reilly: How are \x3ci\x3eyou...?" + O\x27Reilly: How are \x3ci\x3eyou...\x3f If used in an unsafe context, then the value might be filtered out: - Context {{.}} After - #ZgotmplZ + Context {{.}} After + #ZgotmplZ since "O'Reilly:" is not an allowed protocol like "http:". - If {{.}} is the innocuous word, `left`, then it can appear more widely, - Context {{.}} After - {{.}} left - left - left - left - left - left - left - left - left + Context {{.}} After + {{.}} left + left + left + left + left + left + left + left + left Non-string values can be used in JavaScript contexts. If {{.}} is - struct{A,B string}{ "foo", "bar" } + struct{A,B string}{ "foo", "bar" } in the escaped template - + then the template output is - + See package json to understand how non-string content is marshaled for embedding in JavaScript contexts. - -Typed Strings +# Typed Strings By default, this package assumes that all pipelines produce a plain text string. It adds escaping pipeline stages necessary to correctly and safely embed that @@ -197,24 +192,23 @@ exempted from escaping. The template - Hello, {{.}}! + Hello, {{.}}! can be invoked with - tmpl.Execute(out, template.HTML(`World`)) + tmpl.Execute(out, template.HTML(`World`)) to produce - Hello, World! + Hello, World! instead of the - Hello, <b>World<b>! + Hello, <b>World<b>! that would have been produced if {{.}} was a regular string. - -Security Model +# Security Model https://rawgit.com/mikesamuel/sanitized-jquery-templates/trunk/safetemplate.html#problem_definition defines "safe" as used by this package. diff --git a/src/html/template/error.go b/src/html/template/error.go index 6bb5a2027f..5c51f772cb 100644 --- a/src/html/template/error.go +++ b/src/html/template/error.go @@ -32,14 +32,17 @@ type ErrorCode int // // Output: "ZgotmplZ" // Example: -// -// where {{.X}} evaluates to `javascript:...` +// +// +// where {{.X}} evaluates to `javascript:...` +// // Discussion: -// "ZgotmplZ" is a special value that indicates that unsafe content reached a -// CSS or URL context at runtime. The output of the example will be -// -// If the data comes from a trusted source, use content types to exempt it -// from filtering: URL(`javascript:...`). +// +// "ZgotmplZ" is a special value that indicates that unsafe content reached a +// CSS or URL context at runtime. The output of the example will be +// +// If the data comes from a trusted source, use content types to exempt it +// from filtering: URL(`javascript:...`). const ( // OK indicates the lack of an error. OK ErrorCode = iota diff --git a/src/html/template/escape.go b/src/html/template/escape.go index 2b4027348a..54fbcdca33 100644 --- a/src/html/template/escape.go +++ b/src/html/template/escape.go @@ -411,13 +411,19 @@ func newIdentCmd(identifier string, pos parse.Pos) *parse.CommandNode { // nudge returns the context that would result from following empty string // transitions from the input context. // For example, parsing: -// `(function () { // var a = [], d = document.getElementById("d"), i, c, s; // for (i = 0; i < 0x10000; ++i) { -// c = String.fromCharCode(i); -// d.innerHTML = "" -// s = d.getElementsByTagName("SPAN")[0]; -// if (!s || s.title !== c + "lt" + c) { a.push(i.toString(16)); } +// +// c = String.fromCharCode(i); +// d.innerHTML = "" +// s = d.getElementsByTagName("SPAN")[0]; +// if (!s || s.title !== c + "lt" + c) { a.push(i.toString(16)); } +// // } // document.write(a.join(", ")); // })() diff --git a/src/html/template/template.go b/src/html/template/template.go index a99f69231c..30b64dff04 100644 --- a/src/html/template/template.go +++ b/src/html/template/template.go @@ -64,6 +64,7 @@ func (t *Template) Templates() []*Template { // // missingkey: Control the behavior during execution if a map is // indexed with a key that is not present in the map. +// // "missingkey=default" or "missingkey=invalid" // The default behavior: Do nothing and continue execution. // If printed, the result of the index operation is the string @@ -360,6 +361,7 @@ func (t *Template) Lookup(name string) *Template { // Must is a helper that wraps a call to a function returning (*Template, error) // and panics if the error is non-nil. It is intended for use in variable initializations // such as +// // var t = template.Must(template.New("name").Parse("html")) func Must(t *Template, err error) *Template { if err != nil { diff --git a/src/html/template/url.go b/src/html/template/url.go index 93905586a2..9d0be39022 100644 --- a/src/html/template/url.go +++ b/src/html/template/url.go @@ -19,15 +19,15 @@ import ( // // This filter conservatively assumes that all schemes other than the following // are unsafe: -// * http: Navigates to a new website, and may open a new window or tab. -// These side effects can be reversed by navigating back to the -// previous website, or closing the window or tab. No irreversible -// changes will take place without further user interaction with -// the new website. -// * https: Same as http. -// * mailto: Opens an email program and starts a new draft. This side effect -// is not irreversible until the user explicitly clicks send; it -// can be undone by closing the email program. +// - http: Navigates to a new website, and may open a new window or tab. +// These side effects can be reversed by navigating back to the +// previous website, or closing the window or tab. No irreversible +// changes will take place without further user interaction with +// the new website. +// - https: Same as http. +// - mailto: Opens an email program and starts a new draft. This side effect +// is not irreversible until the user explicitly clicks send; it +// can be undone by closing the email program. // // To allow URLs containing other schemes to bypass this filter, developers must // explicitly indicate that such a URL is expected and safe by encapsulating it diff --git a/src/image/draw/draw.go b/src/image/draw/draw.go index 7dd18dfdb5..920ebb905e 100644 --- a/src/image/draw/draw.go +++ b/src/image/draw/draw.go @@ -121,6 +121,11 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas // Fast paths for special cases. If none of them apply, then we fall back // to general but slower implementations. + // + // For NRGBA and NRGBA64 image types, the code paths aren't just faster. + // They also avoid the information loss that would otherwise occur from + // converting non-alpha-premultiplied color to and from alpha-premultiplied + // color. See TestDrawSrcNonpremultiplied. switch dst0 := dst.(type) { case *image.RGBA: if op == Over { @@ -181,7 +186,10 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas drawFillSrc(dst0, r, sr, sg, sb, sa) return case *image.RGBA: - drawCopySrc(dst0, r, src0, sp) + d0 := dst0.PixOffset(r.Min.X, r.Min.Y) + s0 := src0.PixOffset(sp.X, sp.Y) + drawCopySrc( + dst0.Pix[d0:], dst0.Stride, r, src0.Pix[s0:], src0.Stride, sp, 4*r.Dx()) return case *image.NRGBA: drawNRGBASrc(dst0, r, src0, sp) @@ -222,6 +230,26 @@ func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mas return } } + case *image.NRGBA: + if op == Src && mask == nil { + if src0, ok := src.(*image.NRGBA); ok { + d0 := dst0.PixOffset(r.Min.X, r.Min.Y) + s0 := src0.PixOffset(sp.X, sp.Y) + drawCopySrc( + dst0.Pix[d0:], dst0.Stride, r, src0.Pix[s0:], src0.Stride, sp, 4*r.Dx()) + return + } + } + case *image.NRGBA64: + if op == Src && mask == nil { + if src0, ok := src.(*image.NRGBA64); ok { + d0 := dst0.PixOffset(r.Min.X, r.Min.Y) + s0 := src0.PixOffset(sp.X, sp.Y) + drawCopySrc( + dst0.Pix[d0:], dst0.Stride, r, src0.Pix[s0:], src0.Stride, sp, 8*r.Dx()) + return + } + } } x0, x1, dx := r.Min.X, r.Max.X, 1 @@ -449,27 +477,28 @@ func drawCopyOver(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image. } } -func drawCopySrc(dst *image.RGBA, r image.Rectangle, src *image.RGBA, sp image.Point) { - n, dy := 4*r.Dx(), r.Dy() - d0 := dst.PixOffset(r.Min.X, r.Min.Y) - s0 := src.PixOffset(sp.X, sp.Y) - var ddelta, sdelta int - if r.Min.Y <= sp.Y { - ddelta = dst.Stride - sdelta = src.Stride - } else { +// drawCopySrc copies bytes to dstPix from srcPix. These arguments roughly +// correspond to the Pix fields of the image package's concrete image.Image +// implementations, but are offset (dstPix is dst.Pix[dpOffset:] not dst.Pix). +func drawCopySrc( + dstPix []byte, dstStride int, r image.Rectangle, + srcPix []byte, srcStride int, sp image.Point, + bytesPerRow int) { + + d0, s0, ddelta, sdelta, dy := 0, 0, dstStride, srcStride, r.Dy() + if r.Min.Y > sp.Y { // If the source start point is higher than the destination start // point, then we compose the rows in bottom-up order instead of // top-down. Unlike the drawCopyOver function, we don't have to check // the x coordinates because the built-in copy function can handle // overlapping slices. - d0 += (dy - 1) * dst.Stride - s0 += (dy - 1) * src.Stride - ddelta = -dst.Stride - sdelta = -src.Stride + d0 = (dy - 1) * dstStride + s0 = (dy - 1) * srcStride + ddelta = -dstStride + sdelta = -srcStride } for ; dy > 0; dy-- { - copy(dst.Pix[d0:d0+n], src.Pix[s0:s0+n]) + copy(dstPix[d0:d0+bytesPerRow], srcPix[s0:s0+bytesPerRow]) d0 += ddelta s0 += sdelta } diff --git a/src/image/draw/draw_test.go b/src/image/draw/draw_test.go index 3be93962ad..a34d1c3e6e 100644 --- a/src/image/draw/draw_test.go +++ b/src/image/draw/draw_test.go @@ -622,6 +622,70 @@ func TestFill(t *testing.T) { } } +func TestDrawSrcNonpremultiplied(t *testing.T) { + var ( + opaqueGray = color.NRGBA{0x99, 0x99, 0x99, 0xff} + transparentBlue = color.NRGBA{0x00, 0x00, 0xff, 0x00} + transparentGreen = color.NRGBA{0x00, 0xff, 0x00, 0x00} + transparentRed = color.NRGBA{0xff, 0x00, 0x00, 0x00} + + opaqueGray64 = color.NRGBA64{0x9999, 0x9999, 0x9999, 0xffff} + transparentPurple64 = color.NRGBA64{0xfedc, 0x0000, 0x7654, 0x0000} + ) + + // dst and src are 1x3 images but the dr rectangle (and hence the overlap) + // is only 1x2. The Draw call should affect dst's pixels at (1, 10) and (2, + // 10) but the pixel at (0, 10) should be untouched. + // + // The src image is entirely transparent (and the Draw operator is Src) so + // the two touched pixels should be set to transparent colors. + // + // In general, Go's color.Color type (and specifically the Color.RGBA + // method) works in premultiplied alpha, where there's no difference + // between "transparent blue" and "transparent red". It's all "just + // transparent" and canonically "transparent black" (all zeroes). + // + // However, since the operator is Src (so the pixels are 'copied', not + // 'blended') and both dst and src images are *image.NRGBA (N stands for + // Non-premultiplied alpha which *does* distinguish "transparent blue" and + // "transparent red"), we prefer that this distinction carries through and + // dst's touched pixels should be transparent blue and transparent green, + // not just transparent black. + { + dst := image.NewNRGBA(image.Rect(0, 10, 3, 11)) + dst.SetNRGBA(0, 10, opaqueGray) + src := image.NewNRGBA(image.Rect(1, 20, 4, 21)) + src.SetNRGBA(1, 20, transparentBlue) + src.SetNRGBA(2, 20, transparentGreen) + src.SetNRGBA(3, 20, transparentRed) + + dr := image.Rect(1, 10, 3, 11) + Draw(dst, dr, src, image.Point{1, 20}, Src) + + if got, want := dst.At(0, 10), opaqueGray; got != want { + t.Errorf("At(0, 10):\ngot %#v\nwant %#v", got, want) + } + if got, want := dst.At(1, 10), transparentBlue; got != want { + t.Errorf("At(1, 10):\ngot %#v\nwant %#v", got, want) + } + if got, want := dst.At(2, 10), transparentGreen; got != want { + t.Errorf("At(2, 10):\ngot %#v\nwant %#v", got, want) + } + } + + // Check image.NRGBA64 (not image.NRGBA) similarly. + { + dst := image.NewNRGBA64(image.Rect(0, 0, 1, 1)) + dst.SetNRGBA64(0, 0, opaqueGray64) + src := image.NewNRGBA64(image.Rect(0, 0, 1, 1)) + src.SetNRGBA64(0, 0, transparentPurple64) + Draw(dst, dst.Bounds(), src, image.Point{0, 0}, Src) + if got, want := dst.At(0, 0), transparentPurple64; got != want { + t.Errorf("At(0, 0):\ngot %#v\nwant %#v", got, want) + } + } +} + // TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg // error diffusion of a uniform 50% gray source image with a black-and-white // palette is a checkerboard pattern. diff --git a/src/image/image.go b/src/image/image.go index 930d9ac6c7..dfb70d4eaf 100644 --- a/src/image/image.go +++ b/src/image/image.go @@ -13,7 +13,9 @@ // image format requires the prior registration of a decoder function. // Registration is typically automatic as a side effect of initializing that // format's package so that, to decode a PNG image, it suffices to have +// // import _ "image/png" +// // in a program's main package. The _ means to import a package purely for its // initialization side effects. // diff --git a/src/image/jpeg/writer.go b/src/image/jpeg/writer.go index a600499004..0027f78294 100644 --- a/src/image/jpeg/writer.go +++ b/src/image/jpeg/writer.go @@ -481,25 +481,25 @@ func scale(dst *block, src *[4]block) { } // sosHeaderY is the SOS marker "\xff\xda" followed by 8 bytes: -// - the marker length "\x00\x08", -// - the number of components "\x01", -// - component 1 uses DC table 0 and AC table 0 "\x01\x00", -// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for -// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) -// should be 0x00, 0x3f, 0x00<<4 | 0x00. +// - the marker length "\x00\x08", +// - the number of components "\x01", +// - component 1 uses DC table 0 and AC table 0 "\x01\x00", +// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for +// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) +// should be 0x00, 0x3f, 0x00<<4 | 0x00. var sosHeaderY = []byte{ 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, } // sosHeaderYCbCr is the SOS marker "\xff\xda" followed by 12 bytes: -// - the marker length "\x00\x0c", -// - the number of components "\x03", -// - component 1 uses DC table 0 and AC table 0 "\x01\x00", -// - component 2 uses DC table 1 and AC table 1 "\x02\x11", -// - component 3 uses DC table 1 and AC table 1 "\x03\x11", -// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for -// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) -// should be 0x00, 0x3f, 0x00<<4 | 0x00. +// - the marker length "\x00\x0c", +// - the number of components "\x03", +// - component 1 uses DC table 0 and AC table 0 "\x01\x00", +// - component 2 uses DC table 1 and AC table 1 "\x02\x11", +// - component 3 uses DC table 1 and AC table 1 "\x03\x11", +// - the bytes "\x00\x3f\x00". Section B.2.3 of the spec says that for +// sequential DCTs, those bytes (8-bit Ss, 8-bit Se, 4-bit Ah, 4-bit Al) +// should be 0x00, 0x3f, 0x00<<4 | 0x00. var sosHeaderYCbCr = []byte{ 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, diff --git a/src/image/png/reader.go b/src/image/png/reader.go index 4c65038cb5..95b507cf68 100644 --- a/src/image/png/reader.go +++ b/src/image/png/reader.go @@ -325,7 +325,9 @@ func (d *decoder) parsetRNS(length uint32) error { // Read presents one or more IDAT chunks as one continuous stream (minus the // intermediate chunk headers and footers). If the PNG data looked like: -// ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2 +// +// ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2 +// // then this reader presents xxxyy. For well-formed PNG data, the decoder state // immediately before the first Read call is that d.r is positioned between the // first IDAT and xxx, and the decoder state immediately after the last Read diff --git a/src/image/ycbcr.go b/src/image/ycbcr.go index 328b90d152..78f5ebe1d8 100644 --- a/src/image/ycbcr.go +++ b/src/image/ycbcr.go @@ -45,6 +45,7 @@ func (s YCbCrSubsampleRatio) String() string { // that map to separate chroma samples. // It is not an absolute requirement, but YStride and len(Y) are typically // multiples of 8, and: +// // For 4:4:4, CStride == YStride/1 && len(Cb) == len(Cr) == len(Y)/1. // For 4:2:2, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/2. // For 4:2:0, CStride == YStride/2 && len(Cb) == len(Cr) == len(Y)/4. diff --git a/src/index/suffixarray/suffixarray.go b/src/index/suffixarray/suffixarray.go index cdc6e81a80..7fca0fd7ba 100644 --- a/src/index/suffixarray/suffixarray.go +++ b/src/index/suffixarray/suffixarray.go @@ -13,7 +13,6 @@ // // lookup byte slice s // offsets1 := index.Lookup(s, -1) // the list of all indices where s occurs in data // offsets2 := index.Lookup(s, 3) // the list of at most 3 indices where s occurs in data -// package suffixarray import ( diff --git a/src/internal/bytealg/indexbyte_ppc64x.s b/src/internal/bytealg/indexbyte_ppc64x.s index 4cc2b44087..1a6e852d67 100644 --- a/src/internal/bytealg/indexbyte_ppc64x.s +++ b/src/internal/bytealg/indexbyte_ppc64x.s @@ -11,17 +11,20 @@ TEXT ·IndexByte(SB),NOSPLIT|NOFRAME,$0-40 // R3 = byte array pointer // R4 = length MOVD R6, R5 // R5 = byte + MOVBZ internal∕cpu·PPC64+const_offsetPPC64HasPOWER9(SB), R16 BR indexbytebody<>(SB) TEXT ·IndexByteString(SB),NOSPLIT|NOFRAME,$0-32 // R3 = string // R4 = length // R5 = byte + MOVBZ internal∕cpu·PPC64+const_offsetPPC64HasPOWER9(SB), R16 BR indexbytebody<>(SB) // R3 = addr of string // R4 = len of string // R5 = byte to find +// R16 = 1 if running on a POWER9 system, 0 otherwise // On exit: // R3 = return value TEXT indexbytebody<>(SB),NOSPLIT|NOFRAME,$0-0 @@ -29,12 +32,11 @@ TEXT indexbytebody<>(SB),NOSPLIT|NOFRAME,$0-0 RLDICR $0,R3,$60,R8 // Align address to doubleword boundary in R8. RLDIMI $8,R5,$48,R5 // Replicating the byte across the register. ADD R4,R3,R7 // Last acceptable address in R7. - DCBT (R8) // Prepare cache line. RLDIMI $16,R5,$32,R5 CMPU R4,$32 // Check if it's a small string (≤32 bytes). Those will be processed differently. MOVD $-1,R9 - WORD $0x54661EB8 // Calculate padding in R6 (rlwinm r6,r3,3,26,28). + RLWNM $3,R3,$26,$28,R6 // shift amount for mask (r3&0x7)*8 RLDIMI $32,R5,$0,R5 MOVD R7,R10 // Save last acceptable address in R10 for later. ADD $-1,R7,R7 @@ -43,8 +45,77 @@ TEXT indexbytebody<>(SB),NOSPLIT|NOFRAME,$0-0 #else SRD R6,R9,R9 // Same for Big Endian #endif - BLE small_string // Jump to the small string case if it's ≤32 bytes. + BLT small_string // Jump to the small string case if it's <32 bytes. + CMP R16,$1 // optimize for power8 v power9 + BNE power8 + VSPLTISB $3,V10 // Use V10 as control for VBPERMQ + MTVRD R5,V1 + LVSL (R0+R0),V11 // set up the permute vector such that V10 has {0x78, .., 0x8, 0x0} + VSLB V11,V10,V10 // to extract the first bit of match result into GPR + VSPLTB $7,V1,V1 // Replicate byte across V1 + CMP R4,$64 + MOVD $16,R11 + MOVD R3,R8 + BLT cmp32 + MOVD $32,R12 + MOVD $48,R6 +loop64: + LXVB16X (R0)(R8),V2 // scan 64 bytes at a time + VCMPEQUBCC V2,V1,V6 + BNE CR6,foundat0 // match found at R8, jump out + + LXVB16X (R8)(R11),V2 + VCMPEQUBCC V2,V1,V6 + BNE CR6,foundat1 // match found at R8+16 bytes, jump out + + LXVB16X (R8)(R12),V2 + VCMPEQUBCC V2,V1,V6 + BNE CR6,foundat2 // match found at R8+32 bytes, jump out + + LXVB16X (R8)(R6),V2 + VCMPEQUBCC V2,V1,V6 + BNE CR6,foundat3 // match found at R8+48 bytes, jump out + ADD $64,R8 + ADD $-64,R4 + CMP R4,$64 // >=64 bytes left to scan? + BGE loop64 + CMP R4,$32 + BLT rem // jump to rem if there are < 32 bytes left +cmp32: + LXVB16X (R0)(R8),V2 // 32-63 bytes left + VCMPEQUBCC V2,V1,V6 + BNE CR6,foundat0 // match found at R8 + + LXVB16X (R11)(R8),V2 + VCMPEQUBCC V2,V1,V6 + BNE CR6,foundat1 // match found at R8+16 + + ADD $32,R8 + ADD $-32,R4 +rem: + RLDICR $0,R8,$60,R8 // align address to reuse code for tail end processing + BR small_string + +foundat3: + ADD $16,R8 +foundat2: + ADD $16,R8 +foundat1: + ADD $16,R8 +foundat0: + // Compress the result into a single doubleword and + // move it to a GPR for the final calculation. + VBPERMQ V6,V10,V6 + MFVRD V6,R3 + // count leading zeroes upto the match that ends up in low 16 bits + // in both endian modes, compute index by subtracting the number by 16 + CNTLZW R3,R11 + ADD $-16,R11 + ADD R8,R11,R3 // Calculate byte address + SUB R17,R3 + RET +power8: // If we are 64-byte aligned, branch to qw_align just to get the auxiliary values // in V0, V1 and V10, then branch to the preloop. ANDCC $63,R3,R11 @@ -54,7 +125,6 @@ TEXT indexbytebody<>(SB),NOSPLIT|NOFRAME,$0-0 MOVD 0(R8),R12 // Load one doubleword from the aligned address in R8. CMPB R12,R5,R3 // Check for a match. AND R9,R3,R3 // Mask bytes below s_base - RLDICL $0,R7,$61,R6 // length-1 RLDICR $0,R7,$60,R7 // Last doubleword in R7 CMPU R3,$0,CR7 // If we have a match, jump to the final computation BNE CR7,done @@ -252,8 +322,13 @@ found_qw_align: CMPU R11,R4 BLT return BR notfound + PCALIGN $16 done: + ADD $-1,R10,R6 + // Offset of last index for the final + // doubleword comparison + RLDICL $0,R6,$61,R6 // At this point, R3 has 0xFF in the same position as the byte we are // looking for in the doubleword. Use that to calculate the exact index // of the byte. @@ -273,6 +348,7 @@ done: BR notfound small_string: + // process string of length < 32 bytes // We unroll this loop for better performance. CMPU R4,$0 // Check for length=0 BEQ notfound @@ -281,7 +357,6 @@ small_string: CMPB R12,R5,R3 // Check for a match. AND R9,R3,R3 // Mask bytes below s_base. CMPU R3,$0,CR7 // If we have a match, jump to the final computation. - RLDICL $0,R7,$61,R6 // length-1 RLDICR $0,R7,$60,R7 // Last doubleword in R7. CMPU R8,R7 BNE CR7,done diff --git a/src/internal/fmtsort/sort.go b/src/internal/fmtsort/sort.go index 09e987d0e6..278a89bd75 100644 --- a/src/internal/fmtsort/sort.go +++ b/src/internal/fmtsort/sort.go @@ -36,18 +36,18 @@ func (o *SortedMap) Swap(i, j int) { // // The ordering rules are more general than with Go's < operator: // -// - when applicable, nil compares low -// - ints, floats, and strings order by < -// - NaN compares less than non-NaN floats -// - bool compares false before true -// - complex compares real, then imag -// - pointers compare by machine address -// - channel values compare by machine address -// - structs compare each field in turn -// - arrays compare each element in turn. -// Otherwise identical arrays compare by length. -// - interface values compare first by reflect.Type describing the concrete type -// and then by concrete value as described in the previous rules. +// - when applicable, nil compares low +// - ints, floats, and strings order by < +// - NaN compares less than non-NaN floats +// - bool compares false before true +// - complex compares real, then imag +// - pointers compare by machine address +// - channel values compare by machine address +// - structs compare each field in turn +// - arrays compare each element in turn. +// Otherwise identical arrays compare by length. +// - interface values compare first by reflect.Type describing the concrete type +// and then by concrete value as described in the previous rules. func Sort(mapValue reflect.Value) *SortedMap { if mapValue.Type().Kind() != reflect.Map { return nil diff --git a/src/internal/goarch/goarch.go b/src/internal/goarch/goarch.go index 921f5a208f..e8de67b01b 100644 --- a/src/internal/goarch/goarch.go +++ b/src/internal/goarch/goarch.go @@ -9,6 +9,7 @@ package goarch // per-arch information, including constants named $GOARCH for every // GOARCH. The constant is 1 on the current system, 0 otherwise; multiplying // by them is useful for defining GOARCH-specific constants. +// //go:generate go run gengoarch.go type ArchFamilyType int diff --git a/src/internal/goos/goos.go b/src/internal/goos/goos.go index ebb521fec6..02dc9688cb 100644 --- a/src/internal/goos/goos.go +++ b/src/internal/goos/goos.go @@ -9,4 +9,5 @@ package goos // per-OS information, including constants named Is$GOOS for every // known GOOS. The constant is 1 on the current system, 0 otherwise; // multiplying by them is useful for defining GOOS-specific constants. +// //go:generate go run gengoos.go diff --git a/src/internal/intern/intern.go b/src/internal/intern/intern.go index 75641106ab..c7639b4668 100644 --- a/src/internal/intern/intern.go +++ b/src/internal/intern/intern.go @@ -93,6 +93,7 @@ func GetByString(s string) *Value { // We play unsafe games that violate Go's rules (and assume a non-moving // collector). So we quiet Go here. // See the comment below Get for more implementation details. +// //go:nocheckptr func get(k key) *Value { mu.Lock() diff --git a/src/internal/nettrace/nettrace.go b/src/internal/nettrace/nettrace.go index 6e0dbe73bb..0a2bf925e9 100644 --- a/src/internal/nettrace/nettrace.go +++ b/src/internal/nettrace/nettrace.go @@ -16,7 +16,8 @@ type TraceKey struct{} // specify an alternate resolver func. // It is not exposed to outsider users. (But see issue 12503) // The value should be the same type as lookupIP: -// func lookupIP(ctx context.Context, host string) ([]IPAddr, error) +// +// func lookupIP(ctx context.Context, host string) ([]IPAddr, error) type LookupIPAltResolverKey struct{} // Trace contains a set of hooks for tracing events within diff --git a/src/internal/poll/fcntl_libc.go b/src/internal/poll/fcntl_libc.go index f503d7a336..13614dc3e8 100644 --- a/src/internal/poll/fcntl_libc.go +++ b/src/internal/poll/fcntl_libc.go @@ -9,5 +9,6 @@ package poll import _ "unsafe" // for go:linkname // Implemented in the syscall package. +// //go:linkname fcntl syscall.fcntl func fcntl(fd int, cmd int, arg int) (int, error) diff --git a/src/internal/poll/fd_opendir_darwin.go b/src/internal/poll/fd_opendir_darwin.go index 8eb770c358..3ae2dc8448 100644 --- a/src/internal/poll/fd_opendir_darwin.go +++ b/src/internal/poll/fd_opendir_darwin.go @@ -34,5 +34,6 @@ func (fd *FD) OpenDir() (uintptr, string, error) { } // Implemented in syscall/syscall_darwin.go. +// //go:linkname fdopendir syscall.fdopendir func fdopendir(fd int) (dir uintptr, err error) diff --git a/src/internal/poll/fd_poll_runtime.go b/src/internal/poll/fd_poll_runtime.go index 2e9cd5c9d7..4d3cc78405 100644 --- a/src/internal/poll/fd_poll_runtime.go +++ b/src/internal/poll/fd_poll_runtime.go @@ -15,6 +15,7 @@ import ( ) // runtimeNano returns the current value of the runtime clock in nanoseconds. +// //go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 diff --git a/src/internal/poll/fd_writev_darwin.go b/src/internal/poll/fd_writev_darwin.go index 8137510c8b..b5b8998df8 100644 --- a/src/internal/poll/fd_writev_darwin.go +++ b/src/internal/poll/fd_writev_darwin.go @@ -12,5 +12,6 @@ import ( ) // Implemented in syscall/syscall_darwin.go. +// //go:linkname writev syscall.writev func writev(fd int, iovecs []syscall.Iovec) (uintptr, error) diff --git a/src/internal/poll/sendfile_solaris.go b/src/internal/poll/sendfile_solaris.go index 0a884307bb..7ae18f4b1a 100644 --- a/src/internal/poll/sendfile_solaris.go +++ b/src/internal/poll/sendfile_solaris.go @@ -7,6 +7,7 @@ package poll import "syscall" // Not strictly needed, but very helpful for debugging, see issue #10221. +// //go:cgo_import_dynamic _ _ "libsendfile.so" //go:cgo_import_dynamic _ _ "libsocket.so" diff --git a/src/internal/profile/legacy_profile.go b/src/internal/profile/legacy_profile.go index 377a43d585..fee420986e 100644 --- a/src/internal/profile/legacy_profile.go +++ b/src/internal/profile/legacy_profile.go @@ -335,11 +335,12 @@ func addTracebackSample(l []*Location, s []string, p *Profile) { // // The general format for profilez samples is a sequence of words in // binary format. The first words are a header with the following data: -// 1st word -- 0 -// 2nd word -- 3 -// 3rd word -- 0 if a c++ application, 1 if a java application. -// 4th word -- Sampling period (in microseconds). -// 5th word -- Padding. +// +// 1st word -- 0 +// 2nd word -- 3 +// 3rd word -- 0 if a c++ application, 1 if a java application. +// 4th word -- Sampling period (in microseconds). +// 5th word -- Padding. func parseCPU(b []byte) (*Profile, error) { var parse func([]byte) (uint64, []byte) var n1, n2, n3, n4, n5 uint64 @@ -410,15 +411,18 @@ func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) ( // // profilez samples are a repeated sequence of stack frames of the // form: -// 1st word -- The number of times this stack was encountered. -// 2nd word -- The size of the stack (StackSize). -// 3rd word -- The first address on the stack. -// ... -// StackSize + 2 -- The last address on the stack +// +// 1st word -- The number of times this stack was encountered. +// 2nd word -- The size of the stack (StackSize). +// 3rd word -- The first address on the stack. +// ... +// StackSize + 2 -- The last address on the stack +// // The last stack trace is of the form: -// 1st word -- 0 -// 2nd word -- 1 -// 3rd word -- 0 +// +// 1st word -- 0 +// 2nd word -- 1 +// 3rd word -- 0 // // Addresses from stack traces may point to the next instruction after // each call. Optionally adjust by -1 to land somewhere on the actual diff --git a/src/internal/reflectlite/export_test.go b/src/internal/reflectlite/export_test.go index adae229e92..3e5c258fb1 100644 --- a/src/internal/reflectlite/export_test.go +++ b/src/internal/reflectlite/export_test.go @@ -78,7 +78,9 @@ func Zero(typ Type) Value { // ToInterface returns v's current value as an interface{}. // It is equivalent to: +// // var i interface{} = (v's underlying value) +// // It panics if the Value was obtained by accessing // unexported struct fields. func ToInterface(v Value) (i any) { diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index 34677b400e..bc6fc94773 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -113,6 +113,7 @@ const Ptr = Pointer // available in the memory directly following the rtype value. // // tflag values must be kept in sync with copies in: +// // cmd/compile/internal/reflectdata/reflect.go // cmd/link/internal/ld/decodesym.go // runtime/type.go @@ -298,7 +299,7 @@ type structType struct { // // The next two bytes are the data length: // -// l := uint16(data[1])<<8 | uint16(data[2]) +// l := uint16(data[1])<<8 | uint16(data[2]) // // Bytes [3:3+l] are the string data. // diff --git a/src/internal/reflectlite/value.go b/src/internal/reflectlite/value.go index 966230f581..b9bca3ab44 100644 --- a/src/internal/reflectlite/value.go +++ b/src/internal/reflectlite/value.go @@ -458,6 +458,7 @@ func arrayAt(p unsafe.Pointer, i int, eltSize uintptr, whySafe string) unsafe.Po func ifaceE2I(t *rtype, src any, dst unsafe.Pointer) // typedmemmove copies a value of type t to dst from src. +// //go:noescape func typedmemmove(t *rtype, dst, src unsafe.Pointer) diff --git a/src/internal/syscall/unix/nonblocking_libc.go b/src/internal/syscall/unix/nonblocking_libc.go index 75c6e92a6e..84940714c3 100644 --- a/src/internal/syscall/unix/nonblocking_libc.go +++ b/src/internal/syscall/unix/nonblocking_libc.go @@ -20,5 +20,6 @@ func IsNonblock(fd int) (nonblocking bool, err error) { } // Implemented in the syscall package. +// //go:linkname fcntl syscall.fcntl func fcntl(fd int, cmd int, arg int) (int, error) diff --git a/src/internal/syscall/windows/registry/key.go b/src/internal/syscall/windows/registry/key.go index ec38cf9288..ce6397f1e2 100644 --- a/src/internal/syscall/windows/registry/key.go +++ b/src/internal/syscall/windows/registry/key.go @@ -22,7 +22,6 @@ // // NOTE: This package is a copy of golang.org/x/sys/windows/registry // with KeyInfo.ModTime removed to prevent dependency cycles. -// package registry import ( diff --git a/src/internal/testenv/testenv.go b/src/internal/testenv/testenv.go index 6ef889b02a..1feb630cf5 100644 --- a/src/internal/testenv/testenv.go +++ b/src/internal/testenv/testenv.go @@ -35,7 +35,7 @@ func Builder() string { return os.Getenv("GO_BUILDER_NAME") } -// HasGoBuild reports whether the current system can build programs with ``go build'' +// HasGoBuild reports whether the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. func HasGoBuild() bool { if os.Getenv("GO_GCFLAGS") != "" { @@ -52,7 +52,7 @@ func HasGoBuild() bool { return true } -// MustHaveGoBuild checks that the current system can build programs with ``go build'' +// MustHaveGoBuild checks that the current system can build programs with “go build” // and then run them with os.StartProcess or exec.Command. // If not, MustHaveGoBuild calls t.Skip with an explanation. func MustHaveGoBuild(t testing.TB) { @@ -64,13 +64,13 @@ func MustHaveGoBuild(t testing.TB) { } } -// HasGoRun reports whether the current system can run programs with ``go run.'' +// HasGoRun reports whether the current system can run programs with “go run.” func HasGoRun() bool { // For now, having go run and having go build are the same. return HasGoBuild() } -// MustHaveGoRun checks that the current system can run programs with ``go run.'' +// MustHaveGoRun checks that the current system can run programs with “go run.” // If not, MustHaveGoRun calls t.Skip with an explanation. func MustHaveGoRun(t testing.TB) { if !HasGoRun() { diff --git a/src/internal/txtar/archive.go b/src/internal/txtar/archive.go index 214256617b..81b3145451 100644 --- a/src/internal/txtar/archive.go +++ b/src/internal/txtar/archive.go @@ -6,15 +6,15 @@ // // The goals for the format are: // -// - be trivial enough to create and edit by hand. -// - be able to store trees of text files describing go command test cases. -// - diff nicely in git history and code reviews. +// - be trivial enough to create and edit by hand. +// - be able to store trees of text files describing go command test cases. +// - diff nicely in git history and code reviews. // // Non-goals include being a completely general archive format, // storing binary data, storing file modes, storing special files like // symbolic links, and so on. // -// Txtar format +// # Txtar format // // A txtar archive is zero or more comment lines and then a sequence of file entries. // Each file entry begins with a file marker line of the form "-- FILENAME --" diff --git a/src/io/ioutil/ioutil.go b/src/io/ioutil/ioutil.go index 9921c2ae50..6a1d69172c 100644 --- a/src/io/ioutil/ioutil.go +++ b/src/io/ioutil/ioutil.go @@ -55,6 +55,17 @@ func WriteFile(filename string, data []byte, perm fs.FileMode) error { // it returns a list of fs.DirEntry instead of fs.FileInfo, // and it returns partial results in the case of an error // midway through reading a directory. +// +// If you must continue obtaining a list of fs.FileInfo, you still can: +// +// entries, err := os.ReadDir(dirname) +// if err != nil { ... } +// infos := make([]fs.FileInfo, 0, len(entries)) +// for _, entry := range entries { +// info, err := entry.Info() +// if err != nil { ... } +// infos = append(infos, info) +// } func ReadDir(dirname string) ([]fs.FileInfo, error) { f, err := os.Open(dirname) if err != nil { diff --git a/src/log/log.go b/src/log/log.go index 5e79b19522..f7e48d5599 100644 --- a/src/log/log.go +++ b/src/log/log.go @@ -32,8 +32,11 @@ import ( // The prefix is followed by a colon only when Llongfile or Lshortfile // is specified. // For example, flags Ldate | Ltime (or LstdFlags) produce, +// // 2009/01/23 01:23:23 message +// // while flags Ldate | Ltime | Lmicroseconds | Llongfile produce, +// // 2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message const ( Ldate = 1 << iota // the date in the local time zone: 2009/01/23 @@ -107,10 +110,10 @@ func itoa(buf *[]byte, i int, wid int) { } // formatHeader writes log header to buf in following order: -// * l.prefix (if it's not blank and Lmsgprefix is unset), -// * date and/or time (if corresponding flags are provided), -// * file and line number (if corresponding flags are provided), -// * l.prefix (if it's not blank and Lmsgprefix is set). +// - l.prefix (if it's not blank and Lmsgprefix is unset), +// - date and/or time (if corresponding flags are provided), +// - file and line number (if corresponding flags are provided), +// - l.prefix (if it's not blank and Lmsgprefix is set). func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { if l.flag&Lmsgprefix == 0 { *buf = append(*buf, l.prefix...) diff --git a/src/log/syslog/doc.go b/src/log/syslog/doc.go index bd12bea581..9a33eeb5d5 100644 --- a/src/log/syslog/doc.go +++ b/src/log/syslog/doc.go @@ -13,7 +13,7 @@ // The syslog package is frozen and is not accepting new features. // Some external packages provide more functionality. See: // -// https://godoc.org/?q=syslog +// https://godoc.org/?q=syslog package syslog // BUG(brainman): This package is not implemented on Windows. As the diff --git a/src/make.bash b/src/make.bash index e517a1bda9..ab2ce19f4e 100755 --- a/src/make.bash +++ b/src/make.bash @@ -73,12 +73,6 @@ set -e -export GOENV=off -export GOWORK=off # Issue 51558 -unset GOBIN # Issue 14340 -unset GOFLAGS -unset GO111MODULE - if [ ! -f run.bash ]; then echo 'make.bash must be run from $GOROOT/src' 1>&2 exit 1 @@ -204,7 +198,7 @@ if [ "$GOROOT_BOOTSTRAP" = "$GOROOT" ]; then exit 1 fi rm -f cmd/dist/dist -GOROOT="$GOROOT_BOOTSTRAP" GOOS="" GOARCH="" GO111MODULE=off GOEXPERIMENT="" "$GOROOT_BOOTSTRAP/bin/go" build -o cmd/dist/dist ./cmd/dist +GOROOT="$GOROOT_BOOTSTRAP" GOOS="" GOARCH="" GO111MODULE=off GOEXPERIMENT="" GOENV=off GOFLAGS="" "$GOROOT_BOOTSTRAP/bin/go" build -o cmd/dist/dist ./cmd/dist # -e doesn't propagate out of eval, so check success by hand. eval $(./cmd/dist/dist env -p || echo FAIL=true) diff --git a/src/make.bat b/src/make.bat index c2f87ace75..0ba2dd57c5 100644 --- a/src/make.bat +++ b/src/make.bat @@ -46,11 +46,7 @@ if x%4==x--no-local goto nolocal setlocal :nolocal -set GOENV=off -set GOWORK=off set GOBUILDFAIL=0 -set GOFLAGS= -set GO111MODULE= if exist make.bat goto ok echo Must run make.bat from Go src directory. @@ -102,6 +98,8 @@ set GOARCH= set GOBIN= set GOEXPERIMENT= set GO111MODULE=off +set GOENV=off +set GOFLAGS= "%GOROOT_BOOTSTRAP%\bin\go.exe" build -o cmd\dist\dist.exe .\cmd\dist endlocal if errorlevel 1 goto fail diff --git a/src/make.rc b/src/make.rc index 273d151190..4597403a04 100755 --- a/src/make.rc +++ b/src/make.rc @@ -47,10 +47,6 @@ if(~ $1 -v) { shift } -GOENV=off -GOWORK=off -GOFLAGS=() -GO111MODULE=() GOROOT = `{cd .. && pwd} goroot_bootstrap_set = 'true' if(! ~ $#GOROOT_BOOTSTRAP 1){ @@ -88,7 +84,7 @@ if(~ $GOROOT_BOOTSTRAP $GOROOT){ echo 'Building Go cmd/dist using '^$GOROOT_BOOTSTRAP if(~ $#vflag 1) echo cmd/dist -GOROOT=$GOROOT_BOOTSTRAP GOOS='' GOARCH='' GOEXPERIMENT='' GO111MODULE=off $GOROOT_BOOTSTRAP/bin/go build -o cmd/dist/dist ./cmd/dist +GOROOT=$GOROOT_BOOTSTRAP GOOS='' GOARCH='' GOEXPERIMENT='' GO111MODULE=off GOENV=off GOFLAGS='' $GOROOT_BOOTSTRAP/bin/go build -o cmd/dist/dist ./cmd/dist eval `{./cmd/dist/dist env -9} if(~ $#vflag 1) diff --git a/src/math/abs.go b/src/math/abs.go index df83add695..08be14548d 100644 --- a/src/math/abs.go +++ b/src/math/abs.go @@ -7,6 +7,7 @@ package math // Abs returns the absolute value of x. // // Special cases are: +// // Abs(±Inf) = +Inf // Abs(NaN) = NaN func Abs(x float64) float64 { diff --git a/src/math/acosh.go b/src/math/acosh.go index f74e0b62fb..a85d003d3e 100644 --- a/src/math/acosh.go +++ b/src/math/acosh.go @@ -36,6 +36,7 @@ package math // Acosh returns the inverse hyperbolic cosine of x. // // Special cases are: +// // Acosh(+Inf) = +Inf // Acosh(x) = NaN if x < 1 // Acosh(NaN) = NaN diff --git a/src/math/asin.go b/src/math/asin.go index 989a74155b..8e1b2ab491 100644 --- a/src/math/asin.go +++ b/src/math/asin.go @@ -14,6 +14,7 @@ package math // Asin returns the arcsine, in radians, of x. // // Special cases are: +// // Asin(±0) = ±0 // Asin(x) = NaN if x < -1 or x > 1 func Asin(x float64) float64 { @@ -52,6 +53,7 @@ func asin(x float64) float64 { // Acos returns the arccosine, in radians, of x. // // Special case is: +// // Acos(x) = NaN if x < -1 or x > 1 func Acos(x float64) float64 { if haveArchAcos { diff --git a/src/math/asinh.go b/src/math/asinh.go index 6dcb241c1f..6f6e9e4608 100644 --- a/src/math/asinh.go +++ b/src/math/asinh.go @@ -33,6 +33,7 @@ package math // Asinh returns the inverse hyperbolic sine of x. // // Special cases are: +// // Asinh(±0) = ±0 // Asinh(±Inf) = ±Inf // Asinh(NaN) = NaN diff --git a/src/math/atan.go b/src/math/atan.go index 69af860161..e722e99757 100644 --- a/src/math/atan.go +++ b/src/math/atan.go @@ -90,8 +90,9 @@ func satan(x float64) float64 { // Atan returns the arctangent, in radians, of x. // // Special cases are: -// Atan(±0) = ±0 -// Atan(±Inf) = ±Pi/2 +// +// Atan(±0) = ±0 +// Atan(±Inf) = ±Pi/2 func Atan(x float64) float64 { if haveArchAtan { return archAtan(x) diff --git a/src/math/atan2.go b/src/math/atan2.go index 11d7e81acd..c324ed0a15 100644 --- a/src/math/atan2.go +++ b/src/math/atan2.go @@ -9,6 +9,7 @@ package math // of the return value. // // Special cases are (in order): +// // Atan2(y, NaN) = NaN // Atan2(NaN, x) = NaN // Atan2(+0, x>=0) = +0 diff --git a/src/math/atanh.go b/src/math/atanh.go index fe8bd6d8a4..9d594625a5 100644 --- a/src/math/atanh.go +++ b/src/math/atanh.go @@ -39,6 +39,7 @@ package math // Atanh returns the inverse hyperbolic tangent of x. // // Special cases are: +// // Atanh(1) = +Inf // Atanh(±0) = ±0 // Atanh(-1) = -Inf diff --git a/src/math/big/arith_ppc64x.s b/src/math/big/arith_ppc64x.s index 68c6286494..601cafe6bb 100644 --- a/src/math/big/arith_ppc64x.s +++ b/src/math/big/arith_ppc64x.s @@ -346,11 +346,161 @@ done: MOVD R4, c+56(FP) RET +//func shlVU(z, x []Word, s uint) (c Word) TEXT ·shlVU(SB), NOSPLIT, $0 - BR ·shlVU_g(SB) + MOVD z+0(FP), R3 + MOVD x+24(FP), R6 + MOVD s+48(FP), R9 + MOVD z_len+8(FP), R4 + MOVD x_len+32(FP), R7 + CMP R9, R0 // s==0 copy(z,x) + BEQ zeroshift + CMP R4, R0 // len(z)==0 return + BEQ done + ADD $-1, R4, R5 // len(z)-1 + SUBC R9, $64, R4 // ŝ=_W-s, we skip & by _W-1 as the caller ensures s < _W(64) + SLD $3, R5, R7 + ADD R6, R7, R15 // save starting address &x[len(z)-1] + ADD R3, R7, R16 // save starting address &z[len(z)-1] + MOVD (R6)(R7), R14 + SRD R4, R14, R7 // compute x[len(z)-1]>>ŝ into R7 + CMP R5, R0 // iterate from i=len(z)-1 to 0 + BEQ loopexit // Already at end? + MOVD 0(R15),R10 // x[i] +shloop: + SLD R9, R10, R10 // x[i]<>ŝ + OR R11, R10, R10 + MOVD R10, 0(R16) // z[i-1]=x[i]<>ŝ + MOVD R14, R10 // reuse x[i-1] for next iteration + ADD $-8, R16 // i-- + CMP R15, R6 // &x[i-1]>&x[0]? + BGT shloop +loopexit: + MOVD 0(R6), R4 + SLD R9, R4, R4 + MOVD R4, 0(R3) // z[0]=x[0]<>ŝ into c + RET + +zeroshift: + CMP R6, R0 // x is null, nothing to copy + BEQ done + CMP R6, R3 // if x is same as z, nothing to copy + BEQ done + CMP R7, R4 + ISEL $0, R7, R4, R7 // Take the lower bound of lengths of x,z + SLD $3, R7, R7 + SUB R6, R3, R11 // dest - src + CMPU R11, R7, CR2 // < len? + BLT CR2, backward // there is overlap, copy backwards + MOVD $0, R14 + // shlVU processes backwards, but added a forward copy option + // since its faster on POWER +repeat: + MOVD (R6)(R14), R15 // Copy 8 bytes at a time + MOVD R15, (R3)(R14) + ADD $8, R14 + CMP R14, R7 // More 8 bytes left? + BLT repeat + BR done +backward: + ADD $-8,R7, R14 +repeatback: + MOVD (R6)(R14), R15 // copy x into z backwards + MOVD R15, (R3)(R14) // copy 8 bytes at a time + SUB $8, R14 + CMP R14, $-8 // More 8 bytes left? + BGT repeatback + +done: + MOVD R0, c+56(FP) // c=0 + RET + +//func shrVU(z, x []Word, s uint) (c Word) TEXT ·shrVU(SB), NOSPLIT, $0 - BR ·shrVU_g(SB) + MOVD z+0(FP), R3 + MOVD x+24(FP), R6 + MOVD s+48(FP), R9 + MOVD z_len+8(FP), R4 + MOVD x_len+32(FP), R7 + + CMP R9, R0 // s==0, copy(z,x) + BEQ zeroshift + CMP R4, R0 // len(z)==0 return + BEQ done + SUBC R9, $64, R5 // ŝ=_W-s, we skip & by _W-1 as the caller ensures s < _W(64) + + MOVD 0(R6), R7 + SLD R5, R7, R7 // compute x[0]<<ŝ + MOVD $1, R8 // iterate from i=1 to i=3, else jump to scalar loop + CMP R4, $3 + BLT scalar + MTVSRD R9, VS38 // s + VSPLTB $7, V6, V4 + MTVSRD R5, VS39 // ŝ + VSPLTB $7, V7, V2 + ADD $-2, R4, R16 + PCALIGN $16 +loopback: + ADD $-1, R8, R10 + SLD $3, R10 + LXVD2X (R6)(R10), VS32 // load x[i-1], x[i] + SLD $3, R8, R12 + LXVD2X (R6)(R12), VS33 // load x[i], x[i+1] + + VSRD V0, V4, V3 // x[i-1]>>s, x[i]>>s + VSLD V1, V2, V5 // x[i]<<ŝ, x[i+1]<<ŝ + VOR V3, V5, V5 // Or(|) the two registers together + STXVD2X VS37, (R3)(R10) // store into z[i-1] and z[i] + ADD $2, R8 // Done processing 2 entries, i and i+1 + CMP R8, R16 // Are there at least a couple of more entries left? + BLE loopback + CMP R8, R4 // Are we at the last element? + BEQ loopexit +scalar: + ADD $-1, R8, R10 + SLD $3, R10 + MOVD (R6)(R10),R11 + SRD R9, R11, R11 // x[len(z)-2] >> s + SLD $3, R8, R12 + MOVD (R6)(R12), R12 + SLD R5, R12, R12 // x[len(z)-1]<<ŝ + OR R12, R11, R11 // x[len(z)-2]>>s | x[len(z)-1]<<ŝ + MOVD R11, (R3)(R10) // z[len(z)-2]=x[len(z)-2]>>s | x[len(z)-1]<<ŝ +loopexit: + ADD $-1, R4 + SLD $3, R4 + MOVD (R6)(R4), R5 + SRD R9, R5, R5 // x[len(z)-1]>>s + MOVD R5, (R3)(R4) // z[len(z)-1]=x[len(z)-1]>>s + MOVD R7, c+56(FP) // store pre-computed x[0]<<ŝ into c + RET + +zeroshift: + CMP R6, R0 // x is null, nothing to copy + BEQ done + CMP R6, R3 // if x is same as z, nothing to copy + BEQ done + CMP R7, R4 + ISEL $0, R7, R4, R7 // Take the lower bounds of lengths of x, z + SLD $3, R7, R7 + MOVD $0, R14 +repeat: + MOVD (R6)(R14), R15 // copy 8 bytes at a time + MOVD R15, (R3)(R14) // shrVU processes bytes only forwards + ADD $8, R14 + CMP R14, R7 // More 8 bytes left? + BLT repeat +done: + MOVD R0, c+56(FP) + RET // func mulAddVWW(z, x []Word, y, r Word) (c Word) TEXT ·mulAddVWW(SB), NOSPLIT, $0 diff --git a/src/math/big/example_rat_test.go b/src/math/big/example_rat_test.go index a97117001c..dc67430980 100644 --- a/src/math/big/example_rat_test.go +++ b/src/math/big/example_rat_test.go @@ -10,10 +10,13 @@ import ( ) // Use the classic continued fraction for e -// e = [1; 0, 1, 1, 2, 1, 1, ... 2n, 1, 1, ...] +// +// e = [1; 0, 1, 1, 2, 1, 1, ... 2n, 1, 1, ...] +// // i.e., for the nth term, use -// 1 if n mod 3 != 1 -// (n-1)/3 * 2 if n mod 3 == 1 +// +// 1 if n mod 3 != 1 +// (n-1)/3 * 2 if n mod 3 == 1 func recur(n, lim int64) *big.Rat { term := new(big.Rat) if n%3 != 1 { diff --git a/src/math/big/float.go b/src/math/big/float.go index 70c7b794a4..84666d817b 100644 --- a/src/math/big/float.go +++ b/src/math/big/float.go @@ -21,7 +21,7 @@ const debugFloat = false // enable for debugging // A nonzero finite Float represents a multi-precision floating point number // -// sign × mantissa × 2**exponent +// sign × mantissa × 2**exponent // // with 0.5 <= mantissa < 1.0, and MinExp <= exponent <= MaxExp. // A Float may also be zero (+0, -0) or infinite (+Inf, -Inf). @@ -1668,9 +1668,9 @@ func (z *Float) Quo(x, y *Float) *Float { // Cmp compares x and y and returns: // -// -1 if x < y -// 0 if x == y (incl. -0 == 0, -Inf == -Inf, and +Inf == +Inf) -// +1 if x > y +// -1 if x < y +// 0 if x == y (incl. -0 == 0, -Inf == -Inf, and +Inf == +Inf) +// +1 if x > y func (x *Float) Cmp(y *Float) int { if debugFloat { x.validate() diff --git a/src/math/big/floatconv.go b/src/math/big/floatconv.go index 93f7195219..3bb51c7dea 100644 --- a/src/math/big/floatconv.go +++ b/src/math/big/floatconv.go @@ -215,7 +215,7 @@ func (z *Float) pow5(n uint64) *Float { // point number with a mantissa in the given conversion base (the exponent // is always a decimal number), or a string representing an infinite value. // -// For base 0, an underscore character ``_'' may appear between a base +// For base 0, an underscore character “_” may appear between a base // prefix and an adjacent digit, and between successive digits; such // underscores do not change the value of the number, or the returned // digit count. Incorrect placement of underscores is reported as an @@ -229,22 +229,22 @@ func (z *Float) pow5(n uint64) *Float { // If z's precision is 0, it is changed to 64 before rounding takes effect. // The number must be of the form: // -// number = [ sign ] ( float | "inf" | "Inf" ) . -// sign = "+" | "-" . -// float = ( mantissa | prefix pmantissa ) [ exponent ] . -// prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] . -// mantissa = digits "." [ digits ] | digits | "." digits . -// pmantissa = [ "_" ] digits "." [ digits ] | [ "_" ] digits | "." digits . -// exponent = ( "e" | "E" | "p" | "P" ) [ sign ] digits . -// digits = digit { [ "_" ] digit } . -// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . +// number = [ sign ] ( float | "inf" | "Inf" ) . +// sign = "+" | "-" . +// float = ( mantissa | prefix pmantissa ) [ exponent ] . +// prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] . +// mantissa = digits "." [ digits ] | digits | "." digits . +// pmantissa = [ "_" ] digits "." [ digits ] | [ "_" ] digits | "." digits . +// exponent = ( "e" | "E" | "p" | "P" ) [ sign ] digits . +// digits = digit { [ "_" ] digit } . +// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . // // The base argument must be 0, 2, 8, 10, or 16. Providing an invalid base // argument will lead to a run-time panic. // // For base 0, the number prefix determines the actual base: A prefix of -// ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and -// ``0x'' or ``0X'' selects base 16. Otherwise, the actual base is 10 and +// “0b” or “0B” selects base 2, “0o” or “0O” selects base 8, and +// “0x” or “0X” selects base 16. Otherwise, the actual base is 10 and // no prefix is accepted. The octal prefix "0" is not supported (a leading // "0" is simply considered a "0"). // diff --git a/src/math/big/int.go b/src/math/big/int.go index 700d00d031..a31cf27418 100644 --- a/src/math/big/int.go +++ b/src/math/big/int.go @@ -231,7 +231,7 @@ func (z *Int) Rem(x, y *Int) *Int { // q = x/y with the result truncated to zero // r = x - y*q // -// (See Daan Leijen, ``Division and Modulus for Computer Scientists''.) +// (See Daan Leijen, “Division and Modulus for Computer Scientists”.) // See DivMod for Euclidean division and modulus (unlike Go). func (z *Int) QuoRem(x, y, r *Int) (*Int, *Int) { z.abs, r.abs = z.abs.div(r.abs, x.abs, y.abs) @@ -285,8 +285,8 @@ func (z *Int) Mod(x, y *Int) *Int { // q = x div y such that // m = x - y*q with 0 <= m < |y| // -// (See Raymond T. Boute, ``The Euclidean definition of the functions -// div and mod''. ACM Transactions on Programming Languages and +// (See Raymond T. Boute, “The Euclidean definition of the functions +// div and mod”. ACM Transactions on Programming Languages and // Systems (TOPLAS), 14(2):127-144, New York, NY, USA, 4/1992. // ACM press.) // See QuoRem for T-division and modulus (like Go). @@ -310,9 +310,9 @@ func (z *Int) DivMod(x, y, m *Int) (*Int, *Int) { // Cmp compares x and y and returns: // -// -1 if x < y -// 0 if x == y -// +1 if x > y +// -1 if x < y +// 0 if x == y +// +1 if x > y func (x *Int) Cmp(y *Int) (r int) { // x cmp y == x cmp y // x cmp (-y) == x @@ -336,9 +336,9 @@ func (x *Int) Cmp(y *Int) (r int) { // CmpAbs compares the absolute values of x and y and returns: // -// -1 if |x| < |y| -// 0 if |x| == |y| -// +1 if |x| > |y| +// -1 if |x| < |y| +// 0 if |x| == |y| +// +1 if |x| > |y| func (x *Int) CmpAbs(y *Int) int { return x.abs.cmp(y.abs) } @@ -400,8 +400,8 @@ func (x *Int) IsUint64() bool { // // The base argument must be 0 or a value between 2 and MaxBase. // For base 0, the number prefix determines the actual base: A prefix of -// ``0b'' or ``0B'' selects base 2, ``0'', ``0o'' or ``0O'' selects base 8, -// and ``0x'' or ``0X'' selects base 16. Otherwise, the selected base is 10 +// “0b” or “0B” selects base 2, “0”, “0o” or “0O” selects base 8, +// and “0x” or “0X” selects base 16. Otherwise, the selected base is 10 // and no prefix is accepted. // // For bases <= 36, lower and upper case letters are considered the same: @@ -409,7 +409,7 @@ func (x *Int) IsUint64() bool { // For bases > 36, the upper case letters 'A' to 'Z' represent the digit // values 36 to 61. // -// For base 0, an underscore character ``_'' may appear between a base +// For base 0, an underscore character “_” may appear between a base // prefix and an adjacent digit, and between successive digits; such // underscores do not change the value of the number. // Incorrect placement of underscores is reported as an error if there @@ -556,8 +556,10 @@ func (z *Int) GCD(x, y, a, b *Int) *Int { // lehmerSimulate attempts to simulate several Euclidean update steps // using the leading digits of A and B. It returns u0, u1, v0, v1 // such that A and B can be updated as: -// A = u0*A + v0*B -// B = u1*A + v1*B +// +// A = u0*A + v0*B +// B = u1*A + v1*B +// // Requirements: A >= B and len(B.abs) >= 2 // Since we are calculating with full words to avoid overflow, // we use 'even' to track the sign of the cosequences. @@ -608,8 +610,10 @@ func lehmerSimulate(A, B *Int) (u0, u1, v0, v1 Word, even bool) { } // lehmerUpdate updates the inputs A and B such that: -// A = u0*A + v0*B -// B = u1*A + v1*B +// +// A = u0*A + v0*B +// B = u1*A + v1*B +// // where the signs of u0, u1, v0, v1 are given by even // For even == true: u0, v1 >= 0 && u1, v0 <= 0 // For even == false: u0, v1 <= 0 && u1, v0 >= 0 @@ -883,9 +887,11 @@ func Jacobi(x, y *Int) int { } // modSqrt3Mod4 uses the identity -// (a^((p+1)/4))^2 mod p -// == u^(p+1) mod p -// == u^2 mod p +// +// (a^((p+1)/4))^2 mod p +// == u^(p+1) mod p +// == u^2 mod p +// // to calculate the square root of any quadratic residue mod p quickly for 3 // mod 4 primes. func (z *Int) modSqrt3Mod4Prime(x, p *Int) *Int { @@ -896,9 +902,11 @@ func (z *Int) modSqrt3Mod4Prime(x, p *Int) *Int { } // modSqrt5Mod8 uses Atkin's observation that 2 is not a square mod p -// alpha == (2*a)^((p-5)/8) mod p -// beta == 2*a*alpha^2 mod p is a square root of -1 -// b == a*alpha*(beta-1) mod p is a square root of a +// +// alpha == (2*a)^((p-5)/8) mod p +// beta == 2*a*alpha^2 mod p is a square root of -1 +// b == a*alpha*(beta-1) mod p is a square root of a +// // to calculate the square root of any quadratic residue mod p quickly for 5 // mod 8 primes. func (z *Int) modSqrt5Mod8Prime(x, p *Int) *Int { diff --git a/src/math/big/intconv.go b/src/math/big/intconv.go index 2fe10ff0a2..a3a4023caa 100644 --- a/src/math/big/intconv.go +++ b/src/math/big/intconv.go @@ -174,8 +174,8 @@ func (x *Int) Format(s fmt.State, ch rune) { // // The base argument must be 0 or a value from 2 through MaxBase. If the base // is 0, the string prefix determines the actual conversion base. A prefix of -// ``0b'' or ``0B'' selects base 2; a ``0'', ``0o'', or ``0O'' prefix selects -// base 8, and a ``0x'' or ``0X'' prefix selects base 16. Otherwise the selected +// “0b” or “0B” selects base 2; a “0”, “0o”, or “0O” prefix selects +// base 8, and a “0x” or “0X” prefix selects base 16. Otherwise the selected // base is 10. func (z *Int) scan(r io.ByteScanner, base int) (*Int, int, error) { // determine sign diff --git a/src/math/big/nat.go b/src/math/big/nat.go index ee0c63eb28..5cc42b80dc 100644 --- a/src/math/big/nat.go +++ b/src/math/big/nat.go @@ -22,7 +22,7 @@ import ( // An unsigned integer x of the form // -// x = x[n-1]*_B^(n-1) + x[n-2]*_B^(n-2) + ... + x[1]*_B + x[0] +// x = x[n-1]*_B^(n-1) + x[n-2]*_B^(n-2) + ... + x[1]*_B + x[0] // // with 0 <= x[i] < _B and 0 <= i < n is stored in a slice of length n, // with the digits x[i] as the slice elements. diff --git a/src/math/big/natconv.go b/src/math/big/natconv.go index 99488ac833..21fdab53fd 100644 --- a/src/math/big/natconv.go +++ b/src/math/big/natconv.go @@ -66,7 +66,7 @@ var ( // scan returns the corresponding natural number res, the actual base b, // a digit count, and a read or syntax error err, if any. // -// For base 0, an underscore character ``_'' may appear between a base +// For base 0, an underscore character “_” may appear between a base // prefix and an adjacent digit, and between successive digits; such // underscores do not change the value of the number, or the returned // digit count. Incorrect placement of underscores is reported as an @@ -74,12 +74,12 @@ var ( // not recognized and thus terminate scanning like any other character // that is not a valid radix point or digit. // -// number = mantissa | prefix pmantissa . -// prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] . -// mantissa = digits "." [ digits ] | digits | "." digits . -// pmantissa = [ "_" ] digits "." [ digits ] | [ "_" ] digits | "." digits . -// digits = digit { [ "_" ] digit } . -// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . +// number = mantissa | prefix pmantissa . +// prefix = "0" [ "b" | "B" | "o" | "O" | "x" | "X" ] . +// mantissa = digits "." [ digits ] | digits | "." digits . +// pmantissa = [ "_" ] digits "." [ digits ] | [ "_" ] digits | "." digits . +// digits = digit { [ "_" ] digit } . +// digit = "0" ... "9" | "a" ... "z" | "A" ... "Z" . // // Unless fracOk is set, the base argument must be 0 or a value between // 2 and MaxBase. If fracOk is set, the base argument must be one of @@ -87,8 +87,8 @@ var ( // time panic. // // For base 0, the number prefix determines the actual base: A prefix of -// ``0b'' or ``0B'' selects base 2, ``0o'' or ``0O'' selects base 8, and -// ``0x'' or ``0X'' selects base 16. If fracOk is false, a ``0'' prefix +// “0b” or “0B” selects base 2, “0o” or “0O” selects base 8, and +// “0x” or “0X” selects base 16. If fracOk is false, a “0” prefix // (immediately followed by digits) selects base 8 as well. Otherwise, // the selected base is 10 and no prefix is accepted. // @@ -434,8 +434,9 @@ func (q nat) convertWords(s []byte, b Word, ndigits int, bb Word, table []diviso // Split blocks greater than leafSize Words (or set to 0 to disable recursive conversion) // Benchmark and configure leafSize using: go test -bench="Leaf" -// 8 and 16 effective on 3.0 GHz Xeon "Clovertown" CPU (128 byte cache lines) -// 8 and 16 effective on 2.66 GHz Core 2 Duo "Penryn" CPU +// +// 8 and 16 effective on 3.0 GHz Xeon "Clovertown" CPU (128 byte cache lines) +// 8 and 16 effective on 2.66 GHz Core 2 Duo "Penryn" CPU var leafSize int = 8 // number of Word-size binary values treat as a monolithic block type divisor struct { diff --git a/src/math/big/rat.go b/src/math/big/rat.go index e77da67d1b..700a643265 100644 --- a/src/math/big/rat.go +++ b/src/math/big/rat.go @@ -478,9 +478,9 @@ func (z *Int) scaleDenom(x *Int, f nat) { // Cmp compares x and y and returns: // -// -1 if x < y -// 0 if x == y -// +1 if x > y +// -1 if x < y +// 0 if x == y +// +1 if x > y func (x *Rat) Cmp(y *Rat) int { var a, b Int a.scaleDenom(&x.a, y.b.abs) diff --git a/src/math/big/ratconv.go b/src/math/big/ratconv.go index dadd4d7b8e..794a51d007 100644 --- a/src/math/big/ratconv.go +++ b/src/math/big/ratconv.go @@ -41,16 +41,16 @@ func (z *Rat) Scan(s fmt.ScanState, ch rune) error { // success. s can be given as a (possibly signed) fraction "a/b", or as a // floating-point number optionally followed by an exponent. // If a fraction is provided, both the dividend and the divisor may be a -// decimal integer or independently use a prefix of ``0b'', ``0'' or ``0o'', -// or ``0x'' (or their upper-case variants) to denote a binary, octal, or +// decimal integer or independently use a prefix of “0b”, “0” or “0o”, +// or “0x” (or their upper-case variants) to denote a binary, octal, or // hexadecimal integer, respectively. The divisor may not be signed. // If a floating-point number is provided, it may be in decimal form or -// use any of the same prefixes as above but for ``0'' to denote a non-decimal -// mantissa. A leading ``0'' is considered a decimal leading 0; it does not +// use any of the same prefixes as above but for “0” to denote a non-decimal +// mantissa. A leading “0” is considered a decimal leading 0; it does not // indicate octal representation in this case. -// An optional base-10 ``e'' or base-2 ``p'' (or their upper-case variants) +// An optional base-10 “e” or base-2 “p” (or their upper-case variants) // exponent may be provided as well, except for hexadecimal floats which -// only accept an (optional) ``p'' exponent (because an ``e'' or ``E'' cannot +// only accept an (optional) “p” exponent (because an “e” or “E” cannot // be distinguished from a mantissa digit). If the exponent's absolute value // is too large, the operation may fail. // The entire string, not just a prefix, must be valid for success. If the @@ -205,10 +205,10 @@ func (z *Rat) SetString(s string) (*Rat, bool) { } // scanExponent scans the longest possible prefix of r representing a base 10 -// (``e'', ``E'') or a base 2 (``p'', ``P'') exponent, if any. It returns the +// (“e”, “E”) or a base 2 (“p”, “P”) exponent, if any. It returns the // exponent, the exponent base (10 or 2), or a read or syntax error, if any. // -// If sepOk is set, an underscore character ``_'' may appear between successive +// If sepOk is set, an underscore character “_” may appear between successive // exponent digits; such underscores do not change the value of the exponent. // Incorrect placement of underscores is reported as an error if there are no // other errors. If sepOk is not set, underscores are not recognized and thus diff --git a/src/math/big/sqrt.go b/src/math/big/sqrt.go index 0d50164557..b4b03743f4 100644 --- a/src/math/big/sqrt.go +++ b/src/math/big/sqrt.go @@ -82,7 +82,9 @@ func (z *Float) Sqrt(x *Float) *Float { } // Compute √x (to z.prec precision) by solving -// 1/t² - x = 0 +// +// 1/t² - x = 0 +// // for t (using Newton's method), and then inverting. func (z *Float) sqrtInverse(x *Float) { // let diff --git a/src/math/bits.go b/src/math/bits.go index 77bcdbe1ce..c5cb93b159 100644 --- a/src/math/bits.go +++ b/src/math/bits.go @@ -27,10 +27,10 @@ func Inf(sign int) float64 { return Float64frombits(v) } -// NaN returns an IEEE 754 ``not-a-number'' value. +// NaN returns an IEEE 754 “not-a-number” value. func NaN() float64 { return Float64frombits(uvnan) } -// IsNaN reports whether f is an IEEE 754 ``not-a-number'' value. +// IsNaN reports whether f is an IEEE 754 “not-a-number” value. func IsNaN(f float64) (is bool) { // IEEE 754 says that only NaNs satisfy f != f. // To avoid the floating-point hardware, could use: diff --git a/src/math/cbrt.go b/src/math/cbrt.go index 45c8ecb3a8..e5e9548cb1 100644 --- a/src/math/cbrt.go +++ b/src/math/cbrt.go @@ -19,6 +19,7 @@ package math // Cbrt returns the cube root of x. // // Special cases are: +// // Cbrt(±0) = ±0 // Cbrt(±Inf) = ±Inf // Cbrt(NaN) = NaN diff --git a/src/math/cmplx/isnan.go b/src/math/cmplx/isnan.go index d3382c05ee..fed442cb48 100644 --- a/src/math/cmplx/isnan.go +++ b/src/math/cmplx/isnan.go @@ -18,7 +18,7 @@ func IsNaN(x complex128) bool { return false } -// NaN returns a complex ``not-a-number'' value. +// NaN returns a complex “not-a-number” value. func NaN() complex128 { nan := math.NaN() return complex(nan, nan) diff --git a/src/math/cmplx/pow.go b/src/math/cmplx/pow.go index 5a405f8e96..666bba28c5 100644 --- a/src/math/cmplx/pow.go +++ b/src/math/cmplx/pow.go @@ -44,6 +44,7 @@ import "math" // Pow returns x**y, the base-x exponential of y. // For generalized compatibility with math.Pow: +// // Pow(0, ±0) returns 1+0i // Pow(0, c) for real(c)<0 returns Inf+0i if imag(c) is zero, otherwise Inf+Inf i. func Pow(x, y complex128) complex128 { diff --git a/src/math/copysign.go b/src/math/copysign.go index 719c64b9eb..3a30afb413 100644 --- a/src/math/copysign.go +++ b/src/math/copysign.go @@ -4,9 +4,9 @@ package math -// Copysign returns a value with the magnitude -// of x and the sign of y. -func Copysign(x, y float64) float64 { - const sign = 1 << 63 - return Float64frombits(Float64bits(x)&^sign | Float64bits(y)&sign) +// Copysign returns a value with the magnitude of f +// and the sign of sign. +func Copysign(f, sign float64) float64 { + const signBit = 1 << 63 + return Float64frombits(Float64bits(f)&^signBit | Float64bits(sign)&signBit) } diff --git a/src/math/dim.go b/src/math/dim.go index 6a857bbe41..6a286cdc75 100644 --- a/src/math/dim.go +++ b/src/math/dim.go @@ -7,6 +7,7 @@ package math // Dim returns the maximum of x-y or 0. // // Special cases are: +// // Dim(+Inf, +Inf) = NaN // Dim(-Inf, -Inf) = NaN // Dim(x, NaN) = Dim(NaN, x) = NaN @@ -28,6 +29,7 @@ func Dim(x, y float64) float64 { // Max returns the larger of x or y. // // Special cases are: +// // Max(x, +Inf) = Max(+Inf, x) = +Inf // Max(x, NaN) = Max(NaN, x) = NaN // Max(+0, ±0) = Max(±0, +0) = +0 @@ -61,6 +63,7 @@ func max(x, y float64) float64 { // Min returns the smaller of x or y. // // Special cases are: +// // Min(x, -Inf) = Min(-Inf, x) = -Inf // Min(x, NaN) = Min(NaN, x) = NaN // Min(-0, ±0) = Min(±0, -0) = -0 diff --git a/src/math/erf.go b/src/math/erf.go index 4d6fe472f1..ba00c7d03e 100644 --- a/src/math/erf.go +++ b/src/math/erf.go @@ -182,6 +182,7 @@ const ( // Erf returns the error function of x. // // Special cases are: +// // Erf(+Inf) = 1 // Erf(-Inf) = -1 // Erf(NaN) = NaN @@ -266,6 +267,7 @@ func erf(x float64) float64 { // Erfc returns the complementary error function of x. // // Special cases are: +// // Erfc(+Inf) = 0 // Erfc(-Inf) = 2 // Erfc(NaN) = NaN diff --git a/src/math/erfinv.go b/src/math/erfinv.go index ee423d33e4..eed0feb42d 100644 --- a/src/math/erfinv.go +++ b/src/math/erfinv.go @@ -69,6 +69,7 @@ const ( // Erfinv returns the inverse error function of x. // // Special cases are: +// // Erfinv(1) = +Inf // Erfinv(-1) = -Inf // Erfinv(x) = NaN if x < -1 or x > 1 @@ -118,6 +119,7 @@ func Erfinv(x float64) float64 { // Erfcinv returns the inverse of Erfc(x). // // Special cases are: +// // Erfcinv(0) = +Inf // Erfcinv(2) = -Inf // Erfcinv(x) = NaN if x < 0 or x > 2 diff --git a/src/math/exp.go b/src/math/exp.go index d05eb91fb0..760795f46f 100644 --- a/src/math/exp.go +++ b/src/math/exp.go @@ -7,8 +7,10 @@ package math // Exp returns e**x, the base-e exponential of x. // // Special cases are: +// // Exp(+Inf) = +Inf // Exp(NaN) = NaN +// // Very large values overflow to 0 or +Inf. // Very small values underflow to 1. func Exp(x float64) float64 { diff --git a/src/math/expm1.go b/src/math/expm1.go index 66d3421661..ff1c82f524 100644 --- a/src/math/expm1.go +++ b/src/math/expm1.go @@ -117,9 +117,11 @@ package math // It is more accurate than Exp(x) - 1 when x is near zero. // // Special cases are: +// // Expm1(+Inf) = +Inf // Expm1(-Inf) = -1 // Expm1(NaN) = NaN +// // Very large values overflow to -1 or +Inf. func Expm1(x float64) float64 { if haveArchExpm1 { diff --git a/src/math/floor.go b/src/math/floor.go index 7913a900e3..cb5856424b 100644 --- a/src/math/floor.go +++ b/src/math/floor.go @@ -7,6 +7,7 @@ package math // Floor returns the greatest integer value less than or equal to x. // // Special cases are: +// // Floor(±0) = ±0 // Floor(±Inf) = ±Inf // Floor(NaN) = NaN @@ -35,6 +36,7 @@ func floor(x float64) float64 { // Ceil returns the least integer value greater than or equal to x. // // Special cases are: +// // Ceil(±0) = ±0 // Ceil(±Inf) = ±Inf // Ceil(NaN) = NaN @@ -52,6 +54,7 @@ func ceil(x float64) float64 { // Trunc returns the integer value of x. // // Special cases are: +// // Trunc(±0) = ±0 // Trunc(±Inf) = ±Inf // Trunc(NaN) = NaN @@ -73,6 +76,7 @@ func trunc(x float64) float64 { // Round returns the nearest integer, rounding half away from zero. // // Special cases are: +// // Round(±0) = ±0 // Round(±Inf) = ±Inf // Round(NaN) = NaN @@ -110,6 +114,7 @@ func Round(x float64) float64 { // RoundToEven returns the nearest integer, rounding ties to even. // // Special cases are: +// // RoundToEven(±0) = ±0 // RoundToEven(±Inf) = ±Inf // RoundToEven(NaN) = NaN diff --git a/src/math/frexp.go b/src/math/frexp.go index 3c8a909ed0..e194947e64 100644 --- a/src/math/frexp.go +++ b/src/math/frexp.go @@ -10,6 +10,7 @@ package math // with the absolute value of frac in the interval [½, 1). // // Special cases are: +// // Frexp(±0) = ±0, 0 // Frexp(±Inf) = ±Inf, 0 // Frexp(NaN) = NaN, 0 diff --git a/src/math/gamma.go b/src/math/gamma.go index cc9e869496..86c6723258 100644 --- a/src/math/gamma.go +++ b/src/math/gamma.go @@ -121,6 +121,7 @@ func stirling(x float64) (float64, float64) { // Gamma returns the Gamma function of x. // // Special cases are: +// // Gamma(+Inf) = +Inf // Gamma(+0) = +Inf // Gamma(-0) = -Inf diff --git a/src/math/hypot.go b/src/math/hypot.go index 12af17766d..4e79de0e9b 100644 --- a/src/math/hypot.go +++ b/src/math/hypot.go @@ -12,6 +12,7 @@ package math // unnecessary overflow and underflow. // // Special cases are: +// // Hypot(±Inf, q) = +Inf // Hypot(p, ±Inf) = +Inf // Hypot(NaN, q) = NaN diff --git a/src/math/j0.go b/src/math/j0.go index cb5f07bca6..a311e18d62 100644 --- a/src/math/j0.go +++ b/src/math/j0.go @@ -70,6 +70,7 @@ package math // J0 returns the order-zero Bessel function of the first kind. // // Special cases are: +// // J0(±Inf) = 0 // J0(0) = 1 // J0(NaN) = NaN @@ -147,6 +148,7 @@ func J0(x float64) float64 { // Y0 returns the order-zero Bessel function of the second kind. // // Special cases are: +// // Y0(+Inf) = 0 // Y0(0) = -Inf // Y0(x < 0) = NaN diff --git a/src/math/j1.go b/src/math/j1.go index 7c7d279730..cc19e75b95 100644 --- a/src/math/j1.go +++ b/src/math/j1.go @@ -69,6 +69,7 @@ package math // J1 returns the order-one Bessel function of the first kind. // // Special cases are: +// // J1(±Inf) = 0 // J1(NaN) = NaN func J1(x float64) float64 { @@ -147,6 +148,7 @@ func J1(x float64) float64 { // Y1 returns the order-one Bessel function of the second kind. // // Special cases are: +// // Y1(+Inf) = 0 // Y1(0) = -Inf // Y1(x < 0) = NaN diff --git a/src/math/jn.go b/src/math/jn.go index b1aca8ff6b..3491692a96 100644 --- a/src/math/jn.go +++ b/src/math/jn.go @@ -48,6 +48,7 @@ package math // Jn returns the order-n Bessel function of the first kind. // // Special cases are: +// // Jn(n, ±Inf) = 0 // Jn(n, NaN) = NaN func Jn(n int, x float64) float64 { @@ -225,6 +226,7 @@ func Jn(n int, x float64) float64 { // Yn returns the order-n Bessel function of the second kind. // // Special cases are: +// // Yn(n, +Inf) = 0 // Yn(n ≥ 0, 0) = -Inf // Yn(n < 0, 0) = +Inf if n is odd, -Inf if n is even diff --git a/src/math/ldexp.go b/src/math/ldexp.go index 55c82f1e84..df365c0b1a 100644 --- a/src/math/ldexp.go +++ b/src/math/ldexp.go @@ -8,6 +8,7 @@ package math // It returns frac × 2**exp. // // Special cases are: +// // Ldexp(±0, exp) = ±0 // Ldexp(±Inf, exp) = ±Inf // Ldexp(NaN, exp) = NaN diff --git a/src/math/lgamma.go b/src/math/lgamma.go index 7af5871744..4058ad6631 100644 --- a/src/math/lgamma.go +++ b/src/math/lgamma.go @@ -166,6 +166,7 @@ var _lgamW = [...]float64{ // Lgamma returns the natural logarithm and sign (-1 or +1) of Gamma(x). // // Special cases are: +// // Lgamma(+Inf) = +Inf // Lgamma(0) = +Inf // Lgamma(-integer) = +Inf diff --git a/src/math/log.go b/src/math/log.go index 1b3e306adf..695a545e7f 100644 --- a/src/math/log.go +++ b/src/math/log.go @@ -73,6 +73,7 @@ package math // Log returns the natural logarithm of x. // // Special cases are: +// // Log(+Inf) = +Inf // Log(0) = -Inf // Log(x < 0) = NaN diff --git a/src/math/log1p.go b/src/math/log1p.go index c117f7245d..3a7b3854a8 100644 --- a/src/math/log1p.go +++ b/src/math/log1p.go @@ -87,6 +87,7 @@ package math // It is more accurate than Log(1 + x) when x is near zero. // // Special cases are: +// // Log1p(+Inf) = +Inf // Log1p(±0) = ±0 // Log1p(-1) = -Inf diff --git a/src/math/logb.go b/src/math/logb.go index f2769d4fd7..04ba3e968e 100644 --- a/src/math/logb.go +++ b/src/math/logb.go @@ -7,6 +7,7 @@ package math // Logb returns the binary exponent of x. // // Special cases are: +// // Logb(±Inf) = +Inf // Logb(0) = -Inf // Logb(NaN) = NaN @@ -26,6 +27,7 @@ func Logb(x float64) float64 { // Ilogb returns the binary exponent of x as an integer. // // Special cases are: +// // Ilogb(±Inf) = MaxInt32 // Ilogb(0) = MinInt32 // Ilogb(NaN) = MaxInt32 diff --git a/src/math/mod.go b/src/math/mod.go index 6bc5f28832..6f24250cfb 100644 --- a/src/math/mod.go +++ b/src/math/mod.go @@ -13,6 +13,7 @@ package math // sign agrees with that of x. // // Special cases are: +// // Mod(±Inf, y) = NaN // Mod(NaN, y) = NaN // Mod(x, 0) = NaN diff --git a/src/math/modf.go b/src/math/modf.go index bf08dc6556..613a75fc9a 100644 --- a/src/math/modf.go +++ b/src/math/modf.go @@ -8,6 +8,7 @@ package math // that sum to f. Both values have the same sign as f. // // Special cases are: +// // Modf(±Inf) = ±Inf, NaN // Modf(NaN) = NaN, NaN func Modf(f float64) (int float64, frac float64) { diff --git a/src/math/nextafter.go b/src/math/nextafter.go index 9088e4d248..ec18d542d9 100644 --- a/src/math/nextafter.go +++ b/src/math/nextafter.go @@ -7,6 +7,7 @@ package math // Nextafter32 returns the next representable float32 value after x towards y. // // Special cases are: +// // Nextafter32(x, x) = x // Nextafter32(NaN, y) = NaN // Nextafter32(x, NaN) = NaN @@ -29,6 +30,7 @@ func Nextafter32(x, y float32) (r float32) { // Nextafter returns the next representable float64 value after x towards y. // // Special cases are: +// // Nextafter(x, x) = x // Nextafter(NaN, y) = NaN // Nextafter(x, NaN) = NaN diff --git a/src/math/pow.go b/src/math/pow.go index e45a044ae1..3af8c8b649 100644 --- a/src/math/pow.go +++ b/src/math/pow.go @@ -15,6 +15,7 @@ func isOddInt(x float64) bool { // Pow returns x**y, the base-x exponential of y. // // Special cases are (in order): +// // Pow(x, ±0) = 1 for any x // Pow(1, y) = 1 for any y // Pow(x, 1) = x for any x diff --git a/src/math/pow10.go b/src/math/pow10.go index 1234e20885..c31ad8dbc7 100644 --- a/src/math/pow10.go +++ b/src/math/pow10.go @@ -25,6 +25,7 @@ var pow10negtab32 = [...]float64{ // Pow10 returns 10**n, the base-10 exponential of n. // // Special cases are: +// // Pow10(n) = 0 for n < -323 // Pow10(n) = +Inf for n > 308 func Pow10(n int) float64 { diff --git a/src/math/rand/exp.go b/src/math/rand/exp.go index 9a07ba1be0..c1162c19b6 100644 --- a/src/math/rand/exp.go +++ b/src/math/rand/exp.go @@ -26,7 +26,7 @@ const ( // To produce a distribution with a different rate parameter, // callers can adjust the output using: // -// sample = ExpFloat64() / desiredRateParameter +// sample = ExpFloat64() / desiredRateParameter func (r *Rand) ExpFloat64() float64 { for { j := r.Uint32() diff --git a/src/math/rand/normal.go b/src/math/rand/normal.go index 48ecdd5adb..6654479a00 100644 --- a/src/math/rand/normal.go +++ b/src/math/rand/normal.go @@ -33,7 +33,7 @@ func absInt32(i int32) uint32 { // To produce a different normal distribution, callers can // adjust the output using: // -// sample = NormFloat64() * desiredStdDev + desiredMean +// sample = NormFloat64() * desiredStdDev + desiredMean func (r *Rand) NormFloat64() float64 { for { j := int32(r.Uint32()) // Possibly negative diff --git a/src/math/rand/rand.go b/src/math/rand/rand.go index dfbd1fa4e7..4cce3dab64 100644 --- a/src/math/rand/rand.go +++ b/src/math/rand/rand.go @@ -365,7 +365,7 @@ func Read(p []byte) (n int, err error) { return globalRand.Read(p) } // To produce a different normal distribution, callers can // adjust the output using: // -// sample = NormFloat64() * desiredStdDev + desiredMean +// sample = NormFloat64() * desiredStdDev + desiredMean func NormFloat64() float64 { return globalRand.NormFloat64() } // ExpFloat64 returns an exponentially distributed float64 in the range @@ -374,7 +374,7 @@ func NormFloat64() float64 { return globalRand.NormFloat64() } // To produce a distribution with a different rate parameter, // callers can adjust the output using: // -// sample = ExpFloat64() / desiredRateParameter +// sample = ExpFloat64() / desiredRateParameter func ExpFloat64() float64 { return globalRand.ExpFloat64() } type lockedSource struct { diff --git a/src/math/remainder.go b/src/math/remainder.go index bf8bfd5553..8e99345c59 100644 --- a/src/math/remainder.go +++ b/src/math/remainder.go @@ -29,6 +29,7 @@ package math // Remainder returns the IEEE 754 floating-point remainder of x/y. // // Special cases are: +// // Remainder(±Inf, y) = NaN // Remainder(NaN, y) = NaN // Remainder(x, 0) = NaN diff --git a/src/math/sin.go b/src/math/sin.go index d95bb548e8..4793d7e7cd 100644 --- a/src/math/sin.go +++ b/src/math/sin.go @@ -112,6 +112,7 @@ var _cos = [...]float64{ // Cos returns the cosine of the radian argument x. // // Special cases are: +// // Cos(±Inf) = NaN // Cos(NaN) = NaN func Cos(x float64) float64 { @@ -177,6 +178,7 @@ func cos(x float64) float64 { // Sin returns the sine of the radian argument x. // // Special cases are: +// // Sin(±0) = ±0 // Sin(±Inf) = NaN // Sin(NaN) = NaN diff --git a/src/math/sincos.go b/src/math/sincos.go index 5c5726f689..e3fb96094f 100644 --- a/src/math/sincos.go +++ b/src/math/sincos.go @@ -9,6 +9,7 @@ package math // Sincos returns Sin(x), Cos(x). // // Special cases are: +// // Sincos(±0) = ±0, 1 // Sincos(±Inf) = NaN, NaN // Sincos(NaN) = NaN, NaN diff --git a/src/math/sinh.go b/src/math/sinh.go index 9fe9b4e17a..78b3c299d6 100644 --- a/src/math/sinh.go +++ b/src/math/sinh.go @@ -19,6 +19,7 @@ package math // Sinh returns the hyperbolic sine of x. // // Special cases are: +// // Sinh(±0) = ±0 // Sinh(±Inf) = ±Inf // Sinh(NaN) = NaN @@ -71,6 +72,7 @@ func sinh(x float64) float64 { // Cosh returns the hyperbolic cosine of x. // // Special cases are: +// // Cosh(±0) = 1 // Cosh(±Inf) = +Inf // Cosh(NaN) = NaN diff --git a/src/math/sqrt.go b/src/math/sqrt.go index 903d57d5e0..b6d80c2c6f 100644 --- a/src/math/sqrt.go +++ b/src/math/sqrt.go @@ -85,6 +85,7 @@ package math // Sqrt returns the square root of x. // // Special cases are: +// // Sqrt(+Inf) = +Inf // Sqrt(±0) = ±0 // Sqrt(x < 0) = NaN diff --git a/src/math/tan.go b/src/math/tan.go index a25417f527..515dd82f73 100644 --- a/src/math/tan.go +++ b/src/math/tan.go @@ -76,6 +76,7 @@ var _tanQ = [...]float64{ // Tan returns the tangent of the radian argument x. // // Special cases are: +// // Tan(±0) = ±0 // Tan(±Inf) = NaN // Tan(NaN) = NaN diff --git a/src/math/tanh.go b/src/math/tanh.go index a825678424..94ebc3b651 100644 --- a/src/math/tanh.go +++ b/src/math/tanh.go @@ -68,6 +68,7 @@ var tanhQ = [...]float64{ // Tanh returns the hyperbolic tangent of x. // // Special cases are: +// // Tanh(±0) = ±0 // Tanh(±Inf) = ±1 // Tanh(NaN) = NaN diff --git a/src/math/trig_reduce.go b/src/math/trig_reduce.go index 5cdf4fa013..5ecdd8375e 100644 --- a/src/math/trig_reduce.go +++ b/src/math/trig_reduce.go @@ -14,7 +14,9 @@ import ( // where y is given by y = floor(x * (4 / Pi)) and C is the leading partial // terms of 4/Pi. Since the leading terms (PI4A and PI4B in sin.go) have 30 // and 32 trailing zero bits, y should have less than 30 significant bits. +// // y < 1<<30 -> floor(x*4/Pi) < 1<<30 -> x < (1<<30 - 1) * Pi/4 +// // So, conservatively we can take x < 1<<29. // Above this threshold Payne-Hanek range reduction must be used. const reduceThreshold = 1 << 29 diff --git a/src/mime/multipart/multipart.go b/src/mime/multipart/multipart.go index c7bcb4d121..aa05ac8f9c 100644 --- a/src/mime/multipart/multipart.go +++ b/src/mime/multipart/multipart.go @@ -437,7 +437,8 @@ func (r *Reader) isBoundaryDelimiterLine(line []byte) (ret bool) { // skipLWSPChar returns b with leading spaces and tabs removed. // RFC 822 defines: -// LWSP-char = SPACE / HTAB +// +// LWSP-char = SPACE / HTAB func skipLWSPChar(b []byte) []byte { for len(b) > 0 && (b[0] == ' ' || b[0] == '\t') { b = b[1:] diff --git a/src/mime/testdata/test.types.globs2 b/src/mime/testdata/test.types.globs2 index cb5b7899b0..fd9df7078b 100644 --- a/src/mime/testdata/test.types.globs2 +++ b/src/mime/testdata/test.types.globs2 @@ -6,4 +6,6 @@ # mime package test for globs2 50:document/test:*.t3 50:example/test:*.t4 +50:text/plain:*,v +50:application/x-trash:*~ 30:example/do-not-use:*.t4 diff --git a/src/mime/type.go b/src/mime/type.go index bdb8bb319a..465ecf0d59 100644 --- a/src/mime/type.go +++ b/src/mime/type.go @@ -99,11 +99,11 @@ func initMime() { // system's MIME-info database or mime.types file(s) if available under one or // more of these names: // -// /usr/local/share/mime/globs2 -// /usr/share/mime/globs2 -// /etc/mime.types -// /etc/apache2/mime.types -// /etc/apache/mime.types +// /usr/local/share/mime/globs2 +// /usr/share/mime/globs2 +// /etc/mime.types +// /etc/apache2/mime.types +// /etc/apache/mime.types // // On Windows, MIME types are extracted from the registry. // diff --git a/src/mime/type_unix.go b/src/mime/type_unix.go index 52579c56b9..e297ecf5c1 100644 --- a/src/mime/type_unix.go +++ b/src/mime/type_unix.go @@ -40,11 +40,11 @@ func loadMimeGlobsFile(filename string) error { scanner := bufio.NewScanner(f) for scanner.Scan() { - // Each line should be of format: weight:mimetype:*.ext + // Each line should be of format: weight:mimetype:*.ext[:morefields...] fields := strings.Split(scanner.Text(), ":") - if len(fields) < 3 || len(fields[0]) < 1 || len(fields[2]) < 2 { + if len(fields) < 3 || len(fields[0]) < 1 || len(fields[2]) < 3 { continue - } else if fields[0][0] == '#' || fields[2][0] != '*' { + } else if fields[0][0] == '#' || fields[2][0] != '*' || fields[2][1] != '.' { continue } diff --git a/src/mime/type_unix_test.go b/src/mime/type_unix_test.go index 6bb408566c..ab14ae6f80 100644 --- a/src/mime/type_unix_test.go +++ b/src/mime/type_unix_test.go @@ -27,6 +27,8 @@ func TestTypeByExtensionUNIX(t *testing.T) { ".t3": "document/test", ".t4": "example/test", ".png": "image/png", + ",v": "", + "~": "", } for ext, want := range typeTests { diff --git a/src/net/cgo_unix.go b/src/net/cgo_unix.go index b156b198ee..71d90560ac 100644 --- a/src/net/cgo_unix.go +++ b/src/net/cgo_unix.go @@ -250,12 +250,12 @@ func cgoLookupCNAME(ctx context.Context, name string) (cname string, err error, // These are roughly enough for the following: // -// Source Encoding Maximum length of single name entry -// Unicast DNS ASCII or <=253 + a NUL terminator -// Unicode in RFC 5892 252 * total number of labels + delimiters + a NUL terminator -// Multicast DNS UTF-8 in RFC 5198 or <=253 + a NUL terminator -// the same as unicast DNS ASCII <=253 + a NUL terminator -// Local database various depends on implementation +// Source Encoding Maximum length of single name entry +// Unicast DNS ASCII or <=253 + a NUL terminator +// Unicode in RFC 5892 252 * total number of labels + delimiters + a NUL terminator +// Multicast DNS UTF-8 in RFC 5198 or <=253 + a NUL terminator +// the same as unicast DNS ASCII <=253 + a NUL terminator +// Local database various depends on implementation const ( nameinfoLen = 64 maxNameinfoLen = 4096 diff --git a/src/net/conf.go b/src/net/conf.go index 716a37ff80..9d4752173e 100644 --- a/src/net/conf.go +++ b/src/net/conf.go @@ -278,13 +278,15 @@ func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrde // goDebugNetDNS parses the value of the GODEBUG "netdns" value. // The netdns value can be of the form: -// 1 // debug level 1 -// 2 // debug level 2 -// cgo // use cgo for DNS lookups -// go // use go for DNS lookups -// cgo+1 // use cgo for DNS lookups + debug level 1 -// 1+cgo // same -// cgo+2 // same, but debug level 2 +// +// 1 // debug level 1 +// 2 // debug level 2 +// cgo // use cgo for DNS lookups +// go // use go for DNS lookups +// cgo+1 // use cgo for DNS lookups + debug level 1 +// 1+cgo // same +// cgo+2 // same, but debug level 2 +// // etc. func goDebugNetDNS() (dnsMode string, debugLevel int) { goDebug := godebug.Get("netdns") diff --git a/src/net/dial.go b/src/net/dial.go index 9159e6b384..b24bd2f5f4 100644 --- a/src/net/dial.go +++ b/src/net/dial.go @@ -114,6 +114,7 @@ func minNonzeroTime(a, b time.Time) time.Time { // - now+Timeout // - d.Deadline // - the context's deadline +// // Or zero, if none of Timeout, Deadline, or context's deadline is set. func (d *Dialer) deadline(ctx context.Context, now time.Time) (earliest time.Time) { if d.Timeout != 0 { // including negative, for historical reasons @@ -289,6 +290,7 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string // Dial will try each IP address in order until one succeeds. // // Examples: +// // Dial("tcp", "golang.org:http") // Dial("tcp", "192.0.2.1:http") // Dial("tcp", "198.51.100.1:80") @@ -304,6 +306,7 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string // behaves with a non-well known protocol number such as "0" or "255". // // Examples: +// // Dial("ip4:1", "192.0.2.1") // Dial("ip6:ipv6-icmp", "2001:db8::1") // Dial("ip6:58", "fe80::1%lo0") diff --git a/src/net/fcntl_libc_test.go b/src/net/fcntl_libc_test.go index 3478ce7231..78892e3a9f 100644 --- a/src/net/fcntl_libc_test.go +++ b/src/net/fcntl_libc_test.go @@ -9,5 +9,6 @@ package net import _ "unsafe" // for go:linkname // Implemented in the syscall package. +// //go:linkname fcntl syscall.fcntl func fcntl(fd int, cmd int, arg int) (int, error) diff --git a/src/net/http/cgi/host.go b/src/net/http/cgi/host.go index 95b2e13e4e..0d43e140d5 100644 --- a/src/net/http/cgi/host.go +++ b/src/net/http/cgi/host.go @@ -90,10 +90,11 @@ func (h *Handler) stderr() io.Writer { // removeLeadingDuplicates remove leading duplicate in environments. // It's possible to override environment like following. -// cgi.Handler{ -// ... -// Env: []string{"SCRIPT_FILENAME=foo.php"}, -// } +// +// cgi.Handler{ +// ... +// Env: []string{"SCRIPT_FILENAME=foo.php"}, +// } func removeLeadingDuplicates(env []string) (ret []string) { for i, e := range env { found := false diff --git a/src/net/http/client.go b/src/net/http/client.go index bc0ed1fc50..490349f7bd 100644 --- a/src/net/http/client.go +++ b/src/net/http/client.go @@ -423,11 +423,11 @@ func basicAuth(username, password string) string { // the following redirect codes, Get follows the redirect, up to a // maximum of 10 redirects: // -// 301 (Moved Permanently) -// 302 (Found) -// 303 (See Other) -// 307 (Temporary Redirect) -// 308 (Permanent Redirect) +// 301 (Moved Permanently) +// 302 (Found) +// 303 (See Other) +// 307 (Temporary Redirect) +// 308 (Permanent Redirect) // // An error is returned if there were too many redirects or if there // was an HTTP protocol error. A non-2xx response doesn't cause an @@ -452,11 +452,11 @@ func Get(url string) (resp *Response, err error) { // following redirect codes, Get follows the redirect after calling the // Client's CheckRedirect function: // -// 301 (Moved Permanently) -// 302 (Found) -// 303 (See Other) -// 307 (Temporary Redirect) -// 308 (Permanent Redirect) +// 301 (Moved Permanently) +// 302 (Found) +// 303 (See Other) +// 307 (Temporary Redirect) +// 308 (Permanent Redirect) // // An error is returned if the Client's CheckRedirect function fails // or if there was an HTTP protocol error. A non-2xx response doesn't @@ -890,13 +890,13 @@ func (c *Client) PostForm(url string, data url.Values) (resp *Response, err erro // the following redirect codes, Head follows the redirect, up to a // maximum of 10 redirects: // -// 301 (Moved Permanently) -// 302 (Found) -// 303 (See Other) -// 307 (Temporary Redirect) -// 308 (Permanent Redirect) +// 301 (Moved Permanently) +// 302 (Found) +// 303 (See Other) +// 307 (Temporary Redirect) +// 308 (Permanent Redirect) // -// Head is a wrapper around DefaultClient.Head +// # Head is a wrapper around DefaultClient.Head // // To make a request with a specified context.Context, use NewRequestWithContext // and DefaultClient.Do. @@ -908,11 +908,11 @@ func Head(url string) (resp *Response, err error) { // following redirect codes, Head follows the redirect after calling the // Client's CheckRedirect function: // -// 301 (Moved Permanently) -// 302 (Found) -// 303 (See Other) -// 307 (Temporary Redirect) -// 308 (Permanent Redirect) +// 301 (Moved Permanently) +// 302 (Found) +// 303 (See Other) +// 307 (Temporary Redirect) +// 308 (Permanent Redirect) // // To make a request with a specified context.Context, use NewRequestWithContext // and Client.Do. @@ -941,8 +941,8 @@ func (c *Client) CloseIdleConnections() { } // cancelTimerBody is an io.ReadCloser that wraps rc with two features: -// 1) On Read error or close, the stop func is called. -// 2) On Read failure, if reqDidTimeout is true, the error is wrapped and +// 1. On Read error or close, the stop func is called. +// 2. On Read failure, if reqDidTimeout is true, the error is wrapped and // marked as net.Error that hit its timeout. type cancelTimerBody struct { stop func() // stops the time.Timer waiting to cancel the request diff --git a/src/net/http/cookie.go b/src/net/http/cookie.go index 6e1035330b..9cb0804f8f 100644 --- a/src/net/http/cookie.go +++ b/src/net/http/cookie.go @@ -387,11 +387,13 @@ func sanitizeCookieName(n string) string { // sanitizeCookieValue produces a suitable cookie-value from v. // https://tools.ietf.org/html/rfc6265#section-4.1.1 -// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) -// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E -// ; US-ASCII characters excluding CTLs, -// ; whitespace DQUOTE, comma, semicolon, -// ; and backslash +// +// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) +// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E +// ; US-ASCII characters excluding CTLs, +// ; whitespace DQUOTE, comma, semicolon, +// ; and backslash +// // We loosen this as spaces and commas are common in cookie values // but we produce a quoted cookie-value if and only if v contains // commas or spaces. diff --git a/src/net/http/cookiejar/jar.go b/src/net/http/cookiejar/jar.go index e6583da7fe..309dfcc0e1 100644 --- a/src/net/http/cookiejar/jar.go +++ b/src/net/http/cookiejar/jar.go @@ -19,9 +19,9 @@ import ( ) // PublicSuffixList provides the public suffix of a domain. For example: -// - the public suffix of "example.com" is "com", -// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and -// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us". +// - the public suffix of "example.com" is "com", +// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and +// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us". // // Implementations of PublicSuffixList must be safe for concurrent use by // multiple goroutines. diff --git a/src/net/http/cookiejar/jar_test.go b/src/net/http/cookiejar/jar_test.go index 47fb1abdaa..b7267b1718 100644 --- a/src/net/http/cookiejar/jar_test.go +++ b/src/net/http/cookiejar/jar_test.go @@ -20,8 +20,9 @@ var tNow = time.Date(2013, 1, 1, 12, 0, 0, 0, time.UTC) // testPSL implements PublicSuffixList with just two rules: "co.uk" // and the default rule "*". // The implementation has two intentional bugs: -// PublicSuffix("www.buggy.psl") == "xy" -// PublicSuffix("www2.buggy.psl") == "com" +// +// PublicSuffix("www.buggy.psl") == "xy" +// PublicSuffix("www2.buggy.psl") == "com" type testPSL struct{} func (testPSL) String() string { @@ -358,13 +359,13 @@ func mustParseURL(s string) *url.URL { } // jarTest encapsulates the following actions on a jar: -// 1. Perform SetCookies with fromURL and the cookies from setCookies. -// (Done at time tNow + 0 ms.) -// 2. Check that the entries in the jar matches content. -// (Done at time tNow + 1001 ms.) -// 3. For each query in tests: Check that Cookies with toURL yields the -// cookies in want. -// (Query n done at tNow + (n+2)*1001 ms.) +// 1. Perform SetCookies with fromURL and the cookies from setCookies. +// (Done at time tNow + 0 ms.) +// 2. Check that the entries in the jar matches content. +// (Done at time tNow + 1001 ms.) +// 3. For each query in tests: Check that Cookies with toURL yields the +// cookies in want. +// (Query n done at tNow + (n+2)*1001 ms.) type jarTest struct { description string // The description of what this test is supposed to test fromURL string // The full URL of the request from which Set-Cookie headers where received diff --git a/src/net/http/doc.go b/src/net/http/doc.go index ae9b708c69..67c4246c60 100644 --- a/src/net/http/doc.go +++ b/src/net/http/doc.go @@ -102,6 +102,5 @@ directly and use its ConfigureTransport and/or ConfigureServer functions. Manually configuring HTTP/2 via the golang.org/x/net/http2 package takes precedence over the net/http package's built-in HTTP/2 support. - */ package http diff --git a/src/net/http/fcgi/fcgi_test.go b/src/net/http/fcgi/fcgi_test.go index 5888783620..7a344ff31d 100644 --- a/src/net/http/fcgi/fcgi_test.go +++ b/src/net/http/fcgi/fcgi_test.go @@ -401,16 +401,16 @@ func TestResponseWriterSniffsContentType(t *testing.T) { } } -type signallingNopCloser struct { +type signalingNopCloser struct { io.Reader closed chan bool } -func (*signallingNopCloser) Write(buf []byte) (int, error) { +func (*signalingNopCloser) Write(buf []byte) (int, error) { return len(buf), nil } -func (rc *signallingNopCloser) Close() error { +func (rc *signalingNopCloser) Close() error { close(rc.closed) return nil } @@ -429,7 +429,7 @@ func TestSlowRequest(t *testing.T) { } }(pw) - rc := &signallingNopCloser{pr, make(chan bool)} + rc := &signalingNopCloser{pr, make(chan bool)} handlerDone := make(chan bool) c := newChild(rc, http.HandlerFunc(func( diff --git a/src/net/http/filetransport.go b/src/net/http/filetransport.go index 32126d7ec0..94684b07a1 100644 --- a/src/net/http/filetransport.go +++ b/src/net/http/filetransport.go @@ -22,11 +22,11 @@ type fileTransport struct { // The typical use case for NewFileTransport is to register the "file" // protocol with a Transport, as in: // -// t := &http.Transport{} -// t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/"))) -// c := &http.Client{Transport: t} -// res, err := c.Get("file:///etc/passwd") -// ... +// t := &http.Transport{} +// t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/"))) +// c := &http.Client{Transport: t} +// res, err := c.Get("file:///etc/passwd") +// ... func NewFileTransport(fs FileSystem) RoundTripper { return fileTransport{fileHandler{fs}} } diff --git a/src/net/http/fs.go b/src/net/http/fs.go index d8f924296b..7a1d5f4be5 100644 --- a/src/net/http/fs.go +++ b/src/net/http/fs.go @@ -831,7 +831,7 @@ func FS(fsys fs.FS) FileSystem { // To use the operating system's file system implementation, // use http.Dir: // -// http.Handle("/", http.FileServer(http.Dir("/tmp"))) +// http.Handle("/", http.FileServer(http.Dir("/tmp"))) // // To use an fs.FS implementation, use http.FS to convert it: // diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go index e955135c50..4a76e6afe8 100644 --- a/src/net/http/h2_bundle.go +++ b/src/net/http/h2_bundle.go @@ -3388,10 +3388,11 @@ func (s http2SettingID) String() string { // name (key). See httpguts.ValidHeaderName for the base rules. // // Further, http2 says: -// "Just as in HTTP/1.x, header field names are strings of ASCII -// characters that are compared in a case-insensitive -// fashion. However, header field names MUST be converted to -// lowercase prior to their encoding in HTTP/2. " +// +// "Just as in HTTP/1.x, header field names are strings of ASCII +// characters that are compared in a case-insensitive +// fashion. However, header field names MUST be converted to +// lowercase prior to their encoding in HTTP/2. " func http2validWireHeaderFieldName(v string) bool { if len(v) == 0 { return false @@ -3582,8 +3583,8 @@ func (s *http2sorter) SortStrings(ss []string) { // validPseudoPath reports whether v is a valid :path pseudo-header // value. It must be either: // -// *) a non-empty string starting with '/' -// *) the string '*', for OPTIONS requests. +// *) a non-empty string starting with '/' +// *) the string '*', for OPTIONS requests. // // For now this is only used a quick check for deciding when to clean // up Opaque URLs before sending requests from the Transport. @@ -6269,8 +6270,9 @@ func (rws *http2responseWriterState) writeChunk(p []byte) (n int, err error) { // prior to the headers being written. If the set of trailers is fixed // or known before the header is written, the normal Go trailers mechanism // is preferred: -// https://golang.org/pkg/net/http/#ResponseWriter -// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers +// +// https://golang.org/pkg/net/http/#ResponseWriter +// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers const http2TrailerPrefix = "Trailer:" // promoteUndeclaredTrailers permits http.Handlers to set trailers diff --git a/src/net/http/httptest/recorder.go b/src/net/http/httptest/recorder.go index 1b712ef2b0..1c1d880155 100644 --- a/src/net/http/httptest/recorder.go +++ b/src/net/http/httptest/recorder.go @@ -207,18 +207,20 @@ func (rw *ResponseRecorder) Result() *http.Response { if trailers, ok := rw.snapHeader["Trailer"]; ok { res.Trailer = make(http.Header, len(trailers)) for _, k := range trailers { - k = http.CanonicalHeaderKey(k) - if !httpguts.ValidTrailerHeader(k) { - // Ignore since forbidden by RFC 7230, section 4.1.2. - continue + for _, k := range strings.Split(k, ",") { + k = http.CanonicalHeaderKey(textproto.TrimString(k)) + if !httpguts.ValidTrailerHeader(k) { + // Ignore since forbidden by RFC 7230, section 4.1.2. + continue + } + vv, ok := rw.HeaderMap[k] + if !ok { + continue + } + vv2 := make([]string, len(vv)) + copy(vv2, vv) + res.Trailer[k] = vv2 } - vv, ok := rw.HeaderMap[k] - if !ok { - continue - } - vv2 := make([]string, len(vv)) - copy(vv2, vv) - res.Trailer[k] = vv2 } } for k, vv := range rw.HeaderMap { diff --git a/src/net/http/httptest/recorder_test.go b/src/net/http/httptest/recorder_test.go index 8cb32dd740..4782eced43 100644 --- a/src/net/http/httptest/recorder_test.go +++ b/src/net/http/httptest/recorder_test.go @@ -220,8 +220,7 @@ func TestRecorder(t *testing.T) { "Trailer headers are correctly recorded", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Non-Trailer", "correct") - w.Header().Set("Trailer", "Trailer-A") - w.Header().Add("Trailer", "Trailer-B") + w.Header().Set("Trailer", "Trailer-A, Trailer-B") w.Header().Add("Trailer", "Trailer-C") io.WriteString(w, "") w.Header().Set("Non-Trailer", "incorrect") diff --git a/src/net/http/httptest/server.go b/src/net/http/httptest/server.go index 1c0c0f6987..f254a494d1 100644 --- a/src/net/http/httptest/server.go +++ b/src/net/http/httptest/server.go @@ -76,7 +76,9 @@ func newLocalListener() net.Listener { // When debugging a particular http server-based test, // this flag lets you run +// // go test -run=BrokenTest -httptest.serve=127.0.0.1:8000 +// // to start the broken server so you can interact with it manually. // We only register this flag if it looks like the caller knows about it // and is trying to use it as we don't want to pollute flags and this diff --git a/src/net/http/internal/chunked.go b/src/net/http/internal/chunked.go index 37a72e9031..5a174415dc 100644 --- a/src/net/http/internal/chunked.go +++ b/src/net/http/internal/chunked.go @@ -163,10 +163,11 @@ var semi = []byte(";") // removeChunkExtension removes any chunk-extension from p. // For example, -// "0" => "0" -// "0;token" => "0" -// "0;token=val" => "0" -// `0;token="quoted string"` => "0" +// +// "0" => "0" +// "0;token" => "0" +// "0;token=val" => "0" +// `0;token="quoted string"` => "0" func removeChunkExtension(p []byte) ([]byte, error) { p, _, _ = bytes.Cut(p, semi) // TODO: care about exact syntax of chunk extensions? We're diff --git a/src/net/http/pprof/pprof.go b/src/net/http/pprof/pprof.go index dc855c8a6d..de5a4b9752 100644 --- a/src/net/http/pprof/pprof.go +++ b/src/net/http/pprof/pprof.go @@ -10,15 +10,16 @@ // The handled paths all begin with /debug/pprof/. // // To use pprof, link this package into your program: +// // import _ "net/http/pprof" // // If your application is not already running an http server, you // need to start one. Add "net/http" and "log" to your imports and // the following code to your main function: // -// go func() { -// log.Println(http.ListenAndServe("localhost:6060", nil)) -// }() +// go func() { +// log.Println(http.ListenAndServe("localhost:6060", nil)) +// }() // // If you are not using DefaultServeMux, you will have to register handlers // with the mux you are using. @@ -53,7 +54,6 @@ // For a study of the facility in action, visit // // https://blog.golang.org/2011/06/profiling-go-programs.html -// package pprof import ( diff --git a/src/net/http/request.go b/src/net/http/request.go index dbe947aec4..312211977d 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -359,7 +359,6 @@ func (r *Request) WithContext(ctx context.Context) *Request { r2 := new(Request) *r2 = *r r2.ctx = ctx - r2.URL = cloneURL(r.URL) // legacy behavior; TODO: try to remove. Issue 23544 return r2 } @@ -516,6 +515,7 @@ const defaultUserAgent = "Go-http-client/1.1" // Write writes an HTTP/1.1 request, which is the header and body, in wire format. // This method consults the following fields of the request: +// // Host // URL // Method (defaults to "GET") @@ -739,9 +739,11 @@ func idnaASCII(v string) (string, error) { // into Punycode form, if necessary. // // Ideally we'd clean the Host header according to the spec: -// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") -// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) -// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) +// +// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") +// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) +// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) +// // But practically, what we are trying to avoid is the situation in // issue 11206, where a malformed Host header used in the proxy context // would create a bad request. So it is enough to just truncate at the diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go index 4363e11033..d285840c1c 100644 --- a/src/net/http/request_test.go +++ b/src/net/http/request_test.go @@ -998,23 +998,15 @@ func TestMaxBytesReaderDifferentLimits(t *testing.T) { } } -func TestWithContextDeepCopiesURL(t *testing.T) { +func TestWithContextNilURL(t *testing.T) { req, err := NewRequest("POST", "https://golang.org/", nil) if err != nil { t.Fatal(err) } - reqCopy := req.WithContext(context.Background()) - reqCopy.URL.Scheme = "http" - - firstURL, secondURL := req.URL.String(), reqCopy.URL.String() - if firstURL == secondURL { - t.Errorf("unexpected change to original request's URL") - } - - // And also check we don't crash on nil (Issue 20601) + // Issue 20601 req.URL = nil - reqCopy = req.WithContext(context.Background()) + reqCopy := req.WithContext(context.Background()) if reqCopy.URL != nil { t.Error("expected nil URL in cloned request") } diff --git a/src/net/http/response.go b/src/net/http/response.go index 297394eabe..755c696557 100644 --- a/src/net/http/response.go +++ b/src/net/http/response.go @@ -205,8 +205,11 @@ func ReadResponse(r *bufio.Reader, req *Request) (*Response, error) { } // RFC 7234, section 5.4: Should treat +// // Pragma: no-cache +// // like +// // Cache-Control: no-cache func fixPragmaCacheControl(header Header) { if hp, ok := header["Pragma"]; ok && len(hp) > 0 && hp[0] == "no-cache" { @@ -228,24 +231,23 @@ func (r *Response) ProtoAtLeast(major, minor int) bool { // // This method consults the following fields of the response r: // -// StatusCode -// ProtoMajor -// ProtoMinor -// Request.Method -// TransferEncoding -// Trailer -// Body -// ContentLength -// Header, values for non-canonical keys will have unpredictable behavior +// StatusCode +// ProtoMajor +// ProtoMinor +// Request.Method +// TransferEncoding +// Trailer +// Body +// ContentLength +// Header, values for non-canonical keys will have unpredictable behavior // // The Response Body is closed after it is sent. func (r *Response) Write(w io.Writer) error { // Status line text := r.Status if text == "" { - var ok bool - text, ok = statusText[r.StatusCode] - if !ok { + text = StatusText(r.StatusCode) + if text == "" { text = "status code " + strconv.Itoa(r.StatusCode) } } else { diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index fb18cb2c6f..1c85a66599 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -4877,11 +4877,7 @@ func TestServerRequestContextCancel_ConnClose(t *testing.T) { handlerDone := make(chan struct{}) ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { close(inHandler) - select { - case <-r.Context().Done(): - case <-time.After(3 * time.Second): - t.Errorf("timeout waiting for context to be done") - } + <-r.Context().Done() close(handlerDone) })) defer ts.Close() @@ -4891,18 +4887,9 @@ func TestServerRequestContextCancel_ConnClose(t *testing.T) { } defer c.Close() io.WriteString(c, "GET / HTTP/1.1\r\nHost: foo\r\n\r\n") - select { - case <-inHandler: - case <-time.After(3 * time.Second): - t.Fatalf("timeout waiting to see ServeHTTP get called") - } + <-inHandler c.Close() // this should trigger the context being done - - select { - case <-handlerDone: - case <-time.After(4 * time.Second): - t.Fatalf("timeout waiting to see ServeHTTP exit") - } + <-handlerDone } func TestServerContext_ServerContextKey_h1(t *testing.T) { @@ -5077,10 +5064,11 @@ func benchmarkClientServerParallel(b *testing.B, parallelism int, useTLS bool) { // The client code runs in a subprocess. // // For use like: -// $ go test -c -// $ ./http.test -test.run=XX -test.bench=BenchmarkServer -test.benchtime=15s -test.cpuprofile=http.prof -// $ go tool pprof http.test http.prof -// (pprof) web +// +// $ go test -c +// $ ./http.test -test.run=XX -test.bench=BenchmarkServer -test.benchtime=15s -test.cpuprofile=http.prof +// $ go tool pprof http.test http.prof +// (pprof) web func BenchmarkServer(b *testing.B) { b.ReportAllocs() // Child process mode; diff --git a/src/net/http/server.go b/src/net/http/server.go index b91069f9a1..d44b0fb256 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -494,8 +494,9 @@ type response struct { // prior to the headers being written. If the set of trailers is fixed // or known before the header is written, the normal Go trailers mechanism // is preferred: -// https://pkg.go.dev/net/http#ResponseWriter -// https://pkg.go.dev/net/http#example-ResponseWriter-Trailers +// +// https://pkg.go.dev/net/http#ResponseWriter +// https://pkg.go.dev/net/http#example-ResponseWriter-Trailers const TrailerPrefix = "Trailer:" // finalTrailers is called after the Handler exits and returns a non-nil @@ -1515,7 +1516,7 @@ func writeStatusLine(bw *bufio.Writer, is11 bool, code int, scratch []byte) { } else { bw.WriteString("HTTP/1.0 ") } - if text, ok := statusText[code]; ok { + if text := StatusText(code); text != "" { bw.Write(strconv.AppendInt(scratch[:0], int64(code), 10)) bw.WriteByte(' ') bw.WriteString(text) @@ -1721,7 +1722,7 @@ type closeWriter interface { var _ closeWriter = (*net.TCPConn)(nil) // closeWrite flushes any outstanding data and sends a FIN packet (if -// client is connected via TCP), signalling that we're done. We then +// client is connected via TCP), signaling that we're done. We then // pause for a bit, hoping the client processes it before any // subsequent RST. // @@ -2101,7 +2102,7 @@ func Error(w ResponseWriter, error string, code int) { func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) } // NotFoundHandler returns a simple request handler -// that replies to each request with a ``404 page not found'' reply. +// that replies to each request with a “404 page not found” reply. func NotFoundHandler() Handler { return HandlerFunc(NotFound) } // StripPrefix returns a handler that serves HTTP requests by removing the @@ -2191,7 +2192,7 @@ func Redirect(w ResponseWriter, r *Request, url string, code int) { // Shouldn't send the body for POST or HEAD; that leaves GET. if !hadCT && r.Method == "GET" { - body := "" + statusText[code] + ".\n" + body := "" + StatusText(code) + ".\n" fmt.Fprintln(w, body) } } @@ -2394,7 +2395,7 @@ func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool { // the pattern that will match after following the redirect. // // If there is no registered handler that applies to the request, -// Handler returns a ``page not found'' handler and an empty pattern. +// Handler returns a “page not found” handler and an empty pattern. func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) { // CONNECT requests are not canonicalized. diff --git a/src/net/http/status.go b/src/net/http/status.go index 286315f639..75fea0ca35 100644 --- a/src/net/http/status.go +++ b/src/net/http/status.go @@ -76,77 +76,135 @@ const ( StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6 ) -var statusText = map[int]string{ - StatusContinue: "Continue", - StatusSwitchingProtocols: "Switching Protocols", - StatusProcessing: "Processing", - StatusEarlyHints: "Early Hints", - - StatusOK: "OK", - StatusCreated: "Created", - StatusAccepted: "Accepted", - StatusNonAuthoritativeInfo: "Non-Authoritative Information", - StatusNoContent: "No Content", - StatusResetContent: "Reset Content", - StatusPartialContent: "Partial Content", - StatusMultiStatus: "Multi-Status", - StatusAlreadyReported: "Already Reported", - StatusIMUsed: "IM Used", - - StatusMultipleChoices: "Multiple Choices", - StatusMovedPermanently: "Moved Permanently", - StatusFound: "Found", - StatusSeeOther: "See Other", - StatusNotModified: "Not Modified", - StatusUseProxy: "Use Proxy", - StatusTemporaryRedirect: "Temporary Redirect", - StatusPermanentRedirect: "Permanent Redirect", - - StatusBadRequest: "Bad Request", - StatusUnauthorized: "Unauthorized", - StatusPaymentRequired: "Payment Required", - StatusForbidden: "Forbidden", - StatusNotFound: "Not Found", - StatusMethodNotAllowed: "Method Not Allowed", - StatusNotAcceptable: "Not Acceptable", - StatusProxyAuthRequired: "Proxy Authentication Required", - StatusRequestTimeout: "Request Timeout", - StatusConflict: "Conflict", - StatusGone: "Gone", - StatusLengthRequired: "Length Required", - StatusPreconditionFailed: "Precondition Failed", - StatusRequestEntityTooLarge: "Request Entity Too Large", - StatusRequestURITooLong: "Request URI Too Long", - StatusUnsupportedMediaType: "Unsupported Media Type", - StatusRequestedRangeNotSatisfiable: "Requested Range Not Satisfiable", - StatusExpectationFailed: "Expectation Failed", - StatusTeapot: "I'm a teapot", - StatusMisdirectedRequest: "Misdirected Request", - StatusUnprocessableEntity: "Unprocessable Entity", - StatusLocked: "Locked", - StatusFailedDependency: "Failed Dependency", - StatusTooEarly: "Too Early", - StatusUpgradeRequired: "Upgrade Required", - StatusPreconditionRequired: "Precondition Required", - StatusTooManyRequests: "Too Many Requests", - StatusRequestHeaderFieldsTooLarge: "Request Header Fields Too Large", - StatusUnavailableForLegalReasons: "Unavailable For Legal Reasons", - - StatusInternalServerError: "Internal Server Error", - StatusNotImplemented: "Not Implemented", - StatusBadGateway: "Bad Gateway", - StatusServiceUnavailable: "Service Unavailable", - StatusGatewayTimeout: "Gateway Timeout", - StatusHTTPVersionNotSupported: "HTTP Version Not Supported", - StatusVariantAlsoNegotiates: "Variant Also Negotiates", - StatusInsufficientStorage: "Insufficient Storage", - StatusLoopDetected: "Loop Detected", - StatusNotExtended: "Not Extended", - StatusNetworkAuthenticationRequired: "Network Authentication Required", -} - // StatusText returns a text for the HTTP status code. It returns the empty // string if the code is unknown. func StatusText(code int) string { - return statusText[code] + switch code { + case StatusContinue: + return "Continue" + case StatusSwitchingProtocols: + return "Switching Protocols" + case StatusProcessing: + return "Processing" + case StatusEarlyHints: + return "Early Hints" + case StatusOK: + return "OK" + case StatusCreated: + return "Created" + case StatusAccepted: + return "Accepted" + case StatusNonAuthoritativeInfo: + return "Non-Authoritative Information" + case StatusNoContent: + return "No Content" + case StatusResetContent: + return "Reset Content" + case StatusPartialContent: + return "Partial Content" + case StatusMultiStatus: + return "Multi-Status" + case StatusAlreadyReported: + return "Already Reported" + case StatusIMUsed: + return "IM Used" + case StatusMultipleChoices: + return "Multiple Choices" + case StatusMovedPermanently: + return "Moved Permanently" + case StatusFound: + return "Found" + case StatusSeeOther: + return "See Other" + case StatusNotModified: + return "Not Modified" + case StatusUseProxy: + return "Use Proxy" + case StatusTemporaryRedirect: + return "Temporary Redirect" + case StatusPermanentRedirect: + return "Permanent Redirect" + case StatusBadRequest: + return "Bad Request" + case StatusUnauthorized: + return "Unauthorized" + case StatusPaymentRequired: + return "Payment Required" + case StatusForbidden: + return "Forbidden" + case StatusNotFound: + return "Not Found" + case StatusMethodNotAllowed: + return "Method Not Allowed" + case StatusNotAcceptable: + return "Not Acceptable" + case StatusProxyAuthRequired: + return "Proxy Authentication Required" + case StatusRequestTimeout: + return "Request Timeout" + case StatusConflict: + return "Conflict" + case StatusGone: + return "Gone" + case StatusLengthRequired: + return "Length Required" + case StatusPreconditionFailed: + return "Precondition Failed" + case StatusRequestEntityTooLarge: + return "Request Entity Too Large" + case StatusRequestURITooLong: + return "Request URI Too Long" + case StatusUnsupportedMediaType: + return "Unsupported Media Type" + case StatusRequestedRangeNotSatisfiable: + return "Requested Range Not Satisfiable" + case StatusExpectationFailed: + return "Expectation Failed" + case StatusTeapot: + return "I'm a teapot" + case StatusMisdirectedRequest: + return "Misdirected Request" + case StatusUnprocessableEntity: + return "Unprocessable Entity" + case StatusLocked: + return "Locked" + case StatusFailedDependency: + return "Failed Dependency" + case StatusTooEarly: + return "Too Early" + case StatusUpgradeRequired: + return "Upgrade Required" + case StatusPreconditionRequired: + return "Precondition Required" + case StatusTooManyRequests: + return "Too Many Requests" + case StatusRequestHeaderFieldsTooLarge: + return "Request Header Fields Too Large" + case StatusUnavailableForLegalReasons: + return "Unavailable For Legal Reasons" + case StatusInternalServerError: + return "Internal Server Error" + case StatusNotImplemented: + return "Not Implemented" + case StatusBadGateway: + return "Bad Gateway" + case StatusServiceUnavailable: + return "Service Unavailable" + case StatusGatewayTimeout: + return "Gateway Timeout" + case StatusHTTPVersionNotSupported: + return "HTTP Version Not Supported" + case StatusVariantAlsoNegotiates: + return "Variant Also Negotiates" + case StatusInsufficientStorage: + return "Insufficient Storage" + case StatusLoopDetected: + return "Loop Detected" + case StatusNotExtended: + return "Not Extended" + case StatusNetworkAuthenticationRequired: + return "Network Authentication Required" + default: + return "" + } } diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go index 6d51178ee9..d9edf8c725 100644 --- a/src/net/http/transfer.go +++ b/src/net/http/transfer.go @@ -196,10 +196,11 @@ func (t *transferWriter) shouldSendChunkedRequestBody() bool { // headers before the pipe is fed data), we need to be careful and bound how // long we wait for it. This delay will only affect users if all the following // are true: -// * the request body blocks -// * the content length is not set (or set to -1) -// * the method doesn't usually have a body (GET, HEAD, DELETE, ...) -// * there is no transfer-encoding=chunked already set. +// - the request body blocks +// - the content length is not set (or set to -1) +// - the method doesn't usually have a body (GET, HEAD, DELETE, ...) +// - there is no transfer-encoding=chunked already set. +// // In other words, this delay will not normally affect anybody, and there // are workarounds if it does. func (t *transferWriter) probeRequestBody() { diff --git a/src/net/http/transfer_test.go b/src/net/http/transfer_test.go index f0c28b2629..5e0df896d8 100644 --- a/src/net/http/transfer_test.go +++ b/src/net/http/transfer_test.go @@ -267,7 +267,7 @@ func TestTransferWriterWriteBodyReaderTypes(t *testing.T) { } if tc.expectedReader != actualReader { - t.Fatalf("got reader %T want %T", actualReader, tc.expectedReader) + t.Fatalf("got reader %s want %s", actualReader, tc.expectedReader) } } diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 440d6b969b..6fcb458296 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -2099,17 +2099,21 @@ func TestTransportConcurrency(t *testing.T) { for req := range reqs { res, err := c.Get(ts.URL + "/?echo=" + req) if err != nil { - t.Errorf("error on req %s: %v", req, err) + if runtime.GOOS == "netbsd" && strings.HasSuffix(err.Error(), ": connection reset by peer") { + // https://go.dev/issue/52168: this test was observed to fail with + // ECONNRESET errors in Dial on various netbsd builders. + t.Logf("error on req %s: %v", req, err) + t.Logf("(see https://go.dev/issue/52168)") + } else { + t.Errorf("error on req %s: %v", req, err) + } wg.Done() continue } all, err := io.ReadAll(res.Body) if err != nil { t.Errorf("read error on req %s: %v", req, err) - wg.Done() - continue - } - if string(all) != req { + } else if string(all) != req { t.Errorf("body of req %s = %q; want %q", req, all, req) } res.Body.Close() @@ -3435,6 +3439,7 @@ func (c writerFuncConn) Write(p []byte) (n int, err error) { return c.write(p) } // - we reused a keep-alive connection // - we haven't yet received any header data // - either we wrote no bytes to the server, or the request is idempotent +// // This automatically prevents an infinite resend loop because we'll run out of // the cached keep-alive connections eventually. func TestRetryRequestsOnError(t *testing.T) { diff --git a/src/net/ipsock_posix.go b/src/net/ipsock_posix.go index 2c72447848..9a961b96ab 100644 --- a/src/net/ipsock_posix.go +++ b/src/net/ipsock_posix.go @@ -78,29 +78,29 @@ func (p *ipStackCapabilities) probe() { // address family, both AF_INET and AF_INET6, and a wildcard address // like the following: // -// - A listen for a wildcard communication domain, "tcp" or -// "udp", with a wildcard address: If the platform supports -// both IPv6 and IPv4-mapped IPv6 communication capabilities, -// or does not support IPv4, we use a dual stack, AF_INET6 and -// IPV6_V6ONLY=0, wildcard address listen. The dual stack -// wildcard address listen may fall back to an IPv6-only, -// AF_INET6 and IPV6_V6ONLY=1, wildcard address listen. -// Otherwise we prefer an IPv4-only, AF_INET, wildcard address -// listen. +// - A listen for a wildcard communication domain, "tcp" or +// "udp", with a wildcard address: If the platform supports +// both IPv6 and IPv4-mapped IPv6 communication capabilities, +// or does not support IPv4, we use a dual stack, AF_INET6 and +// IPV6_V6ONLY=0, wildcard address listen. The dual stack +// wildcard address listen may fall back to an IPv6-only, +// AF_INET6 and IPV6_V6ONLY=1, wildcard address listen. +// Otherwise we prefer an IPv4-only, AF_INET, wildcard address +// listen. // -// - A listen for a wildcard communication domain, "tcp" or -// "udp", with an IPv4 wildcard address: same as above. +// - A listen for a wildcard communication domain, "tcp" or +// "udp", with an IPv4 wildcard address: same as above. // -// - A listen for a wildcard communication domain, "tcp" or -// "udp", with an IPv6 wildcard address: same as above. +// - A listen for a wildcard communication domain, "tcp" or +// "udp", with an IPv6 wildcard address: same as above. // -// - A listen for an IPv4 communication domain, "tcp4" or "udp4", -// with an IPv4 wildcard address: We use an IPv4-only, AF_INET, -// wildcard address listen. +// - A listen for an IPv4 communication domain, "tcp4" or "udp4", +// with an IPv4 wildcard address: We use an IPv4-only, AF_INET, +// wildcard address listen. // -// - A listen for an IPv6 communication domain, "tcp6" or "udp6", -// with an IPv6 wildcard address: We use an IPv6-only, AF_INET6 -// and IPV6_V6ONLY=1, wildcard address listen. +// - A listen for an IPv6 communication domain, "tcp6" or "udp6", +// with an IPv6 wildcard address: We use an IPv6-only, AF_INET6 +// and IPV6_V6ONLY=1, wildcard address listen. // // Otherwise guess: If the addresses are IPv4 then returns AF_INET, // or else returns AF_INET6. It also returns a boolean value what diff --git a/src/net/mac.go b/src/net/mac.go index 373ac3d7e2..53d5b2dbf5 100644 --- a/src/net/mac.go +++ b/src/net/mac.go @@ -26,6 +26,7 @@ func (a HardwareAddr) String() string { // ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet // IP over InfiniBand link-layer address using one of the following formats: +// // 00:00:5e:00:53:01 // 02:00:5e:10:00:00:00:01 // 00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01 diff --git a/src/net/mail/message.go b/src/net/mail/message.go index 61a3a26b01..c91aa3af12 100644 --- a/src/net/mail/message.go +++ b/src/net/mail/message.go @@ -8,12 +8,12 @@ Package mail implements parsing of mail messages. For the most part, this package follows the syntax as specified by RFC 5322 and extended by RFC 6532. Notable divergences: - * Obsolete address formats are not parsed, including addresses with - embedded route information. - * The full range of spacing (the CFWS syntax element) is not supported, - such as breaking addresses across lines. - * No unicode normalization is performed. - * The special characters ()[]:;@\, are allowed to appear unquoted in names. + - Obsolete address formats are not parsed, including addresses with + embedded route information. + - The full range of spacing (the CFWS syntax element) is not supported, + such as breaking addresses across lines. + - No unicode normalization is performed. + - The special characters ()[]:;@\, are allowed to appear unquoted in names. */ package mail diff --git a/src/net/net.go b/src/net/net.go index ec718d5e43..7a97b9dcfd 100644 --- a/src/net/net.go +++ b/src/net/net.go @@ -36,7 +36,7 @@ The Listen function creates servers: go handleConnection(conn) } -Name Resolution +# Name Resolution The method for resolving domain names, whether indirectly with functions like Dial or directly with functions like LookupHost and LookupAddr, varies by operating system. @@ -74,7 +74,6 @@ join the two settings by a plus sign, as in GODEBUG=netdns=go+1. On Plan 9, the resolver always accesses /net/cs and /net/dns. On Windows, the resolver always uses C library functions, such as GetAddrInfo and DnsQuery. - */ package net diff --git a/src/net/netip/slow_test.go b/src/net/netip/slow_test.go index 5b46a39a83..d7c8025164 100644 --- a/src/net/netip/slow_test.go +++ b/src/net/netip/slow_test.go @@ -21,22 +21,22 @@ var zeros = []string{"0", "0", "0", "0", "0", "0", "0", "0"} // and against which we measure optimized parsers. // // parseIPSlow understands the following forms of IP addresses: -// - Regular IPv4: 1.2.3.4 -// - IPv4 with many leading zeros: 0000001.0000002.0000003.0000004 -// - Regular IPv6: 1111:2222:3333:4444:5555:6666:7777:8888 -// - IPv6 with many leading zeros: 00000001:0000002:0000003:0000004:0000005:0000006:0000007:0000008 -// - IPv6 with zero blocks elided: 1111:2222::7777:8888 -// - IPv6 with trailing 32 bits expressed as IPv4: 1111:2222:3333:4444:5555:6666:77.77.88.88 +// - Regular IPv4: 1.2.3.4 +// - IPv4 with many leading zeros: 0000001.0000002.0000003.0000004 +// - Regular IPv6: 1111:2222:3333:4444:5555:6666:7777:8888 +// - IPv6 with many leading zeros: 00000001:0000002:0000003:0000004:0000005:0000006:0000007:0000008 +// - IPv6 with zero blocks elided: 1111:2222::7777:8888 +// - IPv6 with trailing 32 bits expressed as IPv4: 1111:2222:3333:4444:5555:6666:77.77.88.88 // // It does not process the following IP address forms, which have been // varyingly accepted by some programs due to an under-specification // of the shapes of IPv4 addresses: // -// - IPv4 as a single 32-bit uint: 4660 (same as "1.2.3.4") -// - IPv4 with octal numbers: 0300.0250.0.01 (same as "192.168.0.1") -// - IPv4 with hex numbers: 0xc0.0xa8.0x0.0x1 (same as "192.168.0.1") -// - IPv4 in "class-B style": 1.2.52 (same as "1.2.3.4") -// - IPv4 in "class-A style": 1.564 (same as "1.2.3.4") +// - IPv4 as a single 32-bit uint: 4660 (same as "1.2.3.4") +// - IPv4 with octal numbers: 0300.0250.0.01 (same as "192.168.0.1") +// - IPv4 with hex numbers: 0xc0.0xa8.0x0.0x1 (same as "192.168.0.1") +// - IPv4 in "class-B style": 1.2.52 (same as "1.2.3.4") +// - IPv4 in "class-A style": 1.564 (same as "1.2.3.4") func parseIPSlow(s string) (Addr, error) { // Identify and strip out the zone, if any. There should be 0 or 1 // '%' in the string. @@ -94,13 +94,13 @@ func parseIPSlow(s string) (Addr, error) { // function does not verify the contents of each field. // // This function performs two transformations: -// - The last 32 bits of an IPv6 address may be represented in -// IPv4-style dotted quad form, as in 1:2:3:4:5:6:7.8.9.10. That -// address is transformed to its hex equivalent, -// e.g. 1:2:3:4:5:6:708:90a. -// - An address may contain one "::", which expands into as many -// 16-bit blocks of zeros as needed to make the address its correct -// full size. For example, fe80::1:2 expands to fe80:0:0:0:0:0:1:2. +// - The last 32 bits of an IPv6 address may be represented in +// IPv4-style dotted quad form, as in 1:2:3:4:5:6:7.8.9.10. That +// address is transformed to its hex equivalent, +// e.g. 1:2:3:4:5:6:708:90a. +// - An address may contain one "::", which expands into as many +// 16-bit blocks of zeros as needed to make the address its correct +// full size. For example, fe80::1:2 expands to fe80:0:0:0:0:0:1:2. // // Both short forms may be present in a single address, // e.g. fe80::1.2.3.4. diff --git a/src/net/rpc/server.go b/src/net/rpc/server.go index 0b3e6e3c58..109ebba541 100644 --- a/src/net/rpc/server.go +++ b/src/net/rpc/server.go @@ -13,11 +13,11 @@ objects of the same type. Only methods that satisfy these criteria will be made available for remote access; other methods will be ignored: - - the method's type is exported. - - the method is exported. - - the method has two arguments, both exported (or builtin) types. - - the method's second argument is a pointer. - - the method has return type error. + - the method's type is exported. + - the method is exported. + - the method has two arguments, both exported (or builtin) types. + - the method's second argument is a pointer. + - the method has return type error. In effect, the method must look schematically like @@ -213,10 +213,11 @@ func isExportedOrBuiltinType(t reflect.Type) bool { // Register publishes in the server the set of methods of the // receiver value that satisfy the following conditions: -// - exported method of exported type -// - two arguments, both of exported type -// - the second argument is a pointer -// - one return value, of type error +// - exported method of exported type +// - two arguments, both of exported type +// - the second argument is a pointer +// - one return value, of type error +// // It returns an error if the receiver is not an exported type or has // no suitable methods. It also logs the error using package log. // The client accesses each method using a string of the form "Type.Method", diff --git a/src/net/smtp/smtp.go b/src/net/smtp/smtp.go index c1f00a04e1..3bd2061b0c 100644 --- a/src/net/smtp/smtp.go +++ b/src/net/smtp/smtp.go @@ -4,15 +4,17 @@ // Package smtp implements the Simple Mail Transfer Protocol as defined in RFC 5321. // It also implements the following extensions: +// // 8BITMIME RFC 1652 // AUTH RFC 2554 // STARTTLS RFC 3207 +// // Additional extensions may be handled by clients. // // The smtp package is frozen and is not accepting new features. // Some external packages provide more functionality. See: // -// https://godoc.org/?q=smtp +// https://godoc.org/?q=smtp package smtp import ( diff --git a/src/net/sock_linux.go b/src/net/sock_linux.go index 9f62ed3dee..2513f9ba7b 100644 --- a/src/net/sock_linux.go +++ b/src/net/sock_linux.go @@ -43,8 +43,8 @@ func kernelVersion() (major int, minor int) { // Linux stores the backlog as: // -// - uint16 in kernel version < 4.1, -// - uint32 in kernel version >= 4.1 +// - uint16 in kernel version < 4.1, +// - uint32 in kernel version >= 4.1 // // Truncate number to avoid wrapping. // diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go index ac47f00700..1f7afc5766 100644 --- a/src/net/textproto/reader.go +++ b/src/net/textproto/reader.go @@ -28,7 +28,6 @@ type Reader struct { // should be reading from an io.LimitReader or similar Reader to bound // the size of responses. func NewReader(r *bufio.Reader) *Reader { - commonHeaderOnce.Do(initCommonHeader) return &Reader{R: r} } @@ -216,9 +215,12 @@ func parseCodeLine(line string, expectCode int) (code int, continued bool, messa } // ReadCodeLine reads a response code line of the form +// // code message +// // where code is a three-digit status code and the message // extends to the rest of the line. An example of such a line is: +// // 220 plan9.bell-labs.com ESMTP // // If the prefix of the status does not match the digits in expectCode, @@ -252,10 +254,10 @@ func (r *Reader) ReadCodeLine(expectCode int) (code int, message string, err err // See page 36 of RFC 959 (https://www.ietf.org/rfc/rfc959.txt) for // details of another form of response accepted: // -// code-message line 1 -// message line 2 -// ... -// code message line n +// code-message line 1 +// message line 2 +// ... +// code message line n // // If the prefix of the status does not match the digits in expectCode, // ReadResponse returns with err set to &Error{code, message}. @@ -579,8 +581,6 @@ func (r *Reader) upcomingHeaderNewlines() (n int) { // If s contains a space or invalid header field bytes, it is // returned without modifications. func CanonicalMIMEHeaderKey(s string) string { - commonHeaderOnce.Do(initCommonHeader) - // Quick check for canonical encoding. upper := true for i := 0; i < len(s); i++ { @@ -603,11 +603,12 @@ const toLower = 'a' - 'A' // validHeaderFieldByte reports whether b is a valid byte in a header // field name. RFC 7230 says: -// header-field = field-name ":" OWS field-value OWS -// field-name = token -// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / -// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA -// token = 1*tchar +// +// header-field = field-name ":" OWS field-value OWS +// field-name = token +// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / +// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +// token = 1*tchar func validHeaderFieldByte(b byte) bool { return int(b) < len(isTokenTable) && isTokenTable[b] } @@ -642,6 +643,7 @@ func canonicalMIMEHeaderKey(a []byte) string { a[i] = c upper = c == '-' // for next time } + commonHeaderOnce.Do(initCommonHeader) // The compiler recognizes m[string(byteSlice)] as a special // case, so a copy of a's bytes into a new string does not // happen in this map lookup: diff --git a/src/net/textproto/reader_test.go b/src/net/textproto/reader_test.go index 3124d438fa..d11d40f1cf 100644 --- a/src/net/textproto/reader_test.go +++ b/src/net/textproto/reader_test.go @@ -8,8 +8,10 @@ import ( "bufio" "bytes" "io" + "net" "reflect" "strings" + "sync" "testing" ) @@ -324,6 +326,33 @@ func TestCommonHeaders(t *testing.T) { } } +func TestIssue46363(t *testing.T) { + // Regression test for data race reported in issue 46363: + // ReadMIMEHeader reads commonHeader before commonHeader has been initialized. + // Run this test with the race detector enabled to catch the reported data race. + + // Reset commonHeaderOnce, so that commonHeader will have to be initialized + commonHeaderOnce = sync.Once{} + commonHeader = nil + + // Test for data race by calling ReadMIMEHeader and CanonicalMIMEHeaderKey concurrently + + // Send MIME header over net.Conn + r, w := net.Pipe() + go func() { + // ReadMIMEHeader calls canonicalMIMEHeaderKey, which reads from commonHeader + NewConn(r).ReadMIMEHeader() + }() + w.Write([]byte("A: 1\r\nB: 2\r\nC: 3\r\n\r\n")) + + // CanonicalMIMEHeaderKey calls commonHeaderOnce.Do(initCommonHeader) which initializes commonHeader + CanonicalMIMEHeaderKey("a") + + if commonHeader == nil { + t.Fatal("CanonicalMIMEHeaderKey should initialize commonHeader") + } +} + var clientHeaders = strings.Replace(`Host: golang.org Connection: keep-alive Cache-Control: max-age=0 diff --git a/src/net/textproto/textproto.go b/src/net/textproto/textproto.go index 3487a7dfaf..70038d5888 100644 --- a/src/net/textproto/textproto.go +++ b/src/net/textproto/textproto.go @@ -22,7 +22,6 @@ // // Conn, a convenient packaging of Reader, Writer, and Pipeline for use // with a single network connection. -// package textproto import ( diff --git a/src/net/url/url.go b/src/net/url/url.go index ecfd1d9e94..58b30411a4 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -381,9 +381,9 @@ func User(username string) *Userinfo { // // This functionality should only be used with legacy web sites. // RFC 2396 warns that interpreting Userinfo this way -// ``is NOT RECOMMENDED, because the passing of authentication +// “is NOT RECOMMENDED, because the passing of authentication // information in clear text (such as URI) has proven to be a -// security risk in almost every case where it has been used.'' +// security risk in almost every case where it has been used.” func UserPassword(username, password string) *Userinfo { return &Userinfo{username, password, true} } @@ -793,15 +793,15 @@ func validOptionalPort(port string) bool { // To obtain the path, String uses u.EscapedPath(). // // In the second form, the following rules apply: -// - if u.Scheme is empty, scheme: is omitted. -// - if u.User is nil, userinfo@ is omitted. -// - if u.Host is empty, host/ is omitted. -// - if u.Scheme and u.Host are empty and u.User is nil, -// the entire scheme://userinfo@host/ is omitted. -// - if u.Host is non-empty and u.Path begins with a /, -// the form host/path does not add its own /. -// - if u.RawQuery is empty, ?query is omitted. -// - if u.Fragment is empty, #fragment is omitted. +// - if u.Scheme is empty, scheme: is omitted. +// - if u.User is nil, userinfo@ is omitted. +// - if u.Host is empty, host/ is omitted. +// - if u.Scheme and u.Host are empty and u.User is nil, +// the entire scheme://userinfo@host/ is omitted. +// - if u.Host is non-empty and u.Path begins with a /, +// the form host/path does not add its own /. +// - if u.RawQuery is empty, ?query is omitted. +// - if u.Fragment is empty, #fragment is omitted. func (u *URL) String() string { var buf strings.Builder if u.Scheme != "" { @@ -960,7 +960,7 @@ func parseQuery(m Values, query string) (err error) { return err } -// Encode encodes the values into ``URL encoded'' form +// Encode encodes the values into “URL encoded” form // ("bar=baz&foo=quux") sorted by key. func (v Values) Encode() string { if v == nil { @@ -1189,21 +1189,29 @@ func (u *URL) UnmarshalBinary(text []byte) error { // JoinPath returns a new URL with the provided path elements joined to // any existing path and the resulting path cleaned of any ./ or ../ elements. +// Any sequences of multiple / characters will be reduced to a single /. func (u *URL) JoinPath(elem ...string) *URL { url := *u if len(elem) > 0 { elem = append([]string{u.Path}, elem...) - url.setPath(path.Join(elem...)) + p := path.Join(elem...) + // path.Join will remove any trailing slashes. + // Preserve at least one. + if strings.HasSuffix(elem[len(elem)-1], "/") && !strings.HasSuffix(p, "/") { + p += "/" + } + url.setPath(p) } return &url } // validUserinfo reports whether s is a valid userinfo string per RFC 3986 // Section 3.2.1: -// userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" -// / "*" / "+" / "," / ";" / "=" +// +// userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" +// / "*" / "+" / "," / ";" / "=" // // It doesn't validate pct-encoded. The caller does that via func unescape. func validUserinfo(s string) bool { diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 18aa5f8a1c..478cc34872 100644 --- a/src/net/url/url_test.go +++ b/src/net/url/url_test.go @@ -2099,6 +2099,31 @@ func TestJoinPath(t *testing.T) { base: "http://[fe80::1%en0]:8080/", elem: []string{"/go"}, }, + { + base: "https://go.googlesource.com", + elem: []string{"go/"}, + out: "https://go.googlesource.com/go/", + }, + { + base: "https://go.googlesource.com", + elem: []string{"go//"}, + out: "https://go.googlesource.com/go/", + }, + { + base: "https://go.googlesource.com", + elem: nil, + out: "https://go.googlesource.com", + }, + { + base: "https://go.googlesource.com/", + elem: nil, + out: "https://go.googlesource.com/", + }, + { + base: "/", + elem: nil, + out: "/", + }, } for _, tt := range tests { wantErr := "nil" diff --git a/src/os/file.go b/src/os/file.go index ea64a662cc..ab017d4af7 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -37,7 +37,6 @@ // Note: The maximum number of concurrent operations on a File may be limited by // the OS or the system. The number should be high, but exceeding it may degrade // performance or cause other issues. -// package os import ( diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go index 887e1c8892..93eb233e00 100644 --- a/src/os/file_plan9.go +++ b/src/os/file_plan9.go @@ -66,7 +66,7 @@ type dirInfo struct { func epipecheck(file *File, e error) { } -// DevNull is the name of the operating system's ``null device.'' +// DevNull is the name of the operating system's “null device.” // On Unix-like systems, it is "/dev/null"; on Windows, "NUL". const DevNull = "/dev/null" diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 666143b0de..c30a6890de 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -196,7 +196,7 @@ func epipecheck(file *File, e error) { } } -// DevNull is the name of the operating system's ``null device.'' +// DevNull is the name of the operating system's “null device.” // On Unix-like systems, it is "/dev/null"; on Windows, "NUL". const DevNull = "/dev/null" diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 75b4707eaf..db5c27dd30 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -95,7 +95,7 @@ type dirInfo struct { func epipecheck(file *File, e error) { } -// DevNull is the name of the operating system's ``null device.'' +// DevNull is the name of the operating system's “null device.” // On Unix-like systems, it is "/dev/null"; on Windows, "NUL". const DevNull = "NUL" @@ -402,9 +402,10 @@ func openSymlink(path string) (syscall.Handle, error) { // DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, ...) // into paths acceptable by all Windows APIs. // For example, it converts -// \??\C:\foo\bar into C:\foo\bar -// \??\UNC\foo\bar into \\foo\bar -// \??\Volume{abc}\ into C:\ +// +// \??\C:\foo\bar into C:\foo\bar +// \??\UNC\foo\bar into \\foo\bar +// \??\Volume{abc}\ into C:\ func normaliseLinkPath(path string) (string, error) { if len(path) < 4 || path[:4] != `\??\` { // unexpected path, return it as is diff --git a/src/os/pipe_test.go b/src/os/pipe_test.go index 20716bce1e..26565853e1 100644 --- a/src/os/pipe_test.go +++ b/src/os/pipe_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // Test broken pipes on Unix systems. +// //go:build !plan9 && !js package os_test diff --git a/src/os/rawconn_test.go b/src/os/rawconn_test.go index fd2038a233..62b99f8784 100644 --- a/src/os/rawconn_test.go +++ b/src/os/rawconn_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // Test use of raw connections. +// //go:build !plan9 && !js package os_test diff --git a/src/os/signal/doc.go b/src/os/signal/doc.go index 7af61d2d81..ab262edc58 100644 --- a/src/os/signal/doc.go +++ b/src/os/signal/doc.go @@ -8,7 +8,7 @@ Package signal implements access to incoming signals. Signals are primarily used on Unix-like systems. For the use of this package on Windows and Plan 9, see below. -Types of signals +# Types of signals The signals SIGKILL and SIGSTOP may not be caught by a program, and therefore cannot be affected by this package. @@ -33,7 +33,7 @@ by default is ^\ (Control-Backslash). In general you can cause a program to simply exit by pressing ^C, and you can cause it to exit with a stack dump by pressing ^\. -Default behavior of signals in Go programs +# Default behavior of signals in Go programs By default, a synchronous signal is converted into a run-time panic. A SIGHUP, SIGINT, or SIGTERM signal causes the program to exit. A @@ -55,7 +55,7 @@ and, on Linux, signals 32 (SIGCANCEL) and 33 (SIGSETXID) started by os.Exec, or by the os/exec package, will inherit the modified signal mask. -Changing the behavior of signals in Go programs +# Changing the behavior of signals in Go programs The functions in this package allow a program to change the way Go programs handle signals. @@ -88,7 +88,7 @@ for a blocked signal, it will be unblocked. If, later, Reset is called for that signal, or Stop is called on all channels passed to Notify for that signal, the signal will once again be blocked. -SIGPIPE +# SIGPIPE When a Go program writes to a broken pipe, the kernel will raise a SIGPIPE signal. @@ -109,7 +109,7 @@ This means that, by default, command line programs will behave like typical Unix command line programs, while other programs will not crash with SIGPIPE when writing to a closed network connection. -Go programs that use cgo or SWIG +# Go programs that use cgo or SWIG In a Go program that includes non-Go code, typically C/C++ code accessed using cgo or SWIG, Go's startup code normally runs first. It @@ -164,7 +164,7 @@ signal, and raises it again, to invoke any non-Go handler or default system handler. If the program does not exit, the Go handler then reinstalls itself and continues execution of the program. -Non-Go programs that call Go code +# Non-Go programs that call Go code When Go code is built with options like -buildmode=c-shared, it will be run as part of an existing non-Go program. The non-Go code may @@ -201,7 +201,7 @@ non-Go thread, it will act as described above, except that if there is an existing non-Go signal handler, that handler will be installed before raising the signal. -Windows +# Windows On Windows a ^C (Control-C) or ^BREAK (Control-Break) normally cause the program to exit. If Notify is called for os.Interrupt, ^C or ^BREAK @@ -217,11 +217,10 @@ CTRL_LOGOFF_EVENT or CTRL_SHUTDOWN_EVENT is received - the process will still get terminated unless it exits. But receiving syscall.SIGTERM will give the process an opportunity to clean up before termination. -Plan 9 +# Plan 9 On Plan 9, signals have type syscall.Note, which is a string. Calling Notify with a syscall.Note will cause that value to be sent on the channel when that string is posted as a note. - */ package signal diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go index 3182e83b4e..086ecdbcd5 100644 --- a/src/os/signal/signal_test.go +++ b/src/os/signal/signal_test.go @@ -713,7 +713,7 @@ func TestNotifyContextNotifications(t *testing.T) { } wg.Wait() <-ctx.Done() - fmt.Print("received SIGINT") + fmt.Println("received SIGINT") // Sleep to give time to simultaneous signals to reach the process. // These signals must be ignored given stop() is not called on this code. // We want to guarantee a SIGINT doesn't cause a premature termination of the program. @@ -730,11 +730,17 @@ func TestNotifyContextNotifications(t *testing.T) { {"multiple", 10}, } for _, tc := range testCases { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var subTimeout time.Duration if deadline, ok := t.Deadline(); ok { - subTimeout := time.Until(deadline) - subTimeout -= subTimeout / 10 // Leave 10% headroom for cleaning up subprocess. + timeout := time.Until(deadline) + if timeout < 2*settleTime { + t.Fatalf("starting test with less than %v remaining", 2*settleTime) + } + subTimeout = timeout - (timeout / 10) // Leave 10% headroom for cleaning up subprocess. } args := []string{ @@ -750,7 +756,7 @@ func TestNotifyContextNotifications(t *testing.T) { if err != nil { t.Errorf("ran test with -check_notify_ctx_notification and it failed with %v.\nOutput:\n%s", err, out) } - if want := []byte("received SIGINT"); !bytes.Contains(out, want) { + if want := []byte("received SIGINT\n"); !bytes.Contains(out, want) { t.Errorf("got %q, wanted %q", out, want) } }) diff --git a/src/os/stat_solaris.go b/src/os/stat_solaris.go index 316c26c7ca..4e00ecb075 100644 --- a/src/os/stat_solaris.go +++ b/src/os/stat_solaris.go @@ -9,6 +9,14 @@ import ( "time" ) +// These constants aren't in the syscall package, which is frozen. +// Values taken from golang.org/x/sys/unix. +const ( + _S_IFNAM = 0x5000 + _S_IFDOOR = 0xd000 + _S_IFPORT = 0xe000 +) + func fillFileStatFromSys(fs *fileStat, name string) { fs.name = basename(name) fs.size = fs.sys.Size @@ -29,6 +37,8 @@ func fillFileStatFromSys(fs *fileStat, name string) { // nothing to do case syscall.S_IFSOCK: fs.mode |= ModeSocket + case _S_IFNAM, _S_IFDOOR, _S_IFPORT: + fs.mode |= ModeIrregular } if fs.sys.Mode&syscall.S_ISGID != 0 { fs.mode |= ModeSetgid diff --git a/src/path/filepath/path.go b/src/path/filepath/path.go index 0554deb2ff..ec9e6d8a1f 100644 --- a/src/path/filepath/path.go +++ b/src/path/filepath/path.go @@ -67,13 +67,13 @@ const ( // by purely lexical processing. It applies the following rules // iteratively until no further processing can be done: // -// 1. Replace multiple Separator elements with a single one. -// 2. Eliminate each . path name element (the current directory). -// 3. Eliminate each inner .. path name element (the parent directory) -// along with the non-.. element that precedes it. -// 4. Eliminate .. elements that begin a rooted path: -// that is, replace "/.." by "/" at the beginning of a path, -// assuming Separator is '/'. +// 1. Replace multiple Separator elements with a single one. +// 2. Eliminate each . path name element (the current directory). +// 3. Eliminate each inner .. path name element (the parent directory) +// along with the non-.. element that precedes it. +// 4. Eliminate .. elements that begin a rooted path: +// that is, replace "/.." by "/" at the beginning of a path, +// assuming Separator is '/'. // // The returned path ends in a slash only if it represents a root directory, // such as "/" on Unix or `C:\` on Windows. @@ -83,8 +83,8 @@ const ( // If the result of this process is an empty string, Clean // returns the string ".". // -// See also Rob Pike, ``Lexical File Names in Plan 9 or -// Getting Dot-Dot Right,'' +// See also Rob Pike, “Lexical File Names in Plan 9 or +// Getting Dot-Dot Right,” // https://9p.io/sys/doc/lexnames.html func Clean(path string) string { originalPath := path diff --git a/src/path/filepath/path_windows_test.go b/src/path/filepath/path_windows_test.go index 76a459ac96..37019210fa 100644 --- a/src/path/filepath/path_windows_test.go +++ b/src/path/filepath/path_windows_test.go @@ -197,13 +197,17 @@ func TestEvalSymlinksCanonicalNames(t *testing.T) { // (where c: is vol parameter) to discover "8dot3 name creation state". // The state is combination of 2 flags. The global flag controls if it // is per volume or global setting: -// 0 - Enable 8dot3 name creation on all volumes on the system -// 1 - Disable 8dot3 name creation on all volumes on the system -// 2 - Set 8dot3 name creation on a per volume basis -// 3 - Disable 8dot3 name creation on all volumes except the system volume +// +// 0 - Enable 8dot3 name creation on all volumes on the system +// 1 - Disable 8dot3 name creation on all volumes on the system +// 2 - Set 8dot3 name creation on a per volume basis +// 3 - Disable 8dot3 name creation on all volumes except the system volume +// // If global flag is set to 2, then per-volume flag needs to be examined: -// 0 - Enable 8dot3 name creation on this volume -// 1 - Disable 8dot3 name creation on this volume +// +// 0 - Enable 8dot3 name creation on this volume +// 1 - Disable 8dot3 name creation on this volume +// // checkVolume8dot3Setting verifies that "8dot3 name creation" flags // are set to 2 and 0, if enabled parameter is true, or 2 and 1, if enabled // is false. Otherwise checkVolume8dot3Setting returns error. diff --git a/src/path/filepath/symlink_windows.go b/src/path/filepath/symlink_windows.go index d72279e2bb..9a436d5978 100644 --- a/src/path/filepath/symlink_windows.go +++ b/src/path/filepath/symlink_windows.go @@ -49,11 +49,12 @@ func baseIsDotDot(path string) bool { // toNorm returns the normalized path that is guaranteed to be unique. // It should accept the following formats: -// * UNC paths (e.g \\server\share\foo\bar) -// * absolute paths (e.g C:\foo\bar) -// * relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.) -// * relative paths begin with '\' (e.g \foo\bar) -// * relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .) +// - UNC paths (e.g \\server\share\foo\bar) +// - absolute paths (e.g C:\foo\bar) +// - relative paths begin with drive letter (e.g C:foo\bar, C:..\foo\bar, C:.., C:.) +// - relative paths begin with '\' (e.g \foo\bar) +// - relative paths begin without '\' (e.g foo\bar, ..\foo\bar, .., .) +// // The returned normalized path will be in the same form (of 5 listed above) as the input path. // If two paths A and B are indicating the same file with the same format, toNorm(A) should be equal to toNorm(B). // The normBase parameter should be equal to the normBase func, except for in tests. See docs on the normBase func. diff --git a/src/path/path.go b/src/path/path.go index f1f3499f63..547b9debce 100644 --- a/src/path/path.go +++ b/src/path/path.go @@ -52,20 +52,20 @@ func (b *lazybuf) string() string { // by purely lexical processing. It applies the following rules // iteratively until no further processing can be done: // -// 1. Replace multiple slashes with a single slash. -// 2. Eliminate each . path name element (the current directory). -// 3. Eliminate each inner .. path name element (the parent directory) -// along with the non-.. element that precedes it. -// 4. Eliminate .. elements that begin a rooted path: -// that is, replace "/.." by "/" at the beginning of a path. +// 1. Replace multiple slashes with a single slash. +// 2. Eliminate each . path name element (the current directory). +// 3. Eliminate each inner .. path name element (the parent directory) +// along with the non-.. element that precedes it. +// 4. Eliminate .. elements that begin a rooted path: +// that is, replace "/.." by "/" at the beginning of a path. // // The returned path ends in a slash only if it is the root "/". // // If the result of this process is an empty string, Clean // returns the string ".". // -// See also Rob Pike, ``Lexical File Names in Plan 9 or -// Getting Dot-Dot Right,'' +// See also Rob Pike, “Lexical File Names in Plan 9 or +// Getting Dot-Dot Right,” // https://9p.io/sys/doc/lexnames.html func Clean(path string) string { if path == "" { diff --git a/src/plugin/plugin_dlopen.go b/src/plugin/plugin_dlopen.go index c59f11ef71..6ba0f78065 100644 --- a/src/plugin/plugin_dlopen.go +++ b/src/plugin/plugin_dlopen.go @@ -150,5 +150,6 @@ var ( func lastmoduleinit() (pluginpath string, syms map[string]any, errstr string) // doInit is defined in package runtime +// //go:linkname doInit runtime.doInit func doInit(t unsafe.Pointer) // t should be a *runtime.initTask diff --git a/src/reflect/abi_test.go b/src/reflect/abi_test.go index c9a4cd1c8e..9d93472779 100644 --- a/src/reflect/abi_test.go +++ b/src/reflect/abi_test.go @@ -545,6 +545,7 @@ func passEmptyStruct(a int, b struct{}, c float64) (int, struct{}, float64) { // This test case forces a large argument to the stack followed by more // in-register arguments. +// //go:registerparams //go:noinline func passStruct10AndSmall(a Struct10, b byte, c uint) (Struct10, byte, uint) { diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index 5a35d98b51..a886f9f64a 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -11,6 +11,7 @@ import ( "fmt" "go/token" "internal/goarch" + "internal/testenv" "io" "math" "math/rand" @@ -363,6 +364,10 @@ func TestMapIterSet(t *testing.T) { } } + if strings.HasSuffix(testenv.Builder(), "-noopt") { + return // no inlining with the noopt builder + } + got := int(testing.AllocsPerRun(10, func() { iter := v.MapRange() for iter.Next() { @@ -370,9 +375,12 @@ func TestMapIterSet(t *testing.T) { e.SetIterValue(iter) } })) - // Making a *MapIter allocates. This should be the only allocation. - if got != 1 { - t.Errorf("wanted 1 alloc, got %d", got) + // Calling MapRange should not allocate even though it returns a *MapIter. + // The function is inlineable, so if the local usage does not escape + // the *MapIter, it can remain stack allocated. + want := 0 + if got != want { + t.Errorf("wanted %d alloc, got %d", want, got) } } diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index eaab101221..50b436e5f6 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -174,7 +174,7 @@ func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { } } -// DeepEqual reports whether x and y are ``deeply equal,'' defined as follows. +// DeepEqual reports whether x and y are “deeply equal,” defined as follows. // Two values of identical type are deeply equal if one of the following cases applies. // Values of distinct types are never deeply equal. // diff --git a/src/reflect/makefunc.go b/src/reflect/makefunc.go index 3d9279ceaa..ee0729903e 100644 --- a/src/reflect/makefunc.go +++ b/src/reflect/makefunc.go @@ -26,9 +26,9 @@ type makeFuncImpl struct { // that wraps the function fn. When called, that new function // does the following: // -// - converts its arguments to a slice of Values. -// - runs results := fn(args). -// - returns the results as a slice of Values, one per formal result. +// - converts its arguments to a slice of Values. +// - runs results := fn(args). +// - returns the results as a slice of Values, one per formal result. // // The implementation fn can assume that the argument Value slice // has the number and type of arguments given by typ. @@ -158,6 +158,7 @@ type makeFuncCtxt struct { // nosplit because pointers are being held in uintptr slots in args, so // having our stack scanned now could lead to accidentally freeing // memory. +// //go:nosplit func moveMakeFuncArgPtrs(ctxt *makeFuncCtxt, args *abi.RegArgs) { for i, arg := range args.Ints { diff --git a/src/reflect/type.go b/src/reflect/type.go index 53c17f9e55..e888266475 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -277,6 +277,7 @@ const Ptr = Pointer // available in the memory directly following the rtype value. // // tflag values must be kept in sync with copies in: +// // cmd/compile/internal/reflectdata/reflect.go // cmd/link/internal/ld/decodesym.go // runtime/type.go diff --git a/src/reflect/value.go b/src/reflect/value.go index f1454b8ae2..6fe3cee017 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1437,7 +1437,9 @@ func (v Value) CanInterface() bool { // Interface returns v's current value as an interface{}. // It is equivalent to: +// // var i interface{} = (v's underlying value) +// // It panics if the Value was obtained by accessing // unexported struct fields. func (v Value) Interface() (i any) { @@ -1825,16 +1827,26 @@ func (iter *MapIter) Reset(v Value) { // Example: // // iter := reflect.ValueOf(m).MapRange() -// for iter.Next() { +// for iter.Next() { // k := iter.Key() // v := iter.Value() // ... // } func (v Value) MapRange() *MapIter { - v.mustBe(Map) + // This is inlinable to take advantage of "function outlining". + // The allocation of MapIter can be stack allocated if the caller + // does not allow it to escape. + // See https://blog.filippo.io/efficient-go-apis-with-the-inliner/ + if v.kind() != Map { + v.panicNotMap() + } return &MapIter{m: v} } +func (f flag) panicNotMap() { + f.mustBe(Map) +} + // copyVal returns a Value containing the map key or value at ptr, // allocating a new variable as needed. func copyVal(typ *rtype, fl flag, ptr unsafe.Pointer) Value { @@ -2463,12 +2475,17 @@ func (v Value) TrySend(x Value) bool { // Type returns v's type. func (v Value) Type() Type { - f := v.flag - if f == 0 { + if v.flag != 0 && v.flag&flagMethod == 0 { + return v.typ + } + return v.typeSlow() +} + +func (v Value) typeSlow() Type { + if v.flag == 0 { panic(&ValueError{"reflect.Value.Type", Invalid}) } - if f&flagMethod == 0 { - // Easy case + if v.flag&flagMethod == 0 { return v.typ } @@ -2757,6 +2774,7 @@ type runtimeSelect struct { // If the case was a receive, val is filled in with the received value. // The conventional OK bool indicates whether the receive corresponds // to a sent value. +// //go:noescape func rselect([]runtimeSelect) (chosen int, recvOK bool) @@ -3493,6 +3511,7 @@ func maplen(m unsafe.Pointer) int // Arguments passed through to call do not escape. The type is used only in a // very limited callee of call, the stackArgs are copied, and regArgs is only // used in the call frame. +// //go:noescape //go:linkname call runtime.reflectcall func call(stackArgsType *rtype, f, stackArgs unsafe.Pointer, stackArgsSize, stackRetOffset, frameSize uint32, regArgs *abi.RegArgs) @@ -3500,29 +3519,35 @@ func call(stackArgsType *rtype, f, stackArgs unsafe.Pointer, stackArgsSize, stac func ifaceE2I(t *rtype, src any, dst unsafe.Pointer) // memmove copies size bytes to dst from src. No write barriers are used. +// //go:noescape func memmove(dst, src unsafe.Pointer, size uintptr) // typedmemmove copies a value of type t to dst from src. +// //go:noescape func typedmemmove(t *rtype, dst, src unsafe.Pointer) // typedmemmovepartial is like typedmemmove but assumes that // dst and src point off bytes into the value and only copies size bytes. +// //go:noescape func typedmemmovepartial(t *rtype, dst, src unsafe.Pointer, off, size uintptr) // typedmemclr zeros the value at ptr of type t. +// //go:noescape func typedmemclr(t *rtype, ptr unsafe.Pointer) // typedmemclrpartial is like typedmemclr but assumes that // dst points off bytes into the value and only clears size bytes. +// //go:noescape func typedmemclrpartial(t *rtype, ptr unsafe.Pointer, off, size uintptr) // typedslicecopy copies a slice of elemType values from src to dst, // returning the number of elements copied. +// //go:noescape func typedslicecopy(elemType *rtype, dst, src unsafeheader.Slice) int diff --git a/src/reflect/visiblefields_test.go b/src/reflect/visiblefields_test.go index fdedc21f73..66d545dd1f 100644 --- a/src/reflect/visiblefields_test.go +++ b/src/reflect/visiblefields_test.go @@ -78,7 +78,7 @@ var fieldsTests = []struct { index: []int{0, 1}, }}, }, { - testName: "TwoEmbeddedStructsWithCancellingMembers", + testName: "TwoEmbeddedStructsWithCancelingMembers", val: struct { SFG SF diff --git a/src/regexp/exec_test.go b/src/regexp/exec_test.go index a6e833050b..1694230345 100644 --- a/src/regexp/exec_test.go +++ b/src/regexp/exec_test.go @@ -52,8 +52,8 @@ import ( // submatch indices. An unmatched subexpression formats // its pair as a single - (not illustrated above). For now // each regexp run produces two match results, one for a -// ``full match'' that restricts the regexp to matching the entire -// string or nothing, and one for a ``partial match'' that gives +// “full match” that restricts the regexp to matching the entire +// string or nothing, and one for a “partial match” that gives // the leftmost first match found in the string. // // Lines beginning with # are comments. Lines beginning with diff --git a/src/regexp/regexp.go b/src/regexp/regexp.go index 26ac5f48b2..7958a39728 100644 --- a/src/regexp/regexp.go +++ b/src/regexp/regexp.go @@ -9,14 +9,17 @@ // More precisely, it is the syntax accepted by RE2 and described at // https://golang.org/s/re2syntax, except for \C. // For an overview of the syntax, run -// go doc regexp/syntax +// +// go doc regexp/syntax // // The regexp implementation provided by this package is // guaranteed to run in time linear in the size of the input. // (This is a property not guaranteed by most open source // implementations of regular expressions.) For more information // about this property, see +// // https://swtch.com/~rsc/regexp/regexp1.html +// // or any book about automata theory. // // All characters are UTF-8-encoded code points. @@ -64,7 +67,6 @@ // before returning. // // (There are a few other methods that do not match this pattern.) -// package regexp import ( @@ -1239,13 +1241,15 @@ func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int { // that contains no metacharacters, it is equivalent to strings.SplitN. // // Example: -// s := regexp.MustCompile("a*").Split("abaabaccadaaae", 5) -// // s: ["", "b", "b", "c", "cadaaae"] +// +// s := regexp.MustCompile("a*").Split("abaabaccadaaae", 5) +// // s: ["", "b", "b", "c", "cadaaae"] // // The count determines the number of substrings to return: -// n > 0: at most n substrings; the last substring will be the unsplit remainder. -// n == 0: the result is nil (zero substrings) -// n < 0: all substrings +// +// n > 0: at most n substrings; the last substring will be the unsplit remainder. +// n == 0: the result is nil (zero substrings) +// n < 0: all substrings func (re *Regexp) Split(s string, n int) []string { if n == 0 { diff --git a/src/regexp/syntax/doc.go b/src/regexp/syntax/doc.go index b3f9136b5f..f6a4b43f7a 100644 --- a/src/regexp/syntax/doc.go +++ b/src/regexp/syntax/doc.go @@ -9,123 +9,132 @@ Package syntax parses regular expressions into parse trees and compiles parse trees into programs. Most clients of regular expressions will use the facilities of package regexp (such as Compile and Match) instead of this package. -Syntax +# Syntax The regular expression syntax understood by this package when parsing with the Perl flag is as follows. Parts of the syntax can be disabled by passing alternate flags to Parse. - Single characters: - . any character, possibly including newline (flag s=true) - [xyz] character class - [^xyz] negated character class - \d Perl character class - \D negated Perl character class - [[:alpha:]] ASCII character class - [[:^alpha:]] negated ASCII character class - \pN Unicode character class (one-letter name) - \p{Greek} Unicode character class - \PN negated Unicode character class (one-letter name) - \P{Greek} negated Unicode character class + + . any character, possibly including newline (flag s=true) + [xyz] character class + [^xyz] negated character class + \d Perl character class + \D negated Perl character class + [[:alpha:]] ASCII character class + [[:^alpha:]] negated ASCII character class + \pN Unicode character class (one-letter name) + \p{Greek} Unicode character class + \PN negated Unicode character class (one-letter name) + \P{Greek} negated Unicode character class Composites: - xy x followed by y - x|y x or y (prefer x) + + xy x followed by y + x|y x or y (prefer x) Repetitions: - x* zero or more x, prefer more - x+ one or more x, prefer more - x? zero or one x, prefer one - x{n,m} n or n+1 or ... or m x, prefer more - x{n,} n or more x, prefer more - x{n} exactly n x - x*? zero or more x, prefer fewer - x+? one or more x, prefer fewer - x?? zero or one x, prefer zero - x{n,m}? n or n+1 or ... or m x, prefer fewer - x{n,}? n or more x, prefer fewer - x{n}? exactly n x + + x* zero or more x, prefer more + x+ one or more x, prefer more + x? zero or one x, prefer one + x{n,m} n or n+1 or ... or m x, prefer more + x{n,} n or more x, prefer more + x{n} exactly n x + x*? zero or more x, prefer fewer + x+? one or more x, prefer fewer + x?? zero or one x, prefer zero + x{n,m}? n or n+1 or ... or m x, prefer fewer + x{n,}? n or more x, prefer fewer + x{n}? exactly n x Implementation restriction: The counting forms x{n,m}, x{n,}, and x{n} reject forms that create a minimum or maximum repetition count above 1000. Unlimited repetitions are not subject to this restriction. Grouping: - (re) numbered capturing group (submatch) - (?Pre) named & numbered capturing group (submatch) - (?:re) non-capturing group - (?flags) set flags within current group; non-capturing - (?flags:re) set flags during re; non-capturing - Flag syntax is xyz (set) or -xyz (clear) or xy-z (set xy, clear z). The flags are: + (re) numbered capturing group (submatch) + (?Pre) named & numbered capturing group (submatch) + (?:re) non-capturing group + (?flags) set flags within current group; non-capturing + (?flags:re) set flags during re; non-capturing - i case-insensitive (default false) - m multi-line mode: ^ and $ match begin/end line in addition to begin/end text (default false) - s let . match \n (default false) - U ungreedy: swap meaning of x* and x*?, x+ and x+?, etc (default false) + Flag syntax is xyz (set) or -xyz (clear) or xy-z (set xy, clear z). The flags are: + + i case-insensitive (default false) + m multi-line mode: ^ and $ match begin/end line in addition to begin/end text (default false) + s let . match \n (default false) + U ungreedy: swap meaning of x* and x*?, x+ and x+?, etc (default false) Empty strings: - ^ at beginning of text or line (flag m=true) - $ at end of text (like \z not \Z) or line (flag m=true) - \A at beginning of text - \b at ASCII word boundary (\w on one side and \W, \A, or \z on the other) - \B not at ASCII word boundary - \z at end of text + + ^ at beginning of text or line (flag m=true) + $ at end of text (like \z not \Z) or line (flag m=true) + \A at beginning of text + \b at ASCII word boundary (\w on one side and \W, \A, or \z on the other) + \B not at ASCII word boundary + \z at end of text Escape sequences: - \a bell (== \007) - \f form feed (== \014) - \t horizontal tab (== \011) - \n newline (== \012) - \r carriage return (== \015) - \v vertical tab character (== \013) - \* literal *, for any punctuation character * - \123 octal character code (up to three digits) - \x7F hex character code (exactly two digits) - \x{10FFFF} hex character code - \Q...\E literal text ... even if ... has punctuation + + \a bell (== \007) + \f form feed (== \014) + \t horizontal tab (== \011) + \n newline (== \012) + \r carriage return (== \015) + \v vertical tab character (== \013) + \* literal *, for any punctuation character * + \123 octal character code (up to three digits) + \x7F hex character code (exactly two digits) + \x{10FFFF} hex character code + \Q...\E literal text ... even if ... has punctuation Character class elements: - x single character - A-Z character range (inclusive) - \d Perl character class - [:foo:] ASCII character class foo - \p{Foo} Unicode character class Foo - \pF Unicode character class F (one-letter name) + + x single character + A-Z character range (inclusive) + \d Perl character class + [:foo:] ASCII character class foo + \p{Foo} Unicode character class Foo + \pF Unicode character class F (one-letter name) Named character classes as character class elements: - [\d] digits (== \d) - [^\d] not digits (== \D) - [\D] not digits (== \D) - [^\D] not not digits (== \d) - [[:name:]] named ASCII class inside character class (== [:name:]) - [^[:name:]] named ASCII class inside negated character class (== [:^name:]) - [\p{Name}] named Unicode property inside character class (== \p{Name}) - [^\p{Name}] named Unicode property inside negated character class (== \P{Name}) + + [\d] digits (== \d) + [^\d] not digits (== \D) + [\D] not digits (== \D) + [^\D] not not digits (== \d) + [[:name:]] named ASCII class inside character class (== [:name:]) + [^[:name:]] named ASCII class inside negated character class (== [:^name:]) + [\p{Name}] named Unicode property inside character class (== \p{Name}) + [^\p{Name}] named Unicode property inside negated character class (== \P{Name}) Perl character classes (all ASCII-only): - \d digits (== [0-9]) - \D not digits (== [^0-9]) - \s whitespace (== [\t\n\f\r ]) - \S not whitespace (== [^\t\n\f\r ]) - \w word characters (== [0-9A-Za-z_]) - \W not word characters (== [^0-9A-Za-z_]) + + \d digits (== [0-9]) + \D not digits (== [^0-9]) + \s whitespace (== [\t\n\f\r ]) + \S not whitespace (== [^\t\n\f\r ]) + \w word characters (== [0-9A-Za-z_]) + \W not word characters (== [^0-9A-Za-z_]) ASCII character classes: - [[:alnum:]] alphanumeric (== [0-9A-Za-z]) - [[:alpha:]] alphabetic (== [A-Za-z]) - [[:ascii:]] ASCII (== [\x00-\x7F]) - [[:blank:]] blank (== [\t ]) - [[:cntrl:]] control (== [\x00-\x1F\x7F]) - [[:digit:]] digits (== [0-9]) - [[:graph:]] graphical (== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) - [[:lower:]] lower case (== [a-z]) - [[:print:]] printable (== [ -~] == [ [:graph:]]) - [[:punct:]] punctuation (== [!-/:-@[-`{-~]) - [[:space:]] whitespace (== [\t\n\v\f\r ]) - [[:upper:]] upper case (== [A-Z]) - [[:word:]] word characters (== [0-9A-Za-z_]) - [[:xdigit:]] hex digit (== [0-9A-Fa-f]) + + [[:alnum:]] alphanumeric (== [0-9A-Za-z]) + [[:alpha:]] alphabetic (== [A-Za-z]) + [[:ascii:]] ASCII (== [\x00-\x7F]) + [[:blank:]] blank (== [\t ]) + [[:cntrl:]] control (== [\x00-\x1F\x7F]) + [[:digit:]] digits (== [0-9]) + [[:graph:]] graphical (== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) + [[:lower:]] lower case (== [a-z]) + [[:print:]] printable (== [ -~] == [ [:graph:]]) + [[:punct:]] punctuation (== [!-/:-@[-`{-~]) + [[:space:]] whitespace (== [\t\n\v\f\r ]) + [[:upper:]] upper case (== [A-Z]) + [[:word:]] word characters (== [0-9A-Za-z_]) + [[:xdigit:]] hex digit (== [0-9A-Fa-f]) Unicode character classes are those in unicode.Categories and unicode.Scripts. */ diff --git a/src/regexp/syntax/parse.go b/src/regexp/syntax/parse.go index ebf8e11915..cfb703d285 100644 --- a/src/regexp/syntax/parse.go +++ b/src/regexp/syntax/parse.go @@ -445,11 +445,16 @@ func (p *parser) collapse(subs []*Regexp, op Op) *Regexp { // frees (passes to p.reuse) any removed *Regexps. // // For example, -// ABC|ABD|AEF|BCX|BCY +// +// ABC|ABD|AEF|BCX|BCY +// // simplifies by literal prefix extraction to -// A(B(C|D)|EF)|BC(X|Y) +// +// A(B(C|D)|EF)|BC(X|Y) +// // which simplifies by character class introduction to -// A(B[CD]|EF)|BC[XY] +// +// A(B[CD]|EF)|BC[XY] func (p *parser) factor(sub []*Regexp) []*Regexp { if len(sub) < 2 { return sub diff --git a/src/regexp/syntax/prog.go b/src/regexp/syntax/prog.go index 8583f55e54..ee71decb35 100644 --- a/src/regexp/syntax/prog.go +++ b/src/regexp/syntax/prog.go @@ -102,7 +102,7 @@ func EmptyOpContext(r1, r2 rune) EmptyOp { return op } -// IsWordChar reports whether r is consider a ``word character'' +// IsWordChar reports whether r is consider a “word character” // during the evaluation of the \b and \B zero-width assertions. // These assertions are ASCII-only: the word characters are [A-Za-z0-9_]. func IsWordChar(r rune) bool { diff --git a/src/run.bash b/src/run.bash index 2123c509f8..99b09fcbde 100755 --- a/src/run.bash +++ b/src/run.bash @@ -21,14 +21,10 @@ if [ ! -f ../bin/go ]; then exit 1 fi -eval $(../bin/go env) +eval $(../bin/go tool dist env) export GOROOT # The api test requires GOROOT to be set, so set it to match ../bin/go. -export GOPATH=/nonexist-gopath unset CDPATH # in case user has it set -export GOBIN=$GOROOT/bin # Issue 14340 -unset GOFLAGS -unset GO111MODULE export GOHOSTOS export CC @@ -53,4 +49,5 @@ if ulimit -T &> /dev/null; then [ "$(ulimit -H -T)" = "unlimited" ] || ulimit -S -T $(ulimit -H -T) fi +export GOPATH=/nonexist-gopath exec ../bin/go tool dist test -rebuild "$@" diff --git a/src/run.bat b/src/run.bat index edcaf52659..b4bab85a93 100644 --- a/src/run.bat +++ b/src/run.bat @@ -18,32 +18,21 @@ setlocal set GOBUILDFAIL=0 -set GOPATH=c:\nonexist-gopath -:: Issue 14340: ignore GOBIN during all.bat. -set GOBIN= -set GOFLAGS= -set GO111MODULE= - -rem TODO avoid rebuild if possible - -if x%1==x--no-rebuild goto norebuild -echo ##### Building packages and commands. -..\bin\go install -a -v std cmd -if errorlevel 1 goto fail -echo. -:norebuild - -:: get CGO_ENABLED -..\bin\go env > env.bat +..\bin\go tool dist env > env.bat if errorlevel 1 goto fail call env.bat del env.bat -echo. +set GOPATH=c:\nonexist-gopath + +if x%1==x--no-rebuild goto norebuild +..\bin\go tool dist test --rebuild +if errorlevel 1 goto fail +goto end + +:norebuild ..\bin\go tool dist test if errorlevel 1 goto fail -echo. - goto end :fail diff --git a/src/run.rc b/src/run.rc index a7b4801207..2a0bb7f7a1 100755 --- a/src/run.rc +++ b/src/run.rc @@ -10,11 +10,7 @@ if(! test -f ../bin/go){ exit wrongdir } -eval `{../bin/go env} +eval `{../bin/go tool dist env} GOPATH=/nonexist-gopath -GOBIN=() # Issue 14340 -GOFLAGS=() -GO111MODULE=() - exec ../bin/go tool dist test -rebuild $* diff --git a/src/runtime/asan.go b/src/runtime/asan.go index 5f1e6370d2..8c41e418f7 100644 --- a/src/runtime/asan.go +++ b/src/runtime/asan.go @@ -56,6 +56,7 @@ func asanunpoison(addr unsafe.Pointer, sz uintptr) func asanpoison(addr unsafe.Pointer, sz uintptr) // These are called from asan_GOARCH.s +// //go:cgo_import_static __asan_read_go //go:cgo_import_static __asan_write_go //go:cgo_import_static __asan_unpoison_go diff --git a/src/runtime/callers_test.go b/src/runtime/callers_test.go index 3cf3fbe5ac..d245cbd2d2 100644 --- a/src/runtime/callers_test.go +++ b/src/runtime/callers_test.go @@ -309,3 +309,33 @@ func TestCallersDeferNilFuncPanicWithLoop(t *testing.T) { // function exit, rather than at the defer statement. state = 2 } + +// issue #51988 +// Func.Endlineno was lost when instantiating generic functions, leading to incorrect +// stack trace positions. +func TestCallersEndlineno(t *testing.T) { + testNormalEndlineno(t) + testGenericEndlineno[int](t) +} + +func testNormalEndlineno(t *testing.T) { + defer testCallerLine(t, callerLine(t, 0)+1) +} + +func testGenericEndlineno[_ any](t *testing.T) { + defer testCallerLine(t, callerLine(t, 0)+1) +} + +func testCallerLine(t *testing.T, want int) { + if have := callerLine(t, 1); have != want { + t.Errorf("callerLine(1) returned %d, but want %d\n", have, want) + } +} + +func callerLine(t *testing.T, skip int) int { + _, _, line, ok := runtime.Caller(skip + 1) + if !ok { + t.Fatalf("runtime.Caller(%d) failed", skip+1) + } + return line +} diff --git a/src/runtime/cgo/callbacks.go b/src/runtime/cgo/callbacks.go index cd8b795387..e7c8ef3e07 100644 --- a/src/runtime/cgo/callbacks.go +++ b/src/runtime/cgo/callbacks.go @@ -21,6 +21,7 @@ import "unsafe" // that pattern working. In particular, crosscall2 actually takes four // arguments, but it works to call it with three arguments when // calling _cgo_panic. +// //go:cgo_export_static crosscall2 //go:cgo_export_dynamic crosscall2 diff --git a/src/runtime/cgo/callbacks_aix.go b/src/runtime/cgo/callbacks_aix.go index f4b6fe25fa..8f756fbdd9 100644 --- a/src/runtime/cgo/callbacks_aix.go +++ b/src/runtime/cgo/callbacks_aix.go @@ -6,6 +6,7 @@ package cgo // These functions must be exported in order to perform // longcall on cgo programs (cf gcc_aix_ppc64.c). +// //go:cgo_export_static __cgo_topofstack //go:cgo_export_static runtime.rt0_go //go:cgo_export_static _rt0_ppc64_aix_lib diff --git a/src/runtime/cgo/openbsd.go b/src/runtime/cgo/openbsd.go index 872d02e334..26b62fbdaf 100644 --- a/src/runtime/cgo/openbsd.go +++ b/src/runtime/cgo/openbsd.go @@ -17,4 +17,5 @@ var _guard_local uintptr // This is normally marked as hidden and placed in the // .openbsd.randomdata section. +// //go:cgo_export_dynamic __guard_local __guard_local diff --git a/src/runtime/cgo_mmap.go b/src/runtime/cgo_mmap.go index 0cb25bdcda..4cb3e65f14 100644 --- a/src/runtime/cgo_mmap.go +++ b/src/runtime/cgo_mmap.go @@ -12,11 +12,13 @@ import "unsafe" // _cgo_mmap is filled in by runtime/cgo when it is linked into the // program, so it is only non-nil when using cgo. +// //go:linkname _cgo_mmap _cgo_mmap var _cgo_mmap unsafe.Pointer // _cgo_munmap is filled in by runtime/cgo when it is linked into the // program, so it is only non-nil when using cgo. +// //go:linkname _cgo_munmap _cgo_munmap var _cgo_munmap unsafe.Pointer @@ -24,6 +26,7 @@ var _cgo_munmap unsafe.Pointer // support sanitizer interceptors. Don't allow stack splits, since this function // (used by sysAlloc) is called in a lot of low-level parts of the runtime and // callers often assume it won't acquire any locks. +// //go:nosplit func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) { if _cgo_mmap != nil { diff --git a/src/runtime/cgo_ppc64x.go b/src/runtime/cgo_ppc64x.go index 97b962e40f..c723213809 100644 --- a/src/runtime/cgo_ppc64x.go +++ b/src/runtime/cgo_ppc64x.go @@ -9,4 +9,5 @@ package runtime // crosscall_ppc64 calls into the runtime to set up the registers the // Go runtime expects and so the symbol it calls needs to be exported // for external linking to work. +// //go:cgo_export_static _cgo_reginit diff --git a/src/runtime/cgo_sigaction.go b/src/runtime/cgo_sigaction.go index a2e12f0f0e..9500c52205 100644 --- a/src/runtime/cgo_sigaction.go +++ b/src/runtime/cgo_sigaction.go @@ -12,6 +12,7 @@ import "unsafe" // _cgo_sigaction is filled in by runtime/cgo when it is linked into the // program, so it is only non-nil when using cgo. +// //go:linkname _cgo_sigaction _cgo_sigaction var _cgo_sigaction unsafe.Pointer @@ -88,5 +89,6 @@ func sigaction(sig uint32, new, old *sigactiont) { // callCgoSigaction calls the sigaction function in the runtime/cgo package // using the GCC calling convention. It is implemented in assembly. +// //go:noescape func callCgoSigaction(sig uintptr, new, old *sigactiont) int32 diff --git a/src/runtime/cgocall.go b/src/runtime/cgocall.go index a0c9560fd0..977d049378 100644 --- a/src/runtime/cgocall.go +++ b/src/runtime/cgocall.go @@ -102,6 +102,7 @@ type argset struct { } // wrapper for syscall package to call cgocall for libc (cgo) calls. +// //go:linkname syscall_cgocaller syscall.cgocaller //go:nosplit //go:uintptrescapes @@ -199,6 +200,7 @@ func cgocall(fn, arg unsafe.Pointer) int32 { } // Call from C back to Go. fn must point to an ABIInternal Go entry-point. +// //go:nosplit func cgocallbackg(fn, frame unsafe.Pointer, ctxt uintptr) { gp := getg() @@ -598,6 +600,7 @@ func cgoCheckUnknownPointer(p unsafe.Pointer, msg string) (base, i uintptr) { // cgoIsGoPointer reports whether the pointer is a Go pointer--a // pointer to Go memory. We only care about Go memory that might // contain pointers. +// //go:nosplit //go:nowritebarrierrec func cgoIsGoPointer(p unsafe.Pointer) bool { @@ -619,6 +622,7 @@ func cgoIsGoPointer(p unsafe.Pointer) bool { } // cgoInRange reports whether p is between start and end. +// //go:nosplit //go:nowritebarrierrec func cgoInRange(p unsafe.Pointer, start, end uintptr) bool { diff --git a/src/runtime/cgocheck.go b/src/runtime/cgocheck.go index 3acbadf803..74a2ec09bc 100644 --- a/src/runtime/cgocheck.go +++ b/src/runtime/cgocheck.go @@ -61,6 +61,7 @@ func cgoCheckWriteBarrier(dst *uintptr, src uintptr) { // size is the number of bytes to copy. // It throws if the program is copying a block that contains a Go pointer // into non-Go memory. +// //go:nosplit //go:nowritebarrier func cgoCheckMemmove(typ *_type, dst, src unsafe.Pointer, off, size uintptr) { @@ -81,6 +82,7 @@ func cgoCheckMemmove(typ *_type, dst, src unsafe.Pointer, off, size uintptr) { // typ is the element type of the slice. // It throws if the program is copying slice elements that contain Go pointers // into non-Go memory. +// //go:nosplit //go:nowritebarrier func cgoCheckSliceCopy(typ *_type, dst, src unsafe.Pointer, n int) { @@ -103,6 +105,7 @@ func cgoCheckSliceCopy(typ *_type, dst, src unsafe.Pointer, n int) { // cgoCheckTypedBlock checks the block of memory at src, for up to size bytes, // and throws if it finds a Go pointer. The type of the memory is typ, // and src is off bytes into that type. +// //go:nosplit //go:nowritebarrier func cgoCheckTypedBlock(typ *_type, src unsafe.Pointer, off, size uintptr) { @@ -166,6 +169,7 @@ func cgoCheckTypedBlock(typ *_type, src unsafe.Pointer, off, size uintptr) { // cgoCheckBits checks the block of memory at src, for up to size // bytes, and throws if it finds a Go pointer. The gcbits mark each // pointer value. The src pointer is off bytes into the gcbits. +// //go:nosplit //go:nowritebarrier func cgoCheckBits(src unsafe.Pointer, gcbits *byte, off, size uintptr) { @@ -201,6 +205,7 @@ func cgoCheckBits(src unsafe.Pointer, gcbits *byte, off, size uintptr) { // We only use this when looking at a value on the stack when the type // uses a GC program, because otherwise it's more efficient to use the // GC bits. This is called on the system stack. +// //go:nowritebarrier //go:systemstack func cgoCheckUsingType(typ *_type, src unsafe.Pointer, off, size uintptr) { diff --git a/src/runtime/chan.go b/src/runtime/chan.go index a16782ae94..993af7063b 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -139,6 +139,7 @@ func full(c *hchan) bool { } // entry point for c <- x from compiled code +// //go:nosplit func chansend1(c *hchan, elem unsafe.Pointer) { chansend(c, elem, true, getcallerpc()) @@ -435,6 +436,7 @@ func empty(c *hchan) bool { } // entry points for <- c from compiled code +// //go:nosplit func chanrecv1(c *hchan, elem unsafe.Pointer) { chanrecv(c, elem, true) @@ -508,24 +510,28 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) lock(&c.lock) - if c.closed != 0 && c.qcount == 0 { - if raceenabled { - raceacquire(c.raceaddr()) + if c.closed != 0 { + if c.qcount == 0 { + if raceenabled { + raceacquire(c.raceaddr()) + } + unlock(&c.lock) + if ep != nil { + typedmemclr(c.elemtype, ep) + } + return true, false } - unlock(&c.lock) - if ep != nil { - typedmemclr(c.elemtype, ep) + // The channel has been closed, but the channel's buffer have data. + } else { + // Just found waiting sender with not closed. + if sg := c.sendq.dequeue(); sg != nil { + // Found a waiting sender. If buffer is size 0, receive value + // directly from sender. Otherwise, receive from head of queue + // and add sender's value to the tail of the queue (both map to + // the same buffer slot because the queue is full). + recv(c, sg, ep, func() { unlock(&c.lock) }, 3) + return true, true } - return true, false - } - - if sg := c.sendq.dequeue(); sg != nil { - // Found a waiting sender. If buffer is size 0, receive value - // directly from sender. Otherwise, receive from head of queue - // and add sender's value to the tail of the queue (both map to - // the same buffer slot because the queue is full). - recv(c, sg, ep, func() { unlock(&c.lock) }, 3) - return true, true } if c.qcount > 0 { @@ -594,10 +600,11 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) // recv processes a receive operation on a full channel c. // There are 2 parts: -// 1) The value sent by the sender sg is put into the channel +// 1. The value sent by the sender sg is put into the channel // and the sender is woken up to go on its merry way. -// 2) The value received by the receiver (the current G) is +// 2. The value received by the receiver (the current G) is // written to ep. +// // For synchronous channels, both values are the same. // For asynchronous channels, the receiver gets its data from // the channel buffer and the sender's data is put in the diff --git a/src/runtime/chan_test.go b/src/runtime/chan_test.go index 9471d4596c..a8627e9898 100644 --- a/src/runtime/chan_test.go +++ b/src/runtime/chan_test.go @@ -226,11 +226,13 @@ func TestNonblockRecvRace(t *testing.T) { // This test checks that select acts on the state of the channels at one // moment in the execution, not over a smeared time window. // In the test, one goroutine does: +// // create c1, c2 // make c1 ready for receiving // create second goroutine // make c2 ready for receiving // make c1 no longer ready for receiving (if possible) +// // The second goroutine does a non-blocking select receiving from c1 and c2. // From the time the second goroutine is created, at least one of c1 and c2 // is always ready for receiving, so the select in the second goroutine must @@ -1125,6 +1127,19 @@ func BenchmarkSelectProdCons(b *testing.B) { } } +func BenchmarkReceiveDataFromClosedChan(b *testing.B) { + count := b.N + ch := make(chan struct{}, count) + for i := 0; i < count; i++ { + ch <- struct{}{} + } + close(ch) + + b.ResetTimer() + for range ch { + } +} + func BenchmarkChanCreation(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { diff --git a/src/runtime/cpuprof.go b/src/runtime/cpuprof.go index 48cef46fe9..07673c9bd0 100644 --- a/src/runtime/cpuprof.go +++ b/src/runtime/cpuprof.go @@ -88,6 +88,7 @@ func SetCPUProfileRate(hz int) { // and cannot allocate memory or acquire locks that might be // held at the time of the signal, nor can it use substantial amounts // of stack. +// //go:nowritebarrierrec func (p *cpuProfile) add(tagPtr *unsafe.Pointer, stk []uintptr) { // Simple cas-lock to coordinate with setcpuprofilerate. @@ -117,6 +118,7 @@ func (p *cpuProfile) add(tagPtr *unsafe.Pointer, stk []uintptr) { // Instead, we copy the stack into cpuprof.extra, // which will be drained the next time a Go thread // gets the signal handling event. +// //go:nosplit //go:nowritebarrierrec func (p *cpuProfile) addNonGo(stk []uintptr) { diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go index d8cabcdda2..01d7cbeb29 100644 --- a/src/runtime/crash_test.go +++ b/src/runtime/crash_test.go @@ -800,3 +800,47 @@ func TestDoublePanic(t *testing.T) { } } } + +// Test that panic while panicking discards error message +// See issue 52257 +func TestPanicWhilePanicking(t *testing.T) { + tests := []struct { + Want string + Func string + }{ + { + "panic while printing panic value: important error message", + "ErrorPanic", + }, + { + "panic while printing panic value: important stringer message", + "StringerPanic", + }, + { + "panic while printing panic value: type", + "DoubleErrorPanic", + }, + { + "panic while printing panic value: type", + "DoubleStringerPanic", + }, + { + "panic while printing panic value: type", + "CircularPanic", + }, + { + "important string message", + "StringPanic", + }, + { + "nil", + "NilPanic", + }, + } + for _, x := range tests { + output := runTestProg(t, "testprog", x.Func) + if !strings.Contains(output, x.Want) { + t.Errorf("output does not contain %q:\n%s", x.Want, output) + } + } +} diff --git a/src/runtime/debug.go b/src/runtime/debug.go index 2703a0ce01..0ab23e0eb7 100644 --- a/src/runtime/debug.go +++ b/src/runtime/debug.go @@ -69,7 +69,7 @@ func debug_modinfo() string { // preemption points. To apply this to all preemption points in the // runtime and runtime-like code, use the following in bash or zsh: // -// X=(-{gc,asm}flags={runtime/...,reflect,sync}=-d=maymorestack=runtime.mayMoreStackPreempt) GOFLAGS=${X[@]} +// X=(-{gc,asm}flags={runtime/...,reflect,sync}=-d=maymorestack=runtime.mayMoreStackPreempt) GOFLAGS=${X[@]} // // This must be deeply nosplit because it is called from a function // prologue before the stack is set up and because the compiler will @@ -79,10 +79,9 @@ func debug_modinfo() string { // Ideally it should also use very little stack because the linker // doesn't currently account for this in nosplit stack depth checking. // -//go:nosplit -// // Ensure mayMoreStackPreempt can be called for all ABIs. // +//go:nosplit //go:linkname mayMoreStackPreempt func mayMoreStackPreempt() { // Don't do anything on the g0 or gsignal stack. diff --git a/src/runtime/debug/garbage.go b/src/runtime/debug/garbage.go index 00f92c3ddf..ce4bb10407 100644 --- a/src/runtime/debug/garbage.go +++ b/src/runtime/debug/garbage.go @@ -142,7 +142,9 @@ func SetMaxThreads(threads int) int { // dramatic situations; SetPanicOnFault allows such programs to request // that the runtime trigger only a panic, not a crash. // The runtime.Error that the runtime panics with may have an additional method: -// Addr() uintptr +// +// Addr() uintptr +// // If that method exists, it returns the memory address which triggered the fault. // The results of Addr are best-effort and the veracity of the result // may depend on the platform. diff --git a/src/runtime/debug/heapdump_test.go b/src/runtime/debug/heapdump_test.go index 768934d05d..ee6b054b11 100644 --- a/src/runtime/debug/heapdump_test.go +++ b/src/runtime/debug/heapdump_test.go @@ -67,3 +67,29 @@ func TestWriteHeapDumpFinalizers(t *testing.T) { WriteHeapDump(f.Fd()) println("done dump") } + +type G[T any] struct{} +type I interface { + M() +} + +//go:noinline +func (g G[T]) M() {} + +var dummy I = G[int]{} +var dummy2 I = G[G[int]]{} + +func TestWriteHeapDumpTypeName(t *testing.T) { + if runtime.GOOS == "js" { + t.Skipf("WriteHeapDump is not available on %s.", runtime.GOOS) + } + f, err := os.CreateTemp("", "heapdumptest") + if err != nil { + t.Fatalf("TempFile failed: %v", err) + } + defer os.Remove(f.Name()) + defer f.Close() + WriteHeapDump(f.Fd()) + dummy.M() + dummy2.M() +} diff --git a/src/runtime/env_plan9.go b/src/runtime/env_plan9.go index f1ac4760a7..65480c8217 100644 --- a/src/runtime/env_plan9.go +++ b/src/runtime/env_plan9.go @@ -25,6 +25,7 @@ const ( // For Plan 9 shared environment semantics, instead of Getenv(key) and // Setenv(key, value), one can use os.ReadFile("/env/" + key) and // os.WriteFile("/env/" + key, value, 0666) respectively. +// //go:nosplit func goenvs() { buf := make([]byte, envBufSize) @@ -71,6 +72,7 @@ func goenvs() { // Dofiles reads the directory opened with file descriptor fd, applying function f // to each filename in it. +// //go:nosplit func dofiles(dirfd int32, f func([]byte)) { dirbuf := new([dirBufSize]byte) @@ -96,6 +98,7 @@ func dofiles(dirfd int32, f func([]byte)) { // Gdirname returns the first filename from a buffer of directory entries, // and a slice containing the remaining directory entries. // If the buffer doesn't start with a valid directory entry, the returned name is nil. +// //go:nosplit func gdirname(buf []byte) (name []byte, rest []byte) { if 2+nameOffset+2 > len(buf) { @@ -116,6 +119,7 @@ func gdirname(buf []byte) (name []byte, rest []byte) { // Gbit16 reads a 16-bit little-endian binary number from b and returns it // with the remaining slice of b. +// //go:nosplit func gbit16(b []byte) (int, []byte) { return int(b[0]) | int(b[1])<<8, b[2:] diff --git a/src/runtime/env_posix.go b/src/runtime/env_posix.go index 7d01ab4dd7..94a19d80d8 100644 --- a/src/runtime/env_posix.go +++ b/src/runtime/env_posix.go @@ -49,6 +49,7 @@ var _cgo_unsetenv unsafe.Pointer // pointer to C function // Update the C environment if cgo is loaded. // Called from syscall.Setenv. +// //go:linkname syscall_setenv_c syscall.setenv_c func syscall_setenv_c(k string, v string) { if _cgo_setenv == nil { @@ -60,6 +61,7 @@ func syscall_setenv_c(k string, v string) { // Update the C environment if cgo is loaded. // Called from syscall.unsetenv. +// //go:linkname syscall_unsetenv_c syscall.unsetenv_c func syscall_unsetenv_c(k string) { if _cgo_unsetenv == nil { diff --git a/src/runtime/error.go b/src/runtime/error.go index 43114f092e..b11473c634 100644 --- a/src/runtime/error.go +++ b/src/runtime/error.go @@ -53,10 +53,11 @@ func (e *TypeAssertionError) Error() string { ": missing method " + e.missingMethod } -//go:nosplit // itoa converts val to a decimal representation. The result is // written somewhere within buf and the location of the result is returned. // buf must be at least 20 bytes. +// +//go:nosplit func itoa(buf []byte, val uint64) []byte { i := len(buf) - 1 for val >= 10 { diff --git a/src/runtime/export_test.go b/src/runtime/export_test.go index 0156981524..af27050bfd 100644 --- a/src/runtime/export_test.go +++ b/src/runtime/export_test.go @@ -1143,6 +1143,7 @@ func SemNwait(addr *uint32) uint32 { } // mspan wrapper for testing. +// //go:notinheap type MSpan mspan diff --git a/src/runtime/extern.go b/src/runtime/extern.go index f1f6ea5123..9dd59e0985 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -8,7 +8,7 @@ such as functions to control goroutines. It also includes the low-level type inf used by the reflect package; see reflect's documentation for the programmable interface to the run-time type system. -Environment Variables +# Environment Variables The following environment variables ($name or %name%, depending on the host operating system) control the run-time behavior of Go programs. The meanings @@ -64,13 +64,15 @@ It is a comma-separated list of name=val pairs setting these named variables: Currently, it is: gc # @#s #%: #+#+# ms clock, #+#/#/#+# ms cpu, #->#-># MB, # MB goal, # P where the fields are as follows: - gc # the GC number, incremented at each GC - @#s time in seconds since program start - #% percentage of time spent in GC since program start - #+...+# wall-clock/CPU times for the phases of the GC - #->#-># MB heap size at GC start, at GC end, and live heap - # MB goal goal heap size - # P number of processors used + gc # the GC number, incremented at each GC + @#s time in seconds since program start + #% percentage of time spent in GC since program start + #+...+# wall-clock/CPU times for the phases of the GC + #->#-># MB heap size at GC start, at GC end, and live heap + # MB goal goal heap size + # MB stacks estimated scannable stack size + # MB globals scannable global size + # P number of processors used The phases are stop-the-world (STW) sweep termination, concurrent mark and scan, and STW mark termination. The CPU times for mark/scan are broken down in to assist time (GC performed in @@ -170,9 +172,9 @@ or the failure is internal to the run-time. GOTRACEBACK=none omits the goroutine stack traces entirely. GOTRACEBACK=single (the default) behaves as described above. GOTRACEBACK=all adds stack traces for all user-created goroutines. -GOTRACEBACK=system is like ``all'' but adds stack frames for run-time functions +GOTRACEBACK=system is like “all” but adds stack frames for run-time functions and shows goroutines created internally by the run-time. -GOTRACEBACK=crash is like ``system'' but crashes in an operating system-specific +GOTRACEBACK=crash is like “system” but crashes in an operating system-specific manner instead of exiting. For example, on Unix systems, the crash raises SIGABRT to trigger a core dump. For historical reasons, the GOTRACEBACK settings 0, 1, and 2 are synonyms for diff --git a/src/runtime/float.go b/src/runtime/float.go index 459e58dd7e..c80c8b7abf 100644 --- a/src/runtime/float.go +++ b/src/runtime/float.go @@ -8,7 +8,7 @@ import "unsafe" var inf = float64frombits(0x7FF0000000000000) -// isNaN reports whether f is an IEEE 754 ``not-a-number'' value. +// isNaN reports whether f is an IEEE 754 “not-a-number” value. func isNaN(f float64) (is bool) { // IEEE 754 says that only NaNs satisfy f != f. return f != f @@ -27,6 +27,7 @@ func isInf(f float64) bool { // Abs returns the absolute value of x. // // Special cases are: +// // Abs(±Inf) = +Inf // Abs(NaN) = NaN func abs(x float64) float64 { diff --git a/src/runtime/histogram.go b/src/runtime/histogram.go index cd7e29a8c8..eddfbab3bc 100644 --- a/src/runtime/histogram.go +++ b/src/runtime/histogram.go @@ -84,6 +84,7 @@ type timeHistogram struct { // // Disallow preemptions and stack growths because this function // may run in sensitive locations. +// //go:nosplit func (h *timeHistogram) record(duration int64) { if duration < 0 { diff --git a/src/runtime/internal/atomic/atomic_386.go b/src/runtime/internal/atomic/atomic_386.go index 27a77ec37a..bf2f4b9229 100644 --- a/src/runtime/internal/atomic/atomic_386.go +++ b/src/runtime/internal/atomic/atomic_386.go @@ -9,6 +9,7 @@ package atomic import "unsafe" // Export some functions via linkname to assembly in sync/atomic. +// //go:linkname Load //go:linkname Loadp diff --git a/src/runtime/internal/atomic/atomic_amd64.go b/src/runtime/internal/atomic/atomic_amd64.go index e36eb83a11..52a83620c8 100644 --- a/src/runtime/internal/atomic/atomic_amd64.go +++ b/src/runtime/internal/atomic/atomic_amd64.go @@ -7,6 +7,7 @@ package atomic import "unsafe" // Export some functions via linkname to assembly in sync/atomic. +// //go:linkname Load //go:linkname Loadp //go:linkname Load64 diff --git a/src/runtime/internal/atomic/atomic_arm.go b/src/runtime/internal/atomic/atomic_arm.go index e2539b6c7e..bdb1847279 100644 --- a/src/runtime/internal/atomic/atomic_arm.go +++ b/src/runtime/internal/atomic/atomic_arm.go @@ -12,6 +12,7 @@ import ( ) // Export some functions via linkname to assembly in sync/atomic. +// //go:linkname Xchg //go:linkname Xchguintptr @@ -43,6 +44,7 @@ func addrLock(addr *uint64) *spinlock { } // Atomic add and return new value. +// //go:nosplit func Xadd(val *uint32, delta int32) uint32 { for { diff --git a/src/runtime/internal/atomic/atomic_mipsx.go b/src/runtime/internal/atomic/atomic_mipsx.go index e552e57495..5dd15a0b02 100644 --- a/src/runtime/internal/atomic/atomic_mipsx.go +++ b/src/runtime/internal/atomic/atomic_mipsx.go @@ -5,6 +5,7 @@ //go:build mips || mipsle // Export some functions via linkname to assembly in sync/atomic. +// //go:linkname Xadd64 //go:linkname Xchg64 //go:linkname Cas64 diff --git a/src/runtime/internal/atomic/atomic_s390x.go b/src/runtime/internal/atomic/atomic_s390x.go index a058d60102..9855bf0780 100644 --- a/src/runtime/internal/atomic/atomic_s390x.go +++ b/src/runtime/internal/atomic/atomic_s390x.go @@ -7,6 +7,7 @@ package atomic import "unsafe" // Export some functions via linkname to assembly in sync/atomic. +// //go:linkname Load //go:linkname Loadp //go:linkname Load64 diff --git a/src/runtime/internal/atomic/atomic_wasm.go b/src/runtime/internal/atomic/atomic_wasm.go index 3f77f16b4e..835fc43ccf 100644 --- a/src/runtime/internal/atomic/atomic_wasm.go +++ b/src/runtime/internal/atomic/atomic_wasm.go @@ -6,6 +6,7 @@ // See https://github.com/WebAssembly/design/issues/1073 // Export some functions via linkname to assembly in sync/atomic. +// //go:linkname Load //go:linkname Loadp //go:linkname Load64 diff --git a/src/runtime/lock_futex.go b/src/runtime/lock_futex.go index 575df7a1d5..1578984ce2 100644 --- a/src/runtime/lock_futex.go +++ b/src/runtime/lock_futex.go @@ -38,6 +38,7 @@ const ( // affect mutex's state. // We use the uintptr mutex.key and note.key as a uint32. +// //go:nosplit func key32(p *uintptr) *uint32 { return (*uint32)(unsafe.Pointer(p)) diff --git a/src/runtime/lock_sema.go b/src/runtime/lock_sema.go index 6961c2ea9b..c5e8cfe24a 100644 --- a/src/runtime/lock_sema.go +++ b/src/runtime/lock_sema.go @@ -96,8 +96,9 @@ func unlock(l *mutex) { unlockWithRank(l) } -//go:nowritebarrier // We might not be holding a p in this code. +// +//go:nowritebarrier func unlock2(l *mutex) { gp := getg() var mp *m diff --git a/src/runtime/lockrank_off.go b/src/runtime/lockrank_off.go index daa45b542d..bf046a1041 100644 --- a/src/runtime/lockrank_off.go +++ b/src/runtime/lockrank_off.go @@ -23,6 +23,7 @@ func lockWithRank(l *mutex, rank lockRank) { } // This function may be called in nosplit context and thus must be nosplit. +// //go:nosplit func acquireLockRank(rank lockRank) { } @@ -32,6 +33,7 @@ func unlockWithRank(l *mutex) { } // This function may be called in nosplit context and thus must be nosplit. +// //go:nosplit func releaseLockRank(rank lockRank) { } diff --git a/src/runtime/lockrank_on.go b/src/runtime/lockrank_on.go index 3c8c367c19..a170569d6e 100644 --- a/src/runtime/lockrank_on.go +++ b/src/runtime/lockrank_on.go @@ -82,6 +82,7 @@ func lockWithRank(l *mutex, rank lockRank) { } // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func printHeldLocks(gp *g) { if gp.m.locksHeldLen == 0 { @@ -97,6 +98,7 @@ func printHeldLocks(gp *g) { // acquireLockRank acquires a rank which is not associated with a mutex lock // // This function may be called in nosplit context and thus must be nosplit. +// //go:nosplit func acquireLockRank(rank lockRank) { gp := getg() @@ -181,6 +183,7 @@ func unlockWithRank(l *mutex) { // releaseLockRank releases a rank which is not associated with a mutex lock // // This function may be called in nosplit context and thus must be nosplit. +// //go:nosplit func releaseLockRank(rank lockRank) { gp := getg() @@ -226,6 +229,7 @@ func lockWithRankMayAcquire(l *mutex, rank lockRank) { } // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func checkLockHeld(gp *g, l *mutex) bool { for i := gp.m.locksHeldLen - 1; i >= 0; i-- { @@ -239,6 +243,7 @@ func checkLockHeld(gp *g, l *mutex) bool { // assertLockHeld throws if l is not held by the caller. // // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func assertLockHeld(l *mutex) { gp := getg() @@ -264,6 +269,7 @@ func assertLockHeld(l *mutex) { // pointer to the exact mutex is not available. // // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func assertRankHeld(r lockRank) { gp := getg() @@ -289,6 +295,7 @@ func assertRankHeld(r lockRank) { // Caller must hold worldsema. // // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func worldStopped() { if stopped := atomic.Xadd(&worldIsStopped, 1); stopped != 1 { @@ -304,6 +311,7 @@ func worldStopped() { // Caller must hold worldsema. // // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func worldStarted() { if stopped := atomic.Xadd(&worldIsStopped, -1); stopped != 0 { @@ -315,6 +323,7 @@ func worldStarted() { } // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func checkWorldStopped() bool { stopped := atomic.Load(&worldIsStopped) @@ -332,6 +341,7 @@ func checkWorldStopped() bool { // which M stopped the world. // // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func assertWorldStopped() { if checkWorldStopped() { @@ -345,6 +355,7 @@ func assertWorldStopped() { // passed lock is not held. // // nosplit to ensure it can be called in as many contexts as possible. +// //go:nosplit func assertWorldStoppedOrLockHeld(l *mutex) { if checkWorldStopped() { diff --git a/src/runtime/malloc.go b/src/runtime/malloc.go index a00878a11c..c182197782 100644 --- a/src/runtime/malloc.go +++ b/src/runtime/malloc.go @@ -1326,6 +1326,7 @@ func persistentalloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer { // Must run on system stack because stack growth can (re)invoke it. // See issue 9174. +// //go:systemstack func persistentalloc1(size, align uintptr, sysStat *sysMemStat) *notInHeap { const ( @@ -1395,6 +1396,7 @@ func persistentalloc1(size, align uintptr, sysStat *sysMemStat) *notInHeap { // inPersistentAlloc reports whether p points to memory allocated by // persistentalloc. This must be nosplit because it is called by the // cgo checker code, which is called by the write barrier code. +// //go:nosplit func inPersistentAlloc(p uintptr) bool { chunk := atomic.Loaduintptr((*uintptr)(unsafe.Pointer(&persistentChunks))) diff --git a/src/runtime/map_test.go b/src/runtime/map_test.go index 0c83dd4ddf..5c458b4a49 100644 --- a/src/runtime/map_test.go +++ b/src/runtime/map_test.go @@ -29,8 +29,9 @@ func TestHmapSize(t *testing.T) { } // negative zero is a good test because: -// 1) 0 and -0 are equal, yet have distinct representations. -// 2) 0 is represented as all zeros, -0 isn't. +// 1. 0 and -0 are equal, yet have distinct representations. +// 2. 0 is represented as all zeros, -0 isn't. +// // I'm not sure the language spec actually requires this behavior, // but it's what the current map implementation does. func TestNegativeZero(t *testing.T) { diff --git a/src/runtime/mbarrier.go b/src/runtime/mbarrier.go index a0d145ec76..c3b45415a9 100644 --- a/src/runtime/mbarrier.go +++ b/src/runtime/mbarrier.go @@ -199,6 +199,7 @@ func reflectlite_typedmemmove(typ *_type, dst, src unsafe.Pointer) { // typedmemmovepartial is like typedmemmove but assumes that // dst and src point off bytes into the value and only copies size bytes. // off must be a multiple of goarch.PtrSize. +// //go:linkname reflect_typedmemmovepartial reflect.typedmemmovepartial func reflect_typedmemmovepartial(typ *_type, dst, src unsafe.Pointer, off, size uintptr) { if writeBarrier.needed && typ.ptrdata > off && size >= goarch.PtrSize { diff --git a/src/runtime/mbitmap.go b/src/runtime/mbitmap.go index 937968807b..a3a6590d65 100644 --- a/src/runtime/mbitmap.go +++ b/src/runtime/mbitmap.go @@ -65,6 +65,7 @@ const ( ) // addb returns the byte pointer p+n. +// //go:nowritebarrier //go:nosplit func addb(p *byte, n uintptr) *byte { @@ -75,6 +76,7 @@ func addb(p *byte, n uintptr) *byte { } // subtractb returns the byte pointer p-n. +// //go:nowritebarrier //go:nosplit func subtractb(p *byte, n uintptr) *byte { @@ -85,6 +87,7 @@ func subtractb(p *byte, n uintptr) *byte { } // add1 returns the byte pointer p+1. +// //go:nowritebarrier //go:nosplit func add1(p *byte) *byte { @@ -95,9 +98,10 @@ func add1(p *byte) *byte { } // subtract1 returns the byte pointer p-1. -//go:nowritebarrier // // nosplit because it is used during write barriers and must not be preempted. +// +//go:nowritebarrier //go:nosplit func subtract1(p *byte) *byte { // Note: wrote out full expression instead of calling subtractb(p, 1) @@ -314,6 +318,7 @@ func (m *markBits) advance() { // In particular, be careful not to point past the end of an object. // // nosplit because it is used during write barriers and must not be preempted. +// //go:nosplit func heapBitsForAddr(addr uintptr) (h heapBits) { // 2 bits per word, 4 pairs per byte, and a mask is hard coded. @@ -381,6 +386,7 @@ func badPointer(s *mspan, p, refBase, refOff uintptr) { // // It is nosplit so it is safe for p to be a pointer to the current goroutine's stack. // Since p is a uintptr, it would not be adjusted if the stack were to move. +// //go:nosplit func findObject(p, refBase, refOff uintptr) (base uintptr, s *mspan, objIndex uintptr) { s = spanOf(p) @@ -418,6 +424,7 @@ func findObject(p, refBase, refOff uintptr) (base uintptr, s *mspan, objIndex ui } // verifyNotInHeapPtr reports whether converting the not-in-heap pointer into a unsafe.Pointer is ok. +// //go:linkname reflect_verifyNotInHeapPtr reflect.verifyNotInHeapPtr func reflect_verifyNotInHeapPtr(p uintptr) bool { // Conversion to a pointer is ok as long as findObject above does not call badPointer. @@ -431,6 +438,7 @@ func reflect_verifyNotInHeapPtr(p uintptr) bool { // Note that next does not modify h. The caller must record the result. // // nosplit because it is used during write barriers and must not be preempted. +// //go:nosplit func (h heapBits) next() heapBits { if h.shift < 3*heapBitsShift { @@ -477,6 +485,7 @@ func (h heapBits) nextArena() heapBits { // h.forward(1) is equivalent to h.next(), just slower. // Note that forward does not modify h. The caller must record the result. // bits returns the heap bits for the current word. +// //go:nosplit func (h heapBits) forward(n uintptr) heapBits { n += uintptr(h.shift) / heapBitsShift @@ -517,6 +526,7 @@ func (h heapBits) forwardOrBoundary(n uintptr) (heapBits, uintptr) { // described by the same bitmap byte. // // nosplit because it is used during write barriers and must not be preempted. +// //go:nosplit func (h heapBits) bits() uint32 { // The (shift & 31) eliminates a test and conditional branch @@ -534,6 +544,7 @@ func (h heapBits) morePointers() bool { // isPointer reports whether the heap bits describe a pointer word. // // nosplit because it is used during write barriers and must not be preempted. +// //go:nosplit func (h heapBits) isPointer() bool { return h.bits()&bitPointer != 0 @@ -633,6 +644,7 @@ func bulkBarrierPreWrite(dst, src, size uintptr) { // // This is used for special cases where e.g. dst was just // created and zeroed with malloc. +// //go:nosplit func bulkBarrierPreWriteSrcOnly(dst, src, size uintptr) { if (dst|src|size)&(goarch.PtrSize-1) != 0 { @@ -1951,6 +1963,7 @@ func getgcmaskcb(frame *stkframe, ctxt unsafe.Pointer) bool { // gcbits returns the GC type info for x, for testing. // The result is the bitmap entries (0 or 1), one entry per byte. +// //go:linkname reflect_gcbits reflect.gcbits func reflect_gcbits(x any) []byte { ret := getgcmask(x) diff --git a/src/runtime/mem_aix.go b/src/runtime/mem_aix.go index d6a181ad4d..21726b56ae 100644 --- a/src/runtime/mem_aix.go +++ b/src/runtime/mem_aix.go @@ -10,6 +10,7 @@ import ( // Don't split the stack as this method may be invoked without a valid G, which // prevents us from allocating more stack. +// //go:nosplit func sysAllocOS(n uintptr) unsafe.Pointer { p, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0) @@ -39,6 +40,7 @@ func sysHugePageOS(v unsafe.Pointer, n uintptr) { // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysFreeOS(v unsafe.Pointer, n uintptr) { munmap(v, n) diff --git a/src/runtime/mem_bsd.go b/src/runtime/mem_bsd.go index e83145e86b..782465ae26 100644 --- a/src/runtime/mem_bsd.go +++ b/src/runtime/mem_bsd.go @@ -12,6 +12,7 @@ import ( // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysAllocOS(n uintptr) unsafe.Pointer { v, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0) @@ -33,6 +34,7 @@ func sysHugePageOS(v unsafe.Pointer, n uintptr) { // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysFreeOS(v unsafe.Pointer, n uintptr) { munmap(v, n) diff --git a/src/runtime/mem_darwin.go b/src/runtime/mem_darwin.go index d63b5559aa..25862cf161 100644 --- a/src/runtime/mem_darwin.go +++ b/src/runtime/mem_darwin.go @@ -10,6 +10,7 @@ import ( // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysAllocOS(n uintptr) unsafe.Pointer { v, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0) @@ -37,6 +38,7 @@ func sysHugePageOS(v unsafe.Pointer, n uintptr) { // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysFreeOS(v unsafe.Pointer, n uintptr) { munmap(v, n) diff --git a/src/runtime/mem_js.go b/src/runtime/mem_js.go index c66b91eedd..e87c5f26ae 100644 --- a/src/runtime/mem_js.go +++ b/src/runtime/mem_js.go @@ -12,6 +12,7 @@ import ( // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysAllocOS(n uintptr) unsafe.Pointer { p := sysReserveOS(nil, n) @@ -30,6 +31,7 @@ func sysHugePageOS(v unsafe.Pointer, n uintptr) { // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysFreeOS(v unsafe.Pointer, n uintptr) { } diff --git a/src/runtime/mem_linux.go b/src/runtime/mem_linux.go index 980f7bb53d..1630664cff 100644 --- a/src/runtime/mem_linux.go +++ b/src/runtime/mem_linux.go @@ -16,6 +16,7 @@ const ( // Don't split the stack as this method may be invoked without a valid G, which // prevents us from allocating more stack. +// //go:nosplit func sysAllocOS(n uintptr) unsafe.Pointer { p, err := mmap(nil, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_PRIVATE, -1, 0) @@ -162,6 +163,7 @@ func sysHugePageOS(v unsafe.Pointer, n uintptr) { // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysFreeOS(v unsafe.Pointer, n uintptr) { munmap(v, n) diff --git a/src/runtime/mem_windows.go b/src/runtime/mem_windows.go index c8f039f50b..b1292fc725 100644 --- a/src/runtime/mem_windows.go +++ b/src/runtime/mem_windows.go @@ -23,6 +23,7 @@ const ( // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysAllocOS(n uintptr) unsafe.Pointer { return unsafe.Pointer(stdcall4(_VirtualAlloc, 0, n, _MEM_COMMIT|_MEM_RESERVE, _PAGE_READWRITE)) @@ -95,6 +96,7 @@ func sysHugePageOS(v unsafe.Pointer, n uintptr) { // Don't split the stack as this function may be invoked without a valid G, // which prevents us from allocating more stack. +// //go:nosplit func sysFreeOS(v unsafe.Pointer, n uintptr) { r := stdcall3(_VirtualFree, uintptr(v), 0, _MEM_RELEASE) diff --git a/src/runtime/memclr_ppc64x.s b/src/runtime/memclr_ppc64x.s index ad84ea9600..354325585d 100644 --- a/src/runtime/memclr_ppc64x.s +++ b/src/runtime/memclr_ppc64x.s @@ -52,37 +52,50 @@ byte4: BR zero512xsetup // ptr should now be 8 byte aligned under512: - MOVD R6, CTR // R6 = number of double words - SRDCC $2, R6, R7 // 32 byte chunks? - BNE zero32setup - - // Clear double words - -zero8: - MOVD R0, 0(R3) // double word - ADD $8, R3 - ADD $-8, R4 - BC 16, 0, zero8 // dec ctr, br zero8 if ctr not 0 - BR nozerolarge // handle leftovers - - // Prepare to clear 32 bytes at a time. - -zero32setup: - DCBTST (R3) // prepare data cache + SRDCC $3, R6, R7 // 64 byte chunks? XXLXOR VS32, VS32, VS32 // clear VS32 (V0) - MOVD R7, CTR // number of 32 byte chunks - MOVD $16, R8 + BEQ lt64gt8 -zero32: + // Prepare to clear 64 bytes at a time. + +zero64setup: + DCBTST (R3) // prepare data cache + MOVD R7, CTR // number of 64 byte chunks + MOVD $16, R8 + MOVD $32, R16 + MOVD $48, R17 + +zero64: STXVD2X VS32, (R3+R0) // store 16 bytes STXVD2X VS32, (R3+R8) - ADD $32, R3 - ADD $-32, R4 - BC 16, 0, zero32 // dec ctr, br zero32 if ctr not 0 - RLDCLCC $61, R4, $3, R6 // remaining doublewords + STXVD2X VS32, (R3+R16) + STXVD2X VS32, (R3+R17) + ADD $64, R3 + ADD $-64, R4 + BDNZ zero64 // dec ctr, br zero64 if ctr not 0 + SRDCC $3, R4, R6 // remaining doublewords BEQ nozerolarge - MOVD R6, CTR // set up the CTR for doublewords - BR zero8 + +lt64gt8: + CMP R4, $32 + BLT lt32gt8 + MOVD $16, R8 + STXVD2X VS32, (R3+R0) + STXVD2X VS32, (R3+R8) + ADD $-32, R4 + ADD $32, R3 +lt32gt8: + CMP R4, $16 + BLT lt16gt8 + STXVD2X VS32, (R3+R0) + ADD $16, R3 + ADD $-16, R4 +lt16gt8: + CMP R4, $8 + BLT nozerolarge + MOVD R0, 0(R3) + ADD $8, R3 + ADD $-8, R4 nozerolarge: ANDCC $7, R4, R5 // any remaining bytes @@ -94,7 +107,7 @@ zerotail: zerotailloop: MOVB R0, 0(R3) // clear single bytes ADD $1, R3 - BC 16, 0, zerotailloop // dec ctr, br zerotailloop if ctr not 0 + BDNZ zerotailloop // dec ctr, br zerotailloop if ctr not 0 RET zero512xsetup: // 512 chunk with extra needed @@ -119,7 +132,7 @@ zero512preloop: // clear up to 128 alignment STXVD2X VS32, (R3+R0) // clear 16 bytes ADD $16, R3 // update ptr ADD $-16, R4 // dec count - BC 16, 0, zero512preloop + BDNZ zero512preloop zero512setup: // setup for dcbz loop CMP R4, $512 // check if at least 512 @@ -129,6 +142,7 @@ zero512setup: // setup for dcbz loop MOVD $128, R9 // index regs for 128 bytes MOVD $256, R10 MOVD $384, R11 + PCALIGN $32 zero512: DCBZ (R3+R0) // clear first chunk @@ -136,8 +150,8 @@ zero512: DCBZ (R3+R10) // clear third chunk DCBZ (R3+R11) // clear fourth chunk ADD $512, R3 - ADD $-512, R4 - BC 16, 0, zero512 + BDNZ zero512 + ANDCC $511, R4 remain: CMP R4, $128 // check if 128 byte chunks left @@ -150,16 +164,11 @@ remain: smaller: ANDCC $127, R4, R7 // find leftovers BEQ done - CMP R7, $64 // more than 64, do 32 at a time - BLT zero8setup // less than 64, do 8 at a time - SRD $5, R7, R7 // set up counter for 32 - BR zero32setup - -zero8setup: - SRDCC $3, R7, R7 // less than 8 bytes - BEQ nozerolarge - MOVD R7, CTR - BR zero8 + CMP R7, $64 // more than 64, do 64 at a time + XXLXOR VS32, VS32, VS32 + BLT lt64gt8 // less than 64 + SRD $6, R7, R7 // set up counter for 64 + BR zero64setup done: RET diff --git a/src/runtime/memclr_riscv64.s b/src/runtime/memclr_riscv64.s index 54ddaa4560..f0e517a547 100644 --- a/src/runtime/memclr_riscv64.s +++ b/src/runtime/memclr_riscv64.s @@ -7,40 +7,42 @@ // See memclrNoHeapPointers Go doc for important implementation constraints. // void runtime·memclrNoHeapPointers(void*, uintptr) -TEXT runtime·memclrNoHeapPointers(SB),NOSPLIT,$0-16 - MOV ptr+0(FP), T1 - MOV n+8(FP), T2 - ADD T1, T2, T4 +TEXT runtime·memclrNoHeapPointers(SB),NOSPLIT,$0-16 +#ifndef GOEXPERIMENT_regabiargs + MOV ptr+0(FP), A0 + MOV n+8(FP), A1 +#endif + ADD A0, A1, T4 // If less than eight bytes, do one byte at a time. - SLTU $8, T2, T3 + SLTU $8, A1, T3 BNE T3, ZERO, outcheck // Do one byte at a time until eight-aligned. JMP aligncheck align: - MOVB ZERO, (T1) - ADD $1, T1 + MOVB ZERO, (A0) + ADD $1, A0 aligncheck: - AND $7, T1, T3 + AND $7, A0, T3 BNE T3, ZERO, align // Do eight bytes at a time as long as there is room. ADD $-7, T4, T5 JMP wordscheck words: - MOV ZERO, (T1) - ADD $8, T1 + MOV ZERO, (A0) + ADD $8, A0 wordscheck: - SLTU T5, T1, T3 + SLTU T5, A0, T3 BNE T3, ZERO, words JMP outcheck out: - MOVB ZERO, (T1) - ADD $1, T1 + MOVB ZERO, (A0) + ADD $1, A0 outcheck: - BNE T1, T4, out + BNE A0, T4, out done: RET diff --git a/src/runtime/memmove_amd64.s b/src/runtime/memmove_amd64.s index eeb5033fd9..018bb0b19d 100644 --- a/src/runtime/memmove_amd64.s +++ b/src/runtime/memmove_amd64.s @@ -418,9 +418,9 @@ gobble_mem_fwd_loop: PREFETCHNTA 0x1C0(SI) PREFETCHNTA 0x280(SI) // Prefetch values were chosen empirically. - // Approach for prefetch usage as in 7.6.6 of [1] + // Approach for prefetch usage as in 9.5.6 of [1] // [1] 64-ia-32-architectures-optimization-manual.pdf - // https://www.intel.ru/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf + // https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf VMOVDQU (SI), Y0 VMOVDQU 0x20(SI), Y1 VMOVDQU 0x40(SI), Y2 diff --git a/src/runtime/memmove_ppc64x.s b/src/runtime/memmove_ppc64x.s index 25101a28c7..5fa51c0a4c 100644 --- a/src/runtime/memmove_ppc64x.s +++ b/src/runtime/memmove_ppc64x.s @@ -24,8 +24,12 @@ #define IDX16 R8 // temp used for copies, etc. #define TMP R9 -// number of 32 byte chunks +// number of 64 byte chunks #define QWORDS R10 +// index values +#define IDX32 R14 +#define IDX48 R15 +#define OCTWORDS R16 TEXT runtime·memmove(SB), NOSPLIT|NOFRAME, $0-24 // R3 = TGT = to @@ -52,28 +56,46 @@ check: // Copying forward if no overlap. BC 12, 6, checkbytes // BEQ CR1, checkbytes - SRDCC $2, DWORDS, QWORDS // 32 byte chunks? - BEQ lt32gt8 // < 32 bytes + SRDCC $3, DWORDS, OCTWORDS // 64 byte chunks? + MOVD $16, IDX16 + BEQ lt64gt8 // < 64 bytes - // Prepare for moves of 32 bytes at a time. + // Prepare for moves of 64 bytes at a time. -forward32setup: +forward64setup: DCBTST (TGT) // prepare data cache DCBT (SRC) - MOVD QWORDS, CTR // Number of 32 byte chunks - MOVD $16, IDX16 // 16 for index + MOVD OCTWORDS, CTR // Number of 64 byte chunks + MOVD $32, IDX32 + MOVD $48, IDX48 + PCALIGN $32 -forward32: - LXVD2X (R0)(SRC), VS32 // load 16 bytes - LXVD2X (IDX16)(SRC), VS33 // load 16 bytes - ADD $32, SRC - STXVD2X VS32, (R0)(TGT) // store 16 bytes +forward64: + LXVD2X (R0)(SRC), VS32 // load 64 bytes + LXVD2X (IDX16)(SRC), VS33 + LXVD2X (IDX32)(SRC), VS34 + LXVD2X (IDX48)(SRC), VS35 + ADD $64, SRC + STXVD2X VS32, (R0)(TGT) // store 64 bytes STXVD2X VS33, (IDX16)(TGT) - ADD $32,TGT // bump up for next set - BC 16, 0, forward32 // continue - ANDCC $3, DWORDS // remaining doublewords + STXVD2X VS34, (IDX32)(TGT) + STXVD2X VS35, (IDX48)(TGT) + ADD $64,TGT // bump up for next set + BC 16, 0, forward64 // continue + ANDCC $7, DWORDS // remaining doublewords BEQ checkbytes // only bytes remain +lt64gt8: + CMP DWORDS, $4 + BLT lt32gt8 + LXVD2X (R0)(SRC), VS32 + LXVD2X (IDX16)(SRC), VS33 + ADD $-4, DWORDS + STXVD2X VS32, (R0)(TGT) + STXVD2X VS33, (IDX16)(TGT) + ADD $32, SRC + ADD $32, TGT + lt32gt8: // At this point >= 8 and < 32 // Move 16 bytes if possible @@ -134,7 +156,7 @@ backwardtailloop: SUB $1,SRC MOVBZ TMP, -1(TGT) SUB $1,TGT - BC 16, 0, backwardtailloop // bndz + BDNZ backwardtailloop nobackwardtail: BC 4, 5, LR // blelr cr1, return if DWORDS == 0 @@ -169,6 +191,6 @@ backward32loop: LXVD2X (IDX16)(SRC), VS33 STXVD2X VS32, (R0)(TGT) // store 16x2 bytes STXVD2X VS33, (IDX16)(TGT) - BC 16, 0, backward32loop // bndz + BDNZ backward32loop BC 12, 2, LR // beqlr, return if DWORDS == 0 BR backward24 diff --git a/src/runtime/memmove_riscv64.s b/src/runtime/memmove_riscv64.s index 5dec8d0a33..538aee3642 100644 --- a/src/runtime/memmove_riscv64.s +++ b/src/runtime/memmove_riscv64.s @@ -7,59 +7,61 @@ // See memmove Go doc for important implementation constraints. // void runtime·memmove(void*, void*, uintptr) -TEXT runtime·memmove(SB),NOSPLIT,$-0-24 - MOV to+0(FP), T0 - MOV from+8(FP), T1 - MOV n+16(FP), T2 - ADD T1, T2, T5 +TEXT runtime·memmove(SB),NOSPLIT,$-0-24 +#ifndef GOEXPERIMENT_regabiargs + MOV to+0(FP), A0 + MOV from+8(FP), A1 + MOV n+16(FP), A2 +#endif + ADD A1, A2, T5 // If the destination is ahead of the source, start at the end of the // buffer and go backward. - BLTU T1, T0, b + BLTU A1, A0, b // If less than eight bytes, do one byte at a time. - SLTU $8, T2, T3 + SLTU $8, A2, T3 BNE T3, ZERO, f_outcheck // Do one byte at a time until from is eight-aligned. JMP f_aligncheck f_align: - MOVB (T1), T3 - MOVB T3, (T0) - ADD $1, T0 - ADD $1, T1 + MOVB (A1), T3 + MOVB T3, (A0) + ADD $1, A0 + ADD $1, A1 f_aligncheck: - AND $7, T1, T3 + AND $7, A1, T3 BNE T3, ZERO, f_align // Do eight bytes at a time as long as there is room. ADD $-7, T5, T6 JMP f_wordscheck f_words: - MOV (T1), T3 - MOV T3, (T0) - ADD $8, T0 - ADD $8, T1 + MOV (A1), T3 + MOV T3, (A0) + ADD $8, A0 + ADD $8, A1 f_wordscheck: - SLTU T6, T1, T3 + SLTU T6, A1, T3 BNE T3, ZERO, f_words // Finish off the remaining partial word. JMP f_outcheck f_out: - MOVB (T1), T3 - MOVB T3, (T0) - ADD $1, T0 - ADD $1, T1 + MOVB (A1), T3 + MOVB T3, (A0) + ADD $1, A0 + ADD $1, A1 f_outcheck: - BNE T1, T5, f_out + BNE A1, T5, f_out RET b: - ADD T0, T2, T4 + ADD A0, A2, T4 // If less than eight bytes, do one byte at a time. - SLTU $8, T2, T3 + SLTU $8, A2, T3 BNE T3, ZERO, b_outcheck // Do one byte at a time until from+n is eight-aligned. @@ -74,7 +76,7 @@ b_aligncheck: BNE T3, ZERO, b_align // Do eight bytes at a time as long as there is room. - ADD $7, T1, T6 + ADD $7, A1, T6 JMP b_wordscheck b_words: ADD $-8, T4 @@ -93,6 +95,6 @@ b_out: MOVB (T5), T3 MOVB T3, (T4) b_outcheck: - BNE T5, T1, b_out + BNE T5, A1, b_out RET diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 91ef03072d..63bea8c448 100644 --- a/src/runtime/metrics/doc.go +++ b/src/runtime/metrics/doc.go @@ -11,7 +11,7 @@ The set of metrics defined by this package may evolve as the runtime itself evolves, and also enables variation across Go implementations, whose relevant metric sets may not intersect. -Interface +# Interface Metrics are designated by a string key, rather than, for example, a field name in a struct. The full list of supported metrics is always available in the slice of @@ -30,7 +30,7 @@ In the interest of not breaking users of this package, the "kind" for a given me is guaranteed not to change. If it must change, then a new metric will be introduced with a new key and a new "kind." -Metric key format +# Metric key format As mentioned earlier, metric keys are strings. Their format is simple and well-defined, designed to be both human and machine readable. It is split into two components, @@ -41,13 +41,13 @@ did also, and a new key should be introduced. For more details on the precise definition of the metric key's path and unit formats, see the documentation of the Name field of the Description struct. -A note about floats +# A note about floats This package supports metrics whose values have a floating-point representation. In order to improve ease-of-use, this package promises to never produce the following classes of floating-point values: NaN, infinity. -Supported metrics +# Supported metrics Below is the full list of supported metrics, ordered lexicographically. diff --git a/src/runtime/mfinal.go b/src/runtime/mfinal.go index 10623e4d67..bf537b417c 100644 --- a/src/runtime/mfinal.go +++ b/src/runtime/mfinal.go @@ -439,6 +439,7 @@ okarg: } // Mark KeepAlive as noinline so that it is easily detectable as an intrinsic. +// //go:noinline // KeepAlive marks its argument as currently reachable. @@ -446,16 +447,17 @@ okarg: // before the point in the program where KeepAlive is called. // // A very simplified example showing where KeepAlive is required: -// type File struct { d int } -// d, err := syscall.Open("/file/path", syscall.O_RDONLY, 0) -// // ... do something if err != nil ... -// p := &File{d} -// runtime.SetFinalizer(p, func(p *File) { syscall.Close(p.d) }) -// var buf [10]byte -// n, err := syscall.Read(p.d, buf[:]) -// // Ensure p is not finalized until Read returns. -// runtime.KeepAlive(p) -// // No more uses of p after this point. +// +// type File struct { d int } +// d, err := syscall.Open("/file/path", syscall.O_RDONLY, 0) +// // ... do something if err != nil ... +// p := &File{d} +// runtime.SetFinalizer(p, func(p *File) { syscall.Close(p.d) }) +// var buf [10]byte +// n, err := syscall.Read(p.d, buf[:]) +// // Ensure p is not finalized until Read returns. +// runtime.KeepAlive(p) +// // No more uses of p after this point. // // Without the KeepAlive call, the finalizer could run at the start of // syscall.Read, closing the file descriptor before syscall.Read makes diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go index ba679e0af5..9f17e47488 100644 --- a/src/runtime/mgc.go +++ b/src/runtime/mgc.go @@ -760,7 +760,7 @@ var gcMarkDoneFlushed uint32 // This should be called when all local mark work has been drained and // there are no remaining workers. Specifically, when // -// work.nwait == work.nproc && !gcMarkWorkAvailable(p) +// work.nwait == work.nproc && !gcMarkWorkAvailable(p) // // The calling context must be preemptible. // diff --git a/src/runtime/mgcmark.go b/src/runtime/mgcmark.go index 3e1a0b560a..cd0ec007f3 100644 --- a/src/runtime/mgcmark.go +++ b/src/runtime/mgcmark.go @@ -888,6 +888,7 @@ func scanstack(gp *g, gcw *gcWork) int64 { } // Scan a stack frame: local variables and function arguments/results. +// //go:nowritebarrier func scanframeworker(frame *stkframe, state *stackScanState, gcw *gcWork) { if _DebugGC > 1 && frame.continpc != 0 { @@ -1185,6 +1186,7 @@ func gcDrainN(gcw *gcWork, scanWork int64) int64 { // gcw.bytesMarked or gcw.heapScanWork. // // If stk != nil, possible stack pointers are also reported to stk.putPtr. +// //go:nowritebarrier func scanblock(b0, n0 uintptr, ptrmask *uint8, gcw *gcWork, stk *stackScanState) { // Use local copies of original parameters, so that a stack trace @@ -1413,6 +1415,7 @@ func scanConservative(b, n uintptr, ptrmask *uint8, gcw *gcWork, state *stackSca // Shade the object if it isn't already. // The object is not nil and known to be in the heap. // Preemption must be disabled. +// //go:nowritebarrier func shade(b uintptr) { if obj, span, objIndex := findObject(b, 0, 0); obj != 0 { diff --git a/src/runtime/mgcstack.go b/src/runtime/mgcstack.go index 49dc54e165..472c61a491 100644 --- a/src/runtime/mgcstack.go +++ b/src/runtime/mgcstack.go @@ -158,6 +158,7 @@ type stackObject struct { } // obj.r = r, but with no write barrier. +// //go:nowritebarrier func (obj *stackObject) setRecord(r *stackObjectRecord) { // Types of stack objects are always in read-only memory, not the heap. diff --git a/src/runtime/mgcsweep.go b/src/runtime/mgcsweep.go index a46f4ec2c6..d0b81fd3df 100644 --- a/src/runtime/mgcsweep.go +++ b/src/runtime/mgcsweep.go @@ -387,7 +387,7 @@ func sweepone() uintptr { // concurrent sweeps running, but we're at least very // close to done sweeping. - // Move the scavenge gen forward (signalling + // Move the scavenge gen forward (signaling // that there's new work to do) and wake the scavenger. // // The scavenger is signaled by the last sweeper because once @@ -424,6 +424,7 @@ func isSweepDone() bool { } // Returns only when span s has been swept. +// //go:nowritebarrier func (s *mspan) ensureSwept() { // Caller must disable preemption. diff --git a/src/runtime/mgcwork.go b/src/runtime/mgcwork.go index 56d0b1cd62..424de2fcca 100644 --- a/src/runtime/mgcwork.go +++ b/src/runtime/mgcwork.go @@ -44,9 +44,9 @@ func init() { // // A gcWork can be used on the stack as follows: // -// (preemption must be disabled) -// gcw := &getg().m.p.ptr().gcw -// .. call gcw.put() to produce and gcw.tryGet() to consume .. +// (preemption must be disabled) +// gcw := &getg().m.p.ptr().gcw +// .. call gcw.put() to produce and gcw.tryGet() to consume .. // // It's important that any use of gcWork during the mark phase prevent // the garbage collector from transitioning to mark termination since @@ -107,6 +107,7 @@ func (w *gcWork) init() { // put enqueues a pointer for the garbage collector to trace. // obj must point to the beginning of a heap object or an oblet. +// //go:nowritebarrierrec func (w *gcWork) put(obj uintptr) { flushed := false @@ -145,6 +146,7 @@ func (w *gcWork) put(obj uintptr) { // putFast does a put and reports whether it can be done quickly // otherwise it returns false and the caller needs to call put. +// //go:nowritebarrierrec func (w *gcWork) putFast(obj uintptr) bool { wbuf := w.wbuf1 @@ -196,6 +198,7 @@ func (w *gcWork) putBatch(obj []uintptr) { // If there are no pointers remaining in this gcWork or in the global // queue, tryGet returns 0. Note that there may still be pointers in // other gcWork instances or other caches. +// //go:nowritebarrierrec func (w *gcWork) tryGet() uintptr { wbuf := w.wbuf1 @@ -225,6 +228,7 @@ func (w *gcWork) tryGet() uintptr { // tryGetFast dequeues a pointer for the garbage collector to trace // if one is readily available. Otherwise it returns 0 and // the caller is expected to call tryGet(). +// //go:nowritebarrierrec func (w *gcWork) tryGetFast() uintptr { wbuf := w.wbuf1 @@ -278,6 +282,7 @@ func (w *gcWork) dispose() { // balance moves some work that's cached in this gcWork back on the // global queue. +// //go:nowritebarrierrec func (w *gcWork) balance() { if w.wbuf1 == nil { @@ -300,6 +305,7 @@ func (w *gcWork) balance() { } // empty reports whether w has no mark work available. +// //go:nowritebarrierrec func (w *gcWork) empty() bool { return w.wbuf1 == nil || (w.wbuf1.nobj == 0 && w.wbuf2.nobj == 0) @@ -340,6 +346,7 @@ func (b *workbuf) checkempty() { // getempty pops an empty work buffer off the work.empty list, // allocating new buffers if none are available. +// //go:nowritebarrier func getempty() *workbuf { var b *workbuf @@ -395,6 +402,7 @@ func getempty() *workbuf { // putempty puts a workbuf onto the work.empty list. // Upon entry this goroutine owns b. The lfstack.push relinquishes ownership. +// //go:nowritebarrier func putempty(b *workbuf) { b.checkempty() @@ -404,6 +412,7 @@ func putempty(b *workbuf) { // putfull puts the workbuf on the work.full list for the GC. // putfull accepts partially full buffers so the GC can avoid competing // with the mutators for ownership of partially full buffers. +// //go:nowritebarrier func putfull(b *workbuf) { b.checknonempty() @@ -412,6 +421,7 @@ func putfull(b *workbuf) { // trygetfull tries to get a full or partially empty workbuffer. // If one is not immediately available return nil +// //go:nowritebarrier func trygetfull() *workbuf { b := (*workbuf)(work.full.pop()) diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index d2a63d0938..d99363d991 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -319,16 +319,16 @@ type arenaHint struct { // mSpanManual, or mSpanFree. Transitions between these states are // constrained as follows: // -// * A span may transition from free to in-use or manual during any GC -// phase. +// - A span may transition from free to in-use or manual during any GC +// phase. // -// * During sweeping (gcphase == _GCoff), a span may transition from -// in-use to free (as a result of sweeping) or manual to free (as a -// result of stacks being freed). +// - During sweeping (gcphase == _GCoff), a span may transition from +// in-use to free (as a result of sweeping) or manual to free (as a +// result of stacks being freed). // -// * During GC (gcphase != _GCoff), a span *must not* transition from -// manual or in-use to free. Because concurrent GC may read a pointer -// and then look up its span, the span state must be monotonic. +// - During GC (gcphase != _GCoff), a span *must not* transition from +// manual or in-use to free. Because concurrent GC may read a pointer +// and then look up its span, the span state must be monotonic. // // Setting mspan.state to mSpanInUse or mSpanManual must be done // atomically and only after all other span fields are valid. @@ -589,6 +589,7 @@ func (i arenaIdx) l2() uint { // inheap reports whether b is a pointer into a (potentially dead) heap object. // It returns false for pointers into mSpanManual spans. // Non-preemptible because it is used by write barriers. +// //go:nowritebarrier //go:nosplit func inheap(b uintptr) bool { diff --git a/src/runtime/mpagealloc_64bit.go b/src/runtime/mpagealloc_64bit.go index 1bacfbe0fa..76b54baa55 100644 --- a/src/runtime/mpagealloc_64bit.go +++ b/src/runtime/mpagealloc_64bit.go @@ -41,7 +41,8 @@ var levelBits = [summaryLevels]uint{ // // With levelShift, one can compute the index of the summary at level l related to a // pointer p by doing: -// p >> levelShift[l] +// +// p >> levelShift[l] var levelShift = [summaryLevels]uint{ heapAddrBits - summaryL0Bits, heapAddrBits - summaryL0Bits - 1*summaryLevelBits, diff --git a/src/runtime/msan.go b/src/runtime/msan.go index 902a1e9e74..c485216583 100644 --- a/src/runtime/msan.go +++ b/src/runtime/msan.go @@ -54,6 +54,7 @@ func msanfree(addr unsafe.Pointer, sz uintptr) func msanmove(dst, src unsafe.Pointer, sz uintptr) // These are called from msan_GOARCH.s +// //go:cgo_import_static __msan_read_go //go:cgo_import_static __msan_write_go //go:cgo_import_static __msan_malloc_go diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go index e5c3471ca3..e8b42fbbbe 100644 --- a/src/runtime/mstats.go +++ b/src/runtime/mstats.go @@ -550,6 +550,7 @@ func readGCStats(pauses *[]uint64) { // readGCStats_m must be called on the system stack because it acquires the heap // lock. See mheap for details. +// //go:systemstack func readGCStats_m(pauses *[]uint64) { p := *pauses @@ -622,6 +623,7 @@ type sysMemStat uint64 // load atomically reads the value of the stat. // // Must be nosplit as it is called in runtime initialization, e.g. newosproc0. +// //go:nosplit func (s *sysMemStat) load() uint64 { return atomic.Load64((*uint64)(s)) @@ -630,6 +632,7 @@ func (s *sysMemStat) load() uint64 { // add atomically adds the sysMemStat by n. // // Must be nosplit as it is called in runtime initialization, e.g. newosproc0. +// //go:nosplit func (s *sysMemStat) add(n int64) { if s == nil { diff --git a/src/runtime/mwbbuf.go b/src/runtime/mwbbuf.go index 78d9382620..39ce0b46a9 100644 --- a/src/runtime/mwbbuf.go +++ b/src/runtime/mwbbuf.go @@ -116,11 +116,11 @@ func (b *wbBuf) empty() bool { // putFast adds old and new to the write barrier buffer and returns // false if a flush is necessary. Callers should use this as: // -// buf := &getg().m.p.ptr().wbBuf -// if !buf.putFast(old, new) { -// wbBufFlush(...) -// } -// ... actual memory write ... +// buf := &getg().m.p.ptr().wbBuf +// if !buf.putFast(old, new) { +// wbBufFlush(...) +// } +// ... actual memory write ... // // The arguments to wbBufFlush depend on whether the caller is doing // its own cgo pointer checks. If it is, then this can be diff --git a/src/runtime/netpoll.go b/src/runtime/netpoll.go index 864148b715..ac6bc89530 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -47,16 +47,17 @@ const ( // pollDesc contains 2 binary semaphores, rg and wg, to park reader and writer // goroutines respectively. The semaphore can be in the following states: -// pdReady - io readiness notification is pending; -// a goroutine consumes the notification by changing the state to nil. -// pdWait - a goroutine prepares to park on the semaphore, but not yet parked; -// the goroutine commits to park by changing the state to G pointer, -// or, alternatively, concurrent io notification changes the state to pdReady, -// or, alternatively, concurrent timeout/close changes the state to nil. -// G pointer - the goroutine is blocked on the semaphore; -// io notification or timeout/close changes the state to pdReady or nil respectively -// and unparks the goroutine. -// nil - none of the above. +// +// pdReady - io readiness notification is pending; +// a goroutine consumes the notification by changing the state to nil. +// pdWait - a goroutine prepares to park on the semaphore, but not yet parked; +// the goroutine commits to park by changing the state to G pointer, +// or, alternatively, concurrent io notification changes the state to pdReady, +// or, alternatively, concurrent timeout/close changes the state to nil. +// G pointer - the goroutine is blocked on the semaphore; +// io notification or timeout/close changes the state to pdReady or nil respectively +// and unparks the goroutine. +// nil - none of the above. const ( pdReady uintptr = 1 pdWait uintptr = 2 @@ -271,6 +272,7 @@ func (c *pollCache) free(pd *pollDesc) { // poll_runtime_pollReset, which is internal/poll.runtime_pollReset, // prepares a descriptor for polling in mode, which is 'r' or 'w'. // This returns an error code; the codes are defined above. +// //go:linkname poll_runtime_pollReset internal/poll.runtime_pollReset func poll_runtime_pollReset(pd *pollDesc, mode int) int { errcode := netpollcheckerr(pd, int32(mode)) @@ -289,6 +291,7 @@ func poll_runtime_pollReset(pd *pollDesc, mode int) int { // waits for a descriptor to be ready for reading or writing, // according to mode, which is 'r' or 'w'. // This returns an error code; the codes are defined above. +// //go:linkname poll_runtime_pollWait internal/poll.runtime_pollWait func poll_runtime_pollWait(pd *pollDesc, mode int) int { errcode := netpollcheckerr(pd, int32(mode)) @@ -438,6 +441,7 @@ func poll_runtime_pollUnblock(pd *pollDesc) { // whether the fd is ready for reading or writing or both. // // This may run while the world is stopped, so write barriers are not allowed. +// //go:nowritebarrier func netpollready(toRun *gList, pd *pollDesc, mode int32) { var rg, wg *g diff --git a/src/runtime/netpoll_aix.go b/src/runtime/netpoll_aix.go index 90950af444..22cc513881 100644 --- a/src/runtime/netpoll_aix.go +++ b/src/runtime/netpoll_aix.go @@ -146,6 +146,7 @@ func netpollBreak() { // delay < 0: blocks indefinitely // delay == 0: does not block, just polls // delay > 0: block for up to that many nanoseconds +// //go:nowritebarrierrec func netpoll(delay int64) gList { var timeout uintptr diff --git a/src/runtime/norace_linux_test.go b/src/runtime/norace_linux_test.go index b188a2e88b..3521b24655 100644 --- a/src/runtime/norace_linux_test.go +++ b/src/runtime/norace_linux_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // The file contains tests that cannot run under race detector for some reason. +// //go:build !race package runtime_test diff --git a/src/runtime/norace_test.go b/src/runtime/norace_test.go index d49f2ec0df..3b5eca5341 100644 --- a/src/runtime/norace_test.go +++ b/src/runtime/norace_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // The file contains tests that cannot run under race detector for some reason. +// //go:build !race package runtime_test diff --git a/src/runtime/os2_aix.go b/src/runtime/os2_aix.go index 4d77f0de6d..9ad1caa816 100644 --- a/src/runtime/os2_aix.go +++ b/src/runtime/os2_aix.go @@ -452,6 +452,7 @@ func pipe() (r, w int32, errno int32) { // assembly routine; the higher bits (if required), should be provided // by the assembly routine as 0. // The err result is an OS error code such as ENOMEM. +// //go:nosplit func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) { r, err0 := syscall6(&libc_mmap, uintptr(addr), uintptr(n), uintptr(prot), uintptr(flags), uintptr(fd), uintptr(off)) diff --git a/src/runtime/os3_solaris.go b/src/runtime/os3_solaris.go index f465a3aa3f..8c85b71532 100644 --- a/src/runtime/os3_solaris.go +++ b/src/runtime/os3_solaris.go @@ -141,6 +141,7 @@ func osinit() { func tstart_sysvicall(newm *m) uint32 // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { var ( @@ -267,6 +268,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp diff --git a/src/runtime/os_aix.go b/src/runtime/os_aix.go index 292ff94795..15e4929779 100644 --- a/src/runtime/os_aix.go +++ b/src/runtime/os_aix.go @@ -150,6 +150,7 @@ var failthreadcreate = []byte("runtime: failed to create new OS thread\n") // Called to do synchronous initialization of Go code built with // -buildmode=c-archive or -buildmode=c-shared. // None of the Go runtime is initialized. +// //go:nosplit //go:nowritebarrierrec func libpreinit() { @@ -296,6 +297,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index 9065b76375..8562d7d906 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -195,6 +195,7 @@ func goenvs() { } // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrierrec func newosproc(mp *m) { stk := unsafe.Pointer(mp.g0.stack.hi) @@ -292,6 +293,7 @@ var failthreadcreate = []byte("runtime: failed to create new OS thread\n") // Called to do synchronous initialization of Go code built with // -buildmode=c-archive or -buildmode=c-shared. // None of the Go runtime is initialized. +// //go:nosplit //go:nowritebarrierrec func libpreinit() { @@ -324,6 +326,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { // iOS does not support alternate signal stack. @@ -410,6 +413,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp diff --git a/src/runtime/os_dragonfly.go b/src/runtime/os_dragonfly.go index a56706b415..83478143b9 100644 --- a/src/runtime/os_dragonfly.go +++ b/src/runtime/os_dragonfly.go @@ -142,6 +142,7 @@ func futexwakeup(addr *uint32, cnt uint32) { func lwp_start(uintptr) // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { stk := unsafe.Pointer(mp.g0.stack.hi) @@ -201,6 +202,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { unminitSignals() @@ -247,6 +249,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { s.ss_sp = sp diff --git a/src/runtime/os_freebsd.go b/src/runtime/os_freebsd.go index e4d15474d8..23efd1a46e 100644 --- a/src/runtime/os_freebsd.go +++ b/src/runtime/os_freebsd.go @@ -192,6 +192,7 @@ func futexwakeup(addr *uint32, cnt uint32) { func thr_start() // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { stk := unsafe.Pointer(mp.g0.stack.hi) @@ -221,6 +222,7 @@ func newosproc(mp *m) { } // Version of newosproc that doesn't require a valid G. +// //go:nosplit func newosproc0(stacksize uintptr, fn unsafe.Pointer) { stack := sysAlloc(stacksize, &memstats.stacks_sys) @@ -261,6 +263,7 @@ var failthreadcreate = []byte("runtime: failed to create new OS thread\n") // Called to do synchronous initialization of Go code built with // -buildmode=c-archive or -buildmode=c-shared. // None of the Go runtime is initialized. +// //go:nosplit //go:nowritebarrierrec func libpreinit() { @@ -318,6 +321,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { unminitSignals() @@ -359,6 +363,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { s.ss_sp = sp @@ -431,6 +436,7 @@ func sysauxv(auxv []uintptr) { } // sysSigaction calls the sigaction system call. +// //go:nosplit func sysSigaction(sig uint32, new, old *sigactiont) { // Use system stack to avoid split stack overflow on amd64 @@ -442,6 +448,7 @@ func sysSigaction(sig uint32, new, old *sigactiont) { } // asmSigaction is implemented in assembly. +// //go:noescape func asmSigaction(sig uintptr, new, old *sigactiont) int32 diff --git a/src/runtime/os_js.go b/src/runtime/os_js.go index 9ed916705b..7ec1210b73 100644 --- a/src/runtime/os_js.go +++ b/src/runtime/os_js.go @@ -126,6 +126,7 @@ func initsig(preinit bool) { } // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { panic("newosproc: not implemented") diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index efb54ff20e..a6e7a33191 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -52,9 +52,12 @@ const ( ) // Atomically, +// // if(*addr == val) sleep +// // Might be woken up spuriously; that's allowed. // Don't sleep longer than ns; ns < 0 means forever. +// //go:nosplit func futexsleep(addr *uint32, val uint32, ns int64) { // Some Linux kernels have a bug where futex of @@ -73,6 +76,7 @@ func futexsleep(addr *uint32, val uint32, ns int64) { } // If any procs are sleeping on addr, wake up at most cnt. +// //go:nosplit func futexwakeup(addr *uint32, cnt uint32) { ret := futex(unsafe.Pointer(addr), _FUTEX_WAKE_PRIVATE, cnt, nil, nil, 0) @@ -157,6 +161,7 @@ const ( func clone(flags int32, stk, mp, gp, fn unsafe.Pointer) int32 // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { stk := unsafe.Pointer(mp.g0.stack.hi) @@ -184,6 +189,7 @@ func newosproc(mp *m) { } // Version of newosproc that doesn't require a valid G. +// //go:nosplit func newosproc0(stacksize uintptr, fn unsafe.Pointer) { stack := sysAlloc(stacksize, &memstats.stacks_sys) @@ -365,6 +371,7 @@ func goenvs() { // Called to do synchronous initialization of Go code built with // -buildmode=c-archive or -buildmode=c-shared. // None of the Go runtime is initialized. +// //go:nosplit //go:nowritebarrierrec func libpreinit() { @@ -392,6 +399,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { unminitSignals() @@ -497,6 +505,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { *(*uintptr)(unsafe.Pointer(&s.ss_sp)) = sp @@ -507,6 +516,7 @@ func (c *sigctxt) fixsigcode(sig uint32) { } // sysSigaction calls the rt_sigaction system call. +// //go:nosplit func sysSigaction(sig uint32, new, old *sigactiont) { if rt_sigaction(uintptr(sig), new, old, unsafe.Sizeof(sigactiont{}.sa_mask)) != 0 { @@ -531,6 +541,7 @@ func sysSigaction(sig uint32, new, old *sigactiont) { } // rt_sigaction is implemented in assembly. +// //go:noescape func rt_sigaction(sig uintptr, new, old *sigactiont, size uintptr) int32 diff --git a/src/runtime/os_netbsd.go b/src/runtime/os_netbsd.go index 88a4a8b90e..3cbace38f9 100644 --- a/src/runtime/os_netbsd.go +++ b/src/runtime/os_netbsd.go @@ -201,6 +201,7 @@ func semawakeup(mp *m) { } // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { stk := unsafe.Pointer(mp.g0.stack.hi) @@ -248,6 +249,7 @@ func netbsdMstart() // baroque to remove a signal stack here only to add one in minit, but // it's a simple change that keeps NetBSD working like other OS's. // At this point all signals are blocked, so there is no race. +// //go:nosplit func netbsdMstart0() { st := stackt{ss_flags: _SS_DISABLE} @@ -304,6 +306,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { unminitSignals() @@ -350,6 +353,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { s.ss_sp = sp diff --git a/src/runtime/os_openbsd.go b/src/runtime/os_openbsd.go index 1a00b890db..2383dc8428 100644 --- a/src/runtime/os_openbsd.go +++ b/src/runtime/os_openbsd.go @@ -168,6 +168,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { unminitSignals() @@ -214,6 +215,7 @@ func getsig(i uint32) uintptr { } // setSignaltstackSP sets the ss_sp field of a stackt. +// //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { s.ss_sp = sp diff --git a/src/runtime/os_openbsd_libc.go b/src/runtime/os_openbsd_libc.go index ff21eccb4b..4ad2a061bd 100644 --- a/src/runtime/os_openbsd_libc.go +++ b/src/runtime/os_openbsd_libc.go @@ -17,6 +17,7 @@ var failThreadCreate = []byte("runtime: failed to create new OS thread\n") func mstart_stub() // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrierrec func newosproc(mp *m) { if false { diff --git a/src/runtime/os_openbsd_syscall.go b/src/runtime/os_openbsd_syscall.go index 8128c20453..9d67a7ebbd 100644 --- a/src/runtime/os_openbsd_syscall.go +++ b/src/runtime/os_openbsd_syscall.go @@ -16,6 +16,7 @@ import ( func tfork(param *tforkt, psize uintptr, mm *m, gg *g, fn uintptr) int32 // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { stk := unsafe.Pointer(mp.g0.stack.hi) diff --git a/src/runtime/os_openbsd_syscall2.go b/src/runtime/os_openbsd_syscall2.go index a48f5fa88a..e4c9d2fe89 100644 --- a/src/runtime/os_openbsd_syscall2.go +++ b/src/runtime/os_openbsd_syscall2.go @@ -39,6 +39,7 @@ func usleep_no_g(usec uint32) { // write calls the write system call. // It returns a non-negative number of bytes written or a negative errno value. +// //go:noescape func write1(fd uintptr, p unsafe.Pointer, n int32) int32 diff --git a/src/runtime/os_plan9.go b/src/runtime/os_plan9.go index 975d460a7d..1a0c0e9363 100644 --- a/src/runtime/os_plan9.go +++ b/src/runtime/os_plan9.go @@ -444,6 +444,7 @@ func exit(e int32) { } // May run with m.p==nil, so write barriers are not allowed. +// //go:nowritebarrier func newosproc(mp *m) { if false { @@ -506,6 +507,7 @@ func write1(fd uintptr, buf unsafe.Pointer, n int32) int32 { var _badsignal = []byte("runtime: signal received on thread not created by Go.\n") // This runs on a foreign stack, without an m or a g. No stack split. +// //go:nosplit func badsignal2() { pwrite(2, unsafe.Pointer(&_badsignal[0]), int32(len(_badsignal)), -1) diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index c76add7802..2f6ec75cf8 100644 --- a/src/runtime/os_windows.go +++ b/src/runtime/os_windows.go @@ -902,6 +902,7 @@ func semacreate(mp *m) { // May run with m.p==nil, so write barriers are not allowed. This // function is called by newosproc0, so it is also required to // operate without stack guards. +// //go:nowritebarrierrec //go:nosplit func newosproc(mp *m) { @@ -930,6 +931,7 @@ func newosproc(mp *m) { // Used by the C library build mode. On Linux this function would allocate a // stack, but that's not necessary for Windows. No stack guards are present // and the GC has not been initialized, so write barriers will fail. +// //go:nowritebarrierrec //go:nosplit func newosproc0(mp *m, stk unsafe.Pointer) { @@ -1019,6 +1021,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { mp := getg().m @@ -1032,6 +1035,7 @@ func unminit() { // Called from exitm, but not from drop, to undo the effect of thread-owned // resources in minit, semacreate, or elsewhere. Do not take locks after calling this. +// //go:nosplit func mdestroy(mp *m) { if mp.highResTimer != 0 { @@ -1050,6 +1054,7 @@ func mdestroy(mp *m) { // Calling stdcall on os stack. // May run during STW, so write barriers are not allowed. +// //go:nowritebarrier //go:nosplit func stdcall(fn stdFunction) uintptr { diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 6600410cb6..e4cc7bfb31 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -525,8 +525,14 @@ func Goexit() { // Used when crashing with panicking. func preprintpanics(p *_panic) { defer func() { - if recover() != nil { - throw("panic while printing panic value") + text := "panic while printing panic value" + switch r := recover().(type) { + case nil: + // nothing to do + case string: + throw(text + ": " + r) + default: + throw(text + ": type " + efaceOf(&r)._type.string()) } }() for p != nil { @@ -944,6 +950,7 @@ func gopanic(e any) { // getargp returns the location where the caller // writes outgoing function call arguments. +// //go:nosplit //go:noinline func getargp() uintptr { @@ -956,6 +963,7 @@ func getargp() uintptr { // // TODO(rsc): Once we commit to CopyStackAlways, // this doesn't need to be nosplit. +// //go:nosplit func gorecover(argp uintptr) any { // Must be in a function running as part of a deferred call during the panic. diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go index e3cd6b9d2a..f0b25c131f 100644 --- a/src/runtime/pprof/pprof.go +++ b/src/runtime/pprof/pprof.go @@ -5,7 +5,7 @@ // Package pprof writes runtime profiling data in the format expected // by the pprof visualization tool. // -// Profiling a Go program +// # Profiling a Go program // // The first step to profiling a Go program is to enable profiling. // Support for profiling benchmarks built with the standard testing @@ -13,54 +13,54 @@ // runs benchmarks in the current directory and writes the CPU and // memory profiles to cpu.prof and mem.prof: // -// go test -cpuprofile cpu.prof -memprofile mem.prof -bench . +// go test -cpuprofile cpu.prof -memprofile mem.prof -bench . // // To add equivalent profiling support to a standalone program, add // code like the following to your main function: // -// var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") -// var memprofile = flag.String("memprofile", "", "write memory profile to `file`") +// var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") +// var memprofile = flag.String("memprofile", "", "write memory profile to `file`") // -// func main() { -// flag.Parse() -// if *cpuprofile != "" { -// f, err := os.Create(*cpuprofile) -// if err != nil { -// log.Fatal("could not create CPU profile: ", err) -// } -// defer f.Close() // error handling omitted for example -// if err := pprof.StartCPUProfile(f); err != nil { -// log.Fatal("could not start CPU profile: ", err) -// } -// defer pprof.StopCPUProfile() -// } +// func main() { +// flag.Parse() +// if *cpuprofile != "" { +// f, err := os.Create(*cpuprofile) +// if err != nil { +// log.Fatal("could not create CPU profile: ", err) +// } +// defer f.Close() // error handling omitted for example +// if err := pprof.StartCPUProfile(f); err != nil { +// log.Fatal("could not start CPU profile: ", err) +// } +// defer pprof.StopCPUProfile() +// } // -// // ... rest of the program ... +// // ... rest of the program ... // -// if *memprofile != "" { -// f, err := os.Create(*memprofile) -// if err != nil { -// log.Fatal("could not create memory profile: ", err) -// } -// defer f.Close() // error handling omitted for example -// runtime.GC() // get up-to-date statistics -// if err := pprof.WriteHeapProfile(f); err != nil { -// log.Fatal("could not write memory profile: ", err) -// } -// } -// } +// if *memprofile != "" { +// f, err := os.Create(*memprofile) +// if err != nil { +// log.Fatal("could not create memory profile: ", err) +// } +// defer f.Close() // error handling omitted for example +// runtime.GC() // get up-to-date statistics +// if err := pprof.WriteHeapProfile(f); err != nil { +// log.Fatal("could not write memory profile: ", err) +// } +// } +// } // // There is also a standard HTTP interface to profiling data. Adding // the following line will install handlers under the /debug/pprof/ // URL to download live profiles: // -// import _ "net/http/pprof" +// import _ "net/http/pprof" // // See the net/http/pprof package for more details. // // Profiles can then be visualized with the pprof tool: // -// go tool pprof cpu.prof +// go tool pprof cpu.prof // // There are many commands available from the pprof command line. // Commonly used commands include "top", which prints a summary of the diff --git a/src/runtime/pprof/pprof_test.go b/src/runtime/pprof/pprof_test.go index ff4ecb4c68..1742dc0cdc 100644 --- a/src/runtime/pprof/pprof_test.go +++ b/src/runtime/pprof/pprof_test.go @@ -1175,6 +1175,7 @@ func blockInfrequentLong(rate int) { } // Used by TestBlockProfileBias. +// //go:linkname blockevent runtime.blockevent func blockevent(cycles int64, skip int) diff --git a/src/runtime/pprof/proto.go b/src/runtime/pprof/proto.go index 68dac42d20..f0769935ae 100644 --- a/src/runtime/pprof/proto.go +++ b/src/runtime/pprof/proto.go @@ -56,9 +56,10 @@ type memMap struct { } // symbolizeFlag keeps track of symbolization result. -// 0 : no symbol lookup was performed -// 1<<0 (lookupTried) : symbol lookup was performed -// 1<<1 (lookupFailed): symbol lookup was performed but failed +// +// 0 : no symbol lookup was performed +// 1<<0 (lookupTried) : symbol lookup was performed +// 1<<1 (lookupFailed): symbol lookup was performed but failed type symbolizeFlag uint8 const ( @@ -507,9 +508,10 @@ func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLo // and looking up debug info is not ideal, so we use a heuristic to filter // the fake pcs and restore the inlined and entry functions. Inlined functions // have the following properties: -// Frame's Func is nil (note: also true for non-Go functions), and -// Frame's Entry matches its entry function frame's Entry (note: could also be true for recursive calls and non-Go functions), and -// Frame's Name does not match its entry function frame's name (note: inlined functions cannot be directly recursive). +// +// Frame's Func is nil (note: also true for non-Go functions), and +// Frame's Entry matches its entry function frame's Entry (note: could also be true for recursive calls and non-Go functions), and +// Frame's Name does not match its entry function frame's name (note: inlined functions cannot be directly recursive). // // As reading and processing the pcs in a stack trace one by one (from leaf to the root), // we use pcDeck to temporarily hold the observed pcs and their expanded frames diff --git a/src/runtime/proc.go b/src/runtime/proc.go index f9f82f3867..4535f62053 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -277,6 +277,7 @@ func main() { } // os_beforeExit is called from os.Exit(0). +// //go:linkname os_beforeExit os.runtime_beforeExit func os_beforeExit() { if raceenabled { @@ -319,6 +320,7 @@ func Gosched() { // goschedguarded yields the processor like gosched, but also checks // for forbidden states and opts out of the yield in those cases. +// //go:nosplit func goschedguarded() { mcall(goschedguarded_m) @@ -894,6 +896,7 @@ func freezetheworld() { // All reads and writes of g's status go through readgstatus, casgstatus // castogscanstatus, casfrom_Gscanstatus. +// //go:nosplit func readgstatus(gp *g) uint32 { return atomic.Load(&gp.atomicstatus) @@ -955,6 +958,7 @@ func castogscanstatus(gp *g, oldval, newval uint32) bool { // and casfrom_Gscanstatus instead. // casgstatus will loop if the g->atomicstatus is in a Gscan status until the routine that // put it in the Gscan state is finished. +// //go:nosplit func casgstatus(gp *g, oldval, newval uint32) { if (oldval&_Gscan != 0) || (newval&_Gscan != 0) || oldval == newval { @@ -1028,6 +1032,7 @@ func casgstatus(gp *g, oldval, newval uint32) { // async wakeup that might come in from netpoll. If we see Gwaiting from the readgstatus, // it might have become Grunnable by the time we get to the cas. If we called casgstatus, // it would loop waiting for the status to go back to Gwaiting, which it never will. +// //go:nosplit func casgcopystack(gp *g) uint32 { for { @@ -1387,6 +1392,7 @@ func mstart0() { // The go:noinline is to guarantee the getcallerpc/getcallersp below are safe, // so that we can set up g0.sched to return to the call of mstart1 above. +// //go:noinline func mstart1() { _g_ := getg() @@ -1443,6 +1449,7 @@ func mstartm0() { } // mPark causes a thread to park itself, returning once woken. +// //go:nosplit func mPark() { gp := getg() @@ -1657,9 +1664,9 @@ func forEachP(fn func(*p)) { // runSafePointFn runs the safe point function, if any, for this P. // This should be called like // -// if getg().m.p.runSafePointFn != 0 { -// runSafePointFn() -// } +// if getg().m.p.runSafePointFn != 0 { +// runSafePointFn() +// } // // runSafePointFn must be checked on any transition in to _Pidle or // _Psyscall to avoid a race where forEachP sees that the P is running @@ -1795,6 +1802,7 @@ func allocm(_p_ *p, fn func(), id int64) *m { // // When the callback is done with the m, it calls dropm to // put the m back on the list. +// //go:nosplit func needm() { if (iscgo || GOOS == "windows") && !cgoHasExtraM { @@ -2000,6 +2008,7 @@ var extraMWaiters uint32 // to extram. If nilokay is true, then lockextra will // return a nil list head if that's what it finds. If nilokay is false, // lockextra will keep waiting until the list head is no longer nil. +// //go:nosplit func lockextra(nilokay bool) *m { const locked = 1 @@ -2073,6 +2082,7 @@ var newmHandoff struct { // May run with m.p==nil, so write barriers are not allowed. // // id is optional pre-allocated m ID. Omit by passing -1. +// //go:nowritebarrierrec func newm(fn func(), _p_ *p, id int64) { // allocm adds a new M to allm, but they do not start until created by @@ -2245,6 +2255,7 @@ func mspinning() { // comment on acquirem below. // // Must not have write barriers because this may be called without a P. +// //go:nowritebarrierrec func startm(_p_ *p, spinning bool) { // Disable preemption. @@ -2329,6 +2340,7 @@ func startm(_p_ *p, spinning bool) { // Hands off P from syscall or locked M. // Always runs without a P, so write barriers are not allowed. +// //go:nowritebarrierrec func handoffp(_p_ *p) { // handoffp must start an M in any situation where @@ -2432,6 +2444,7 @@ func stoplockedm() { // Schedules the locked m to run the locked gp. // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func startlockedm(gp *g) { _g_ := getg() @@ -3248,6 +3261,7 @@ func dropg() { // If the time when the next timer should run is not 0, // it is always larger than the returned time. // We pass now in and out to avoid extra calls of nanotime. +// //go:yeswritebarrierrec func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) { // If it's not yet time for the first timer, or the first adjusted @@ -3680,6 +3694,7 @@ func entersyscall_gcwait() { } // The same as entersyscall(), but with a hint that the syscall is blocking. +// //go:nosplit func entersyscallblock() { _g_ := getg() @@ -3939,6 +3954,7 @@ func exitsyscall0(gp *g) { } // Called from syscall package before fork. +// //go:linkname syscall_runtime_BeforeFork syscall.runtime_BeforeFork //go:nosplit func syscall_runtime_BeforeFork() { @@ -3959,6 +3975,7 @@ func syscall_runtime_BeforeFork() { } // Called from syscall package after fork in parent. +// //go:linkname syscall_runtime_AfterFork syscall.runtime_AfterFork //go:nosplit func syscall_runtime_AfterFork() { @@ -4009,6 +4026,7 @@ func syscall_runtime_AfterForkInChild() { var pendingPreemptSignals uint32 // Called from syscall package before Exec. +// //go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec func syscall_runtime_BeforeExec() { // Prevent thread creation during exec. @@ -4024,6 +4042,7 @@ func syscall_runtime_BeforeExec() { } // Called from syscall package after Exec. +// //go:linkname syscall_runtime_AfterExec syscall.runtime_AfterExec func syscall_runtime_AfterExec() { execLock.unlock() @@ -4305,6 +4324,7 @@ func Breakpoint() { // dolockOSThread is called by LockOSThread and lockOSThread below // after they modify m.locked. Do not allow preemption during this call, // or else the m might be different in this function than in the caller. +// //go:nosplit func dolockOSThread() { if GOARCH == "wasm" { @@ -4356,6 +4376,7 @@ func lockOSThread() { // dounlockOSThread is called by UnlockOSThread and unlockOSThread below // after they update m->locked. Do not allow preemption during this call, // or else the m might be in different in this function than in the caller. +// //go:nosplit func dounlockOSThread() { if GOARCH == "wasm" { @@ -4438,6 +4459,7 @@ func _VDSO() { _VDSO() } // Called if we receive a SIGPROF signal. // Called by the signal handler, may run during STW. +// //go:nowritebarrierrec func sigprof(pc, sp, lr uintptr, gp *g, mp *m) { if prof.hz == 0 { @@ -5446,6 +5468,7 @@ func schedEnabled(gp *g) bool { // Put mp on midle list. // sched.lock must be held. // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func mput(mp *m) { assertLockHeld(&sched.lock) @@ -5459,6 +5482,7 @@ func mput(mp *m) { // Try to get an m from midle list. // sched.lock must be held. // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func mget() *m { assertLockHeld(&sched.lock) @@ -5474,6 +5498,7 @@ func mget() *m { // Put gp on the global runnable queue. // sched.lock must be held. // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func globrunqput(gp *g) { assertLockHeld(&sched.lock) @@ -5485,6 +5510,7 @@ func globrunqput(gp *g) { // Put gp at the head of the global runnable queue. // sched.lock must be held. // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func globrunqputhead(gp *g) { assertLockHeld(&sched.lock) @@ -5497,6 +5523,7 @@ func globrunqputhead(gp *g) { // This clears *batch. // sched.lock must be held. // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func globrunqputbatch(batch *gQueue, n int32) { assertLockHeld(&sched.lock) @@ -5575,11 +5602,11 @@ func (p pMask) clear(id int32) { // // Thus, we get the following effects on timer-stealing in findrunnable: // -// * Idle Ps with no timers when they go idle are never checked in findrunnable -// (for work- or timer-stealing; this is the ideal case). -// * Running Ps must always be checked. -// * Idle Ps whose timers are stolen must continue to be checked until they run -// again, even after timer expiration. +// - Idle Ps with no timers when they go idle are never checked in findrunnable +// (for work- or timer-stealing; this is the ideal case). +// - Running Ps must always be checked. +// - Idle Ps whose timers are stolen must continue to be checked until they run +// again, even after timer expiration. // // When the P starts running again, the mask should be set, as a timer may be // added at any time. @@ -5609,6 +5636,7 @@ func updateTimerPMask(pp *p) { // sched.lock must be held. // // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func pidleput(_p_ *p) { assertLockHeld(&sched.lock) @@ -5628,6 +5656,7 @@ func pidleput(_p_ *p) { // sched.lock must be held. // // May run during STW, so write barriers are not allowed. +// //go:nowritebarrierrec func pidleget() *p { assertLockHeld(&sched.lock) @@ -6083,6 +6112,7 @@ func sync_atomic_runtime_procUnpin() { } // Active spinning for sync.Mutex. +// //go:linkname sync_runtime_canSpin sync.runtime_canSpin //go:nosplit func sync_runtime_canSpin(i int) bool { diff --git a/src/runtime/proc_test.go b/src/runtime/proc_test.go index 719d0d1aee..c49d6ae8a8 100644 --- a/src/runtime/proc_test.go +++ b/src/runtime/proc_test.go @@ -1023,6 +1023,7 @@ func TestLockOSThreadTemplateThreadRace(t *testing.T) { } // fakeSyscall emulates a system call. +// //go:nosplit func fakeSyscall(duration time.Duration) { runtime.Entersyscall() diff --git a/src/runtime/race.go b/src/runtime/race.go index e019923bb5..4694288082 100644 --- a/src/runtime/race.go +++ b/src/runtime/race.go @@ -233,6 +233,7 @@ func raceSymbolizeData(ctx *symbolizeDataContext) { } // Race runtime functions called via runtime·racecall. +// //go:linkname __tsan_init __tsan_init var __tsan_init byte @@ -285,6 +286,7 @@ var __tsan_go_ignore_sync_end byte var __tsan_report_count byte // Mimic what cmd/cgo would do. +// //go:cgo_import_static __tsan_init //go:cgo_import_static __tsan_fini //go:cgo_import_static __tsan_proc_create @@ -304,6 +306,7 @@ var __tsan_report_count byte //go:cgo_import_static __tsan_report_count // These are called from race_amd64.s. +// //go:cgo_import_static __tsan_read //go:cgo_import_static __tsan_read_pc //go:cgo_import_static __tsan_read_range @@ -348,6 +351,7 @@ func racecallbackthunk(uintptr) func racecall(fn *byte, arg0, arg1, arg2, arg3 uintptr) // checks if the address has shadow (i.e. heap or data/bss) +// //go:nosplit func isvalidaddr(addr unsafe.Pointer) bool { return racearenastart <= uintptr(addr) && uintptr(addr) < racearenaend || diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go index 65e1e0eebc..5429aa2e5b 100644 --- a/src/runtime/runtime1.go +++ b/src/runtime/runtime1.go @@ -53,6 +53,7 @@ var ( ) // nosplit for use in linux startup sysargs +// //go:nosplit func argv_index(argv **byte, i int32) *byte { return *(**byte)(add(unsafe.Pointer(argv), uintptr(i)*goarch.PtrSize)) @@ -438,6 +439,7 @@ func setTraceback(level string) { // int64 division is lowered into _divv() call on 386, which does not fit into nosplit functions. // Handles overflow in a time-specific manner. // This keeps us within no-split stack limits on 32-bit processors. +// //go:nosplit func timediv(v int64, div int32, rem *int32) int32 { res := int32(0) @@ -493,18 +495,21 @@ func reflect_typelinks() ([]unsafe.Pointer, [][]int32) { } // reflect_resolveNameOff resolves a name offset from a base pointer. +// //go:linkname reflect_resolveNameOff reflect.resolveNameOff func reflect_resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer { return unsafe.Pointer(resolveNameOff(ptrInModule, nameOff(off)).bytes) } // reflect_resolveTypeOff resolves an *rtype offset from a base type. +// //go:linkname reflect_resolveTypeOff reflect.resolveTypeOff func reflect_resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer { return unsafe.Pointer((*_type)(rtype).typeOff(typeOff(off))) } // reflect_resolveTextOff resolves a function pointer offset from a base type. +// //go:linkname reflect_resolveTextOff reflect.resolveTextOff func reflect_resolveTextOff(rtype unsafe.Pointer, off int32) unsafe.Pointer { return (*_type)(rtype).textOff(textOff(off)) @@ -512,18 +517,21 @@ func reflect_resolveTextOff(rtype unsafe.Pointer, off int32) unsafe.Pointer { } // reflectlite_resolveNameOff resolves a name offset from a base pointer. +// //go:linkname reflectlite_resolveNameOff internal/reflectlite.resolveNameOff func reflectlite_resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer { return unsafe.Pointer(resolveNameOff(ptrInModule, nameOff(off)).bytes) } // reflectlite_resolveTypeOff resolves an *rtype offset from a base type. +// //go:linkname reflectlite_resolveTypeOff internal/reflectlite.resolveTypeOff func reflectlite_resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer { return unsafe.Pointer((*_type)(rtype).typeOff(typeOff(off))) } // reflect_addReflectOff adds a pointer to the reflection offset lookup map. +// //go:linkname reflect_addReflectOff reflect.addReflectOff func reflect_addReflectOff(ptr unsafe.Pointer) int32 { reflectOffsLock() diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index dc18bf927e..b2c42d0e5c 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -272,6 +272,7 @@ func (gp *guintptr) cas(old, new guintptr) bool { // setGNoWB performs *gp = new without a write barrier. // For times when it's impractical to use a guintptr. +// //go:nosplit //go:nowritebarrier func setGNoWB(gp **g, new *g) { @@ -305,6 +306,7 @@ func (mp *muintptr) set(m *m) { *mp = muintptr(unsafe.Pointer(m)) } // setMNoWB performs *mp = new without a write barrier. // For times when it's impractical to use an muintptr. +// //go:nosplit //go:nowritebarrier func setMNoWB(mp **m, new *m) { @@ -514,6 +516,7 @@ type m struct { g0 *g // goroutine with scheduling stack morebuf gobuf // gobuf arg to morestack divmod uint32 // div/mod denominator for arm - known to liblink + _ uint32 // align next field to 8 bytes // Fields not known to debuggers. procid uint64 // for debuggers, but offset not hard-coded diff --git a/src/runtime/runtime_test.go b/src/runtime/runtime_test.go index 12f261bdd2..1dc04ac55d 100644 --- a/src/runtime/runtime_test.go +++ b/src/runtime/runtime_test.go @@ -196,6 +196,7 @@ func TestSetPanicOnFault(t *testing.T) { // testSetPanicOnFault tests one potentially faulting address. // It deliberately constructs and uses an invalid pointer, // so mark it as nocheckptr. +// //go:nocheckptr func testSetPanicOnFault(t *testing.T, addr uintptr, nfault *int) { if GOOS == "js" { diff --git a/src/runtime/sema.go b/src/runtime/sema.go index f94c1aa891..e83deee083 100644 --- a/src/runtime/sema.go +++ b/src/runtime/sema.go @@ -475,6 +475,7 @@ func less(a, b uint32) bool { // notifyListAdd adds the caller to a notify list such that it can receive // notifications. The caller must eventually call notifyListWait to wait for // such a notification, passing the returned ticket number. +// //go:linkname notifyListAdd sync.runtime_notifyListAdd func notifyListAdd(l *notifyList) uint32 { // This may be called concurrently, for example, when called from @@ -484,6 +485,7 @@ func notifyListAdd(l *notifyList) uint32 { // notifyListWait waits for a notification. If one has been sent since // notifyListAdd was called, it returns immediately. Otherwise, it blocks. +// //go:linkname notifyListWait sync.runtime_notifyListWait func notifyListWait(l *notifyList, t uint32) { lockWithRank(&l.lock, lockRankNotifyList) @@ -518,6 +520,7 @@ func notifyListWait(l *notifyList, t uint32) { } // notifyListNotifyAll notifies all entries in the list. +// //go:linkname notifyListNotifyAll sync.runtime_notifyListNotifyAll func notifyListNotifyAll(l *notifyList) { // Fast-path: if there are no new waiters since the last notification @@ -550,6 +553,7 @@ func notifyListNotifyAll(l *notifyList) { } // notifyListNotifyOne notifies one entry in the list. +// //go:linkname notifyListNotifyOne sync.runtime_notifyListNotifyOne func notifyListNotifyOne(l *notifyList) { // Fast-path: if there are no new waiters since the last notification diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go index 0e11c57683..3db789396d 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -108,6 +108,7 @@ var signalsOK bool // Initialize signals. // Called by libpreinit so runtime may not be initialized. +// //go:nosplit //go:nowritebarrierrec func initsig(preinit bool) { @@ -260,6 +261,7 @@ func sigignore(sig uint32) { // back to the default. This is called by the child after a fork, so that // we can enable the signal mask for the exec without worrying about // running a signal handler in the child. +// //go:nosplit //go:nowritebarrierrec func clearSignalHandlers() { @@ -519,6 +521,7 @@ func sigprofNonGo(sig uint32, info *siginfo, ctx unsafe.Pointer) { // sigprofNonGoPC is called when a profiling signal arrived on a // non-Go thread and we have a single PC value, not a stack trace. // g is nil, and what we can do is very limited. +// //go:nosplit //go:nowritebarrierrec func sigprofNonGoPC(pc uintptr) { @@ -536,6 +539,7 @@ func sigprofNonGoPC(pc uintptr) { // We do this in case some non-Go code called sigaltstack. // This reports whether the stack was adjusted, and if so stores the old // signal stack in *gsigstack. +// //go:nosplit func adjustSignalStack(sig uint32, mp *m, gsigStack *gsignalStack) bool { sp := uintptr(unsafe.Pointer(&sig)) @@ -795,6 +799,7 @@ func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) { // getg().throwsplit, since sigpanic may need to grow the stack. // // This is exported via linkname to assembly in runtime/cgo. +// //go:linkname sigpanic func sigpanic() { g := getg() @@ -843,6 +848,7 @@ func sigpanic() { // dieFromSignal kills the program with a signal. // This provides the expected exit status for the shell. // This is only called with fatal signals expected to kill the process. +// //go:nosplit //go:nowritebarrierrec func dieFromSignal(sig uint32) { @@ -1015,6 +1021,7 @@ func signalDuringFork(sig uint32) { var badginsignalMsg = "fatal: bad g in signal handler\n" // This runs on a foreign stack, without an m or a g. No stack split. +// //go:nosplit //go:norace //go:nowritebarrierrec @@ -1044,6 +1051,7 @@ func sigfwd(fn uintptr, sig uint32, info *siginfo, ctx unsafe.Pointer) // signal to the handler that was installed before Go's. Returns whether the // signal was forwarded. // This is called by the signal handler, and the world may be stopped. +// //go:nosplit //go:nowritebarrierrec func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool { @@ -1113,6 +1121,7 @@ func sigfwdgo(sig uint32, info *siginfo, ctx unsafe.Pointer) bool { // thread calls a Go function. // This is nosplit and nowritebarrierrec because it is called by needm // which may be called on a non-Go thread with no g available. +// //go:nosplit //go:nowritebarrierrec func sigsave(p *sigset) { @@ -1124,6 +1133,7 @@ func sigsave(p *sigset) { // calls a Go function. // This is nosplit and nowritebarrierrec because it is called by dropm // after g has been cleared. +// //go:nosplit //go:nowritebarrierrec func msigrestore(sigmask sigset) { @@ -1143,6 +1153,7 @@ var sigsetAllExiting = sigset_all // definition of sigset_all is used. // This is nosplit and nowritebarrierrec because it is called by needm // which may be called on a non-Go thread with no g available. +// //go:nosplit //go:nowritebarrierrec func sigblock(exiting bool) { @@ -1157,6 +1168,7 @@ func sigblock(exiting bool) { // This is nosplit and nowritebarrierrec because it is called from // dieFromSignal, which can be called by sigfwdgo while running in the // signal handler, on the signal stack, with no g available. +// //go:nosplit //go:nowritebarrierrec func unblocksig(sig uint32) { @@ -1215,6 +1227,7 @@ func minitSignalMask() { // unminitSignals is called from dropm, via unminit, to undo the // effect of calling minit on a non-Go thread. +// //go:nosplit func unminitSignals() { if getg().m.newSigstack { @@ -1234,6 +1247,7 @@ func unminitSignals() { // blockableSig reports whether sig may be blocked by the signal mask. // We never want to block the signals marked _SigUnblock; // these are the synchronous signals that turn into a Go panic. +// We never want to block the preemption signal if it is being used. // In a Go program--not a c-archive/c-shared--we never want to block // the signals marked _SigKill or _SigThrow, as otherwise it's possible // for all running threads to block them and delay their delivery until @@ -1244,6 +1258,9 @@ func blockableSig(sig uint32) bool { if flags&_SigUnblock != 0 { return false } + if sig == sigPreempt && preemptMSupported && debug.asyncpreemptoff == 0 { + return false + } if isarchive || islibrary { return true } @@ -1264,6 +1281,7 @@ type gsignalStack struct { // It saves the old values in *old for use by restoreGsignalStack. // This is used when handling a signal if non-Go code has set the // alternate signal stack. +// //go:nosplit //go:nowritebarrierrec func setGsignalStack(st *stackt, old *gsignalStack) { @@ -1283,6 +1301,7 @@ func setGsignalStack(st *stackt, old *gsignalStack) { // restoreGsignalStack restores the gsignal stack to the value it had // before entering the signal handler. +// //go:nosplit //go:nowritebarrierrec func restoreGsignalStack(st *gsignalStack) { @@ -1294,6 +1313,7 @@ func restoreGsignalStack(st *gsignalStack) { } // signalstack sets the current thread's alternate signal stack to s. +// //go:nosplit func signalstack(s *stack) { st := stackt{ss_size: s.hi - s.lo} diff --git a/src/runtime/sigqueue.go b/src/runtime/sigqueue.go index fdf99d94a2..49502cbed3 100644 --- a/src/runtime/sigqueue.go +++ b/src/runtime/sigqueue.go @@ -125,6 +125,7 @@ Send: // Called to receive the next queued signal. // Must only be called from a single goroutine at a time. +// //go:linkname signal_recv os/signal.signal_recv func signal_recv() uint32 { for { @@ -173,6 +174,7 @@ func signal_recv() uint32 { // the signal(s) in question, and here we are just waiting to make sure // that all the signals have been delivered to the user channels // by the os/signal package. +// //go:linkname signalWaitUntilIdle os/signal.signalWaitUntilIdle func signalWaitUntilIdle() { // Although the signals we care about have been removed from @@ -193,6 +195,7 @@ func signalWaitUntilIdle() { } // Must only be called from a single goroutine at a time. +// //go:linkname signal_enable os/signal.signal_enable func signal_enable(s uint32) { if !sig.inuse { @@ -221,6 +224,7 @@ func signal_enable(s uint32) { } // Must only be called from a single goroutine at a time. +// //go:linkname signal_disable os/signal.signal_disable func signal_disable(s uint32) { if s >= uint32(len(sig.wanted)*32) { @@ -234,6 +238,7 @@ func signal_disable(s uint32) { } // Must only be called from a single goroutine at a time. +// //go:linkname signal_ignore os/signal.signal_ignore func signal_ignore(s uint32) { if s >= uint32(len(sig.wanted)*32) { @@ -253,6 +258,7 @@ func signal_ignore(s uint32) { // sigInitIgnored marks the signal as already ignored. This is called at // program start by initsig. In a shared library initsig is called by // libpreinit, so the runtime may not be initialized yet. +// //go:nosplit func sigInitIgnored(s uint32) { i := sig.ignored[s/32] @@ -261,6 +267,7 @@ func sigInitIgnored(s uint32) { } // Checked by signal handlers. +// //go:linkname signal_ignored os/signal.signal_ignored func signal_ignored(s uint32) bool { i := atomic.Load(&sig.ignored[s/32]) diff --git a/src/runtime/sigqueue_plan9.go b/src/runtime/sigqueue_plan9.go index d5fe8f8b35..9ed6fb5886 100644 --- a/src/runtime/sigqueue_plan9.go +++ b/src/runtime/sigqueue_plan9.go @@ -94,6 +94,7 @@ func sendNote(s *byte) bool { // Called to receive the next queued signal. // Must only be called from a single goroutine at a time. +// //go:linkname signal_recv os/signal.signal_recv func signal_recv() string { for { @@ -117,6 +118,7 @@ func signal_recv() string { // the signal(s) in question, and here we are just waiting to make sure // that all the signals have been delivered to the user channels // by the os/signal package. +// //go:linkname signalWaitUntilIdle os/signal.signalWaitUntilIdle func signalWaitUntilIdle() { for { @@ -131,6 +133,7 @@ func signalWaitUntilIdle() { } // Must only be called from a single goroutine at a time. +// //go:linkname signal_enable os/signal.signal_enable func signal_enable(s uint32) { if !sig.inuse { @@ -141,11 +144,13 @@ func signal_enable(s uint32) { } // Must only be called from a single goroutine at a time. +// //go:linkname signal_disable os/signal.signal_disable func signal_disable(s uint32) { } // Must only be called from a single goroutine at a time. +// //go:linkname signal_ignore os/signal.signal_ignore func signal_ignore(s uint32) { } diff --git a/src/runtime/stack.go b/src/runtime/stack.go index 54a02173c3..b7df231722 100644 --- a/src/runtime/stack.go +++ b/src/runtime/stack.go @@ -151,7 +151,9 @@ const ( // Global pool of spans that have free stacks. // Stacks are assigned an order according to size. -// order = log_2(size/FixedStack) +// +// order = log_2(size/FixedStack) +// // There is a free list for each order. var stackpool [_NumStackOrders]struct { item stackpoolItem diff --git a/src/runtime/string.go b/src/runtime/string.go index eec29075b9..8b20c93fd7 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -147,10 +147,10 @@ func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) { // and otherwise intrinsified by the compiler. // // Some internal compiler optimizations use this function. -// - Used for m[T1{... Tn{..., string(k), ...} ...}] and m[string(k)] -// where k is []byte, T1 to Tn is a nesting of struct and array literals. -// - Used for "<"+string(b)+">" concatenation where b is []byte. -// - Used for string(b)=="foo" comparison where b is []byte. +// - Used for m[T1{... Tn{..., string(k), ...} ...}] and m[string(k)] +// where k is []byte, T1 to Tn is a nesting of struct and array literals. +// - Used for "<"+string(b)+">" concatenation where b is []byte. +// - Used for string(b)=="foo" comparison where b is []byte. func slicebytetostringtmp(ptr *byte, n int) (str string) { if raceenabled && n > 0 { racereadrangepc(unsafe.Pointer(ptr), @@ -325,6 +325,7 @@ func gobytes(p *byte, n int) (b []byte) { } // This is exported via linkname to assembly in syscall (for Plan9). +// //go:linkname gostring func gostring(p *byte) string { l := findnull(p) diff --git a/src/runtime/stubs.go b/src/runtime/stubs.go index cd7c91029b..8c4ab3ed4e 100644 --- a/src/runtime/stubs.go +++ b/src/runtime/stubs.go @@ -12,6 +12,7 @@ import ( ) // Should be a built-in for unsafe.Pointer? +// //go:nosplit func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { return unsafe.Pointer(uintptr(p) + x) @@ -111,6 +112,7 @@ func reflect_memclrNoHeapPointers(ptr unsafe.Pointer, n uintptr) { func memmove(to, from unsafe.Pointer, n uintptr) // Outside assembly calls memmove. Make sure it has ABI wrappers. +// //go:linkname memmove //go:linkname reflect_memmove reflect.memmove @@ -165,6 +167,7 @@ func net_fastrand() uint32 { return fastrand() } func os_fastrand() uint32 { return fastrand() } // in internal/bytealg/equal_*.s +// //go:noescape func memequal(a, b unsafe.Pointer, size uintptr) bool @@ -173,6 +176,7 @@ func memequal(a, b unsafe.Pointer, size uintptr) bool // output depends on the input. noescape is inlined and currently // compiles down to zero instructions. // USE CAREFULLY! +// //go:nosplit func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) @@ -235,6 +239,7 @@ func breakpoint() // Arguments passed through to reflectcall do not escape. The type is used // only in a very limited callee of reflectcall, the stackArgs are copied, and // regArgs is only used in the reflectcall frame. +// //go:noescape func reflectcall(stackArgsType *_type, fn, stackArgs unsafe.Pointer, stackArgsSize, stackRetOffset, frameSize uint32, regArgs *abi.RegArgs) diff --git a/src/runtime/stubs2.go b/src/runtime/stubs2.go index 9aa965454d..94a888dec6 100644 --- a/src/runtime/stubs2.go +++ b/src/runtime/stubs2.go @@ -24,6 +24,7 @@ func usleep_no_g(usec uint32) { // write calls the write system call. // It returns a non-negative number of bytes written or a negative errno value. +// //go:noescape func write1(fd uintptr, p unsafe.Pointer, n int32) int32 diff --git a/src/runtime/stubs_linux.go b/src/runtime/stubs_linux.go index 06c14e2160..2367dc2bd0 100644 --- a/src/runtime/stubs_linux.go +++ b/src/runtime/stubs_linux.go @@ -13,6 +13,7 @@ func sbrk0() uintptr // Called from write_err_android.go only, but defined in sys_linux_*.s; // declared here (instead of in write_err_android.go) for go vet on non-android builds. // The return value is the raw syscall result, which may encode an error number. +// //go:noescape func access(name *byte, mode int32) int32 func connect(fd int32, addr unsafe.Pointer, len int32) int32 diff --git a/src/runtime/stubs_ppc64.go b/src/runtime/stubs_ppc64.go index 07127629d1..6919b748f0 100644 --- a/src/runtime/stubs_ppc64.go +++ b/src/runtime/stubs_ppc64.go @@ -7,5 +7,6 @@ package runtime // This is needed for vet +// //go:noescape func callCgoSigaction(sig uintptr, new, old *sigactiont) int32 diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index ee4db47314..ad34b68c7d 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -494,6 +494,7 @@ var modulesSlice *[]*moduledata // see activeModules // // This is nosplit/nowritebarrier because it is called by the // cgo pointer checking code. +// //go:nosplit //go:nowritebarrier func activeModules() []*moduledata { @@ -659,6 +660,7 @@ func moduledataverify1(datap *moduledata) { // relocated baseaddr to compute the function address. // // It is nosplit because it is part of the findfunc implementation. +// //go:nosplit func (md *moduledata) textAddr(off32 uint32) uintptr { off := uintptr(off32) @@ -683,6 +685,7 @@ func (md *moduledata) textAddr(off32 uint32) uintptr { // to md.text, and returns if the PC is in any Go text section. // // It is nosplit because it is part of the findfunc implementation. +// //go:nosplit func (md *moduledata) textOff(pc uintptr) (uint32, bool) { res := uint32(pc - md.text) diff --git a/src/runtime/symtab_test.go b/src/runtime/symtab_test.go index a83afc3385..1a0c55af97 100644 --- a/src/runtime/symtab_test.go +++ b/src/runtime/symtab_test.go @@ -29,6 +29,7 @@ func TestCaller(t *testing.T) { // These are marked noinline so that we can use FuncForPC // in testCallerBar. +// //go:noinline func testCallerFoo(t *testing.T) { testCallerBar(t) @@ -205,15 +206,15 @@ func tracebackFunc(t *testing.T) uintptr { // Go obviously doesn't easily expose the problematic PCs to running programs, // so this test is a bit fragile. Some details: // -// * tracebackFunc is our target function. We want to get a PC in the -// alignment region following this function. This function also has other -// functions inlined into it to ensure it has an InlTree (this was the source -// of the bug in issue 44971). +// - tracebackFunc is our target function. We want to get a PC in the +// alignment region following this function. This function also has other +// functions inlined into it to ensure it has an InlTree (this was the source +// of the bug in issue 44971). // -// * We acquire a PC in tracebackFunc, walking forwards until FuncForPC says -// we're in a new function. The last PC of the function according to FuncForPC -// should be in the alignment region (assuming the function isn't already -// perfectly aligned). +// - We acquire a PC in tracebackFunc, walking forwards until FuncForPC says +// we're in a new function. The last PC of the function according to FuncForPC +// should be in the alignment region (assuming the function isn't already +// perfectly aligned). // // This is a regression test for issue 44971. func TestFunctionAlignmentTraceback(t *testing.T) { diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go index 58b3a9171c..1547fdceb0 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -170,6 +170,7 @@ func pthread_kill_trampoline() // mmap is used to do low-level memory allocation via mmap. Don't allow stack // splits, since this function (used by sysAlloc) is called in a lot of low-level // parts of the runtime and callers often assume it won't acquire any locks. +// //go:nosplit func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) { args := struct { @@ -232,10 +233,10 @@ func closefd(fd int32) int32 { } func close_trampoline() +// This is exported via linkname to assembly in runtime/cgo. +// //go:nosplit //go:cgo_unsafe_args -// -// This is exported via linkname to assembly in runtime/cgo. //go:linkname exit func exit(code int32) { libcCall(unsafe.Pointer(abi.FuncPCABI0(exit_trampoline)), unsafe.Pointer(&code)) diff --git a/src/runtime/sys_libc.go b/src/runtime/sys_libc.go index 7012b4167e..0c6f13ca9f 100644 --- a/src/runtime/sys_libc.go +++ b/src/runtime/sys_libc.go @@ -12,6 +12,7 @@ import "unsafe" // fn is the raw pc value of the entry point of the desired function. // Switches to the system stack, if not already there. // Preserves the calling point as the location where a profiler traceback will begin. +// //go:nosplit func libcCall(fn, arg unsafe.Pointer) int32 { // Leave caller's PC/SP/G around for traceback. diff --git a/src/runtime/sys_openbsd2.go b/src/runtime/sys_openbsd2.go index d174d87a49..f936e0cfc3 100644 --- a/src/runtime/sys_openbsd2.go +++ b/src/runtime/sys_openbsd2.go @@ -12,6 +12,7 @@ import ( ) // This is exported via linkname to assembly in runtime/cgo. +// //go:linkname exit //go:nosplit //go:cgo_unsafe_args @@ -45,6 +46,7 @@ func thrkill_trampoline() // mmap is used to do low-level memory allocation via mmap. Don't allow stack // splits, since this function (used by sysAlloc) is called in a lot of low-level // parts of the runtime and callers often assume it won't acquire any locks. +// //go:nosplit func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) (unsafe.Pointer, int) { args := struct { diff --git a/src/runtime/syscall_aix.go b/src/runtime/syscall_aix.go index 79b51240e9..f294922e7d 100644 --- a/src/runtime/syscall_aix.go +++ b/src/runtime/syscall_aix.go @@ -126,6 +126,7 @@ func syscall_chroot1(path uintptr) (err uintptr) { } // like close, but must not split stack, for fork. +// //go:linkname syscall_close syscall.close //go:nosplit func syscall_close(fd int32) int32 { @@ -148,6 +149,7 @@ func syscall_execve(path, argv, envp uintptr) (err uintptr) { } // like exit, but must not split stack, for fork. +// //go:linkname syscall_exit syscall.exit //go:nosplit func syscall_exit(code uintptr) { diff --git a/src/runtime/syscall_solaris.go b/src/runtime/syscall_solaris.go index 79775711ae..e7bab3b23f 100644 --- a/src/runtime/syscall_solaris.go +++ b/src/runtime/syscall_solaris.go @@ -85,6 +85,7 @@ func syscall_chroot(path uintptr) (err uintptr) { } // like close, but must not split stack, for forkx. +// //go:nosplit //go:linkname syscall_close func syscall_close(fd int32) int32 { @@ -113,6 +114,7 @@ func syscall_execve(path, argv, envp uintptr) (err uintptr) { } // like exit, but must not split stack, for forkx. +// //go:nosplit //go:linkname syscall_exit func syscall_exit(code uintptr) { diff --git a/src/runtime/syscall_windows.go b/src/runtime/syscall_windows.go index 9c38facf08..a841a31a27 100644 --- a/src/runtime/syscall_windows.go +++ b/src/runtime/syscall_windows.go @@ -399,6 +399,7 @@ const _LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800 // parameter and the important SEARCH_SYSTEM32 argument. But on systems that // do not have that option, absoluteFilepath should contain a fallback // to the full path inside of system32 for use with vanilla LoadLibrary. +// //go:linkname syscall_loadsystemlibrary syscall.loadsystemlibrary //go:nosplit //go:cgo_unsafe_args diff --git a/src/runtime/syscall_windows_test.go b/src/runtime/syscall_windows_test.go index 034a1d84db..37f8f40cfb 100644 --- a/src/runtime/syscall_windows_test.go +++ b/src/runtime/syscall_windows_test.go @@ -469,6 +469,7 @@ func sum5andPair(i1, i2, i3, i4, i5 uint8Pair) uintptr { // that insufficient spill slots allocated (according to the ABI) // may cause compiler-generated spills to clobber the return PC. // Then, the GC stack scanning will catch that. +// //go:registerparams func sum9andGC(i1, i2, i3, i4, i5, i6, i7, i8, i9 uint32) uintptr { runtime.GC() diff --git a/src/runtime/testdata/testprog/crash.go b/src/runtime/testdata/testprog/crash.go index c4990cdda9..a2294ba149 100644 --- a/src/runtime/testdata/testprog/crash.go +++ b/src/runtime/testdata/testprog/crash.go @@ -12,6 +12,13 @@ import ( func init() { register("Crash", Crash) register("DoublePanic", DoublePanic) + register("ErrorPanic", ErrorPanic) + register("StringerPanic", StringerPanic) + register("DoubleErrorPanic", DoubleErrorPanic) + register("DoubleStringerPanic", DoubleStringerPanic) + register("StringPanic", StringPanic) + register("NilPanic", NilPanic) + register("CircularPanic", CircularPanic) } func test(name string) { @@ -64,3 +71,69 @@ func DoublePanic() { }() panic(P("XXX")) } + +// Test that panic while panicking discards error message +// See issue 52257 +type exampleError struct{} + +func (e exampleError) Error() string { + panic("important error message") +} + +func ErrorPanic() { + panic(exampleError{}) +} + +type examplePanicError struct{} + +func (e examplePanicError) Error() string { + panic(exampleError{}) +} + +func DoubleErrorPanic() { + panic(examplePanicError{}) +} + +type exampleStringer struct{} + +func (s exampleStringer) String() string { + panic("important stringer message") +} + +func StringerPanic() { + panic(exampleStringer{}) +} + +type examplePanicStringer struct{} + +func (s examplePanicStringer) String() string { + panic(exampleStringer{}) +} + +func DoubleStringerPanic() { + panic(examplePanicStringer{}) +} + +func StringPanic() { + panic("important string message") +} + +func NilPanic() { + panic(nil) +} + +type exampleCircleStartError struct {} + +func (e exampleCircleStartError) Error() string { + panic(exampleCircleEndError{}) +} + +type exampleCircleEndError struct {} + +func (e exampleCircleEndError) Error() string { + panic(exampleCircleStartError{}) +} + +func CircularPanic() { + panic(exampleCircleStartError{}) +} \ No newline at end of file diff --git a/src/runtime/testdata/testprogcgo/dropm_stub.go b/src/runtime/testdata/testprogcgo/dropm_stub.go index f7f142c1fd..6997cfd3fa 100644 --- a/src/runtime/testdata/testprogcgo/dropm_stub.go +++ b/src/runtime/testdata/testprogcgo/dropm_stub.go @@ -7,5 +7,6 @@ package main import _ "unsafe" // for go:linkname // Defined in the runtime package. +// //go:linkname runtime_getm_for_test runtime.getm func runtime_getm_for_test() uintptr diff --git a/src/runtime/testdata/testprogcgo/eintr.go b/src/runtime/testdata/testprogcgo/eintr.go index b35b280a76..6e9677f988 100644 --- a/src/runtime/testdata/testprogcgo/eintr.go +++ b/src/runtime/testdata/testprogcgo/eintr.go @@ -70,6 +70,7 @@ func EINTR() { // spin does CPU bound spinning and allocating for a millisecond, // to get a SIGURG. +// //go:noinline func spin() (float64, []byte) { stop := time.Now().Add(time.Millisecond) diff --git a/src/runtime/time.go b/src/runtime/time.go index a9ad620776..e4d8269987 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -173,6 +173,7 @@ const verifyTimers = false // time.now is implemented in assembly. // timeSleep puts the current goroutine to sleep for at least ns nanoseconds. +// //go:linkname timeSleep time.Sleep func timeSleep(ns int64) { if ns <= 0 { @@ -205,6 +206,7 @@ func resetForSleep(gp *g, ut unsafe.Pointer) bool { } // startTimer adds t to the timer heap. +// //go:linkname startTimer time.startTimer func startTimer(t *timer) { if raceenabled { @@ -215,14 +217,17 @@ func startTimer(t *timer) { // stopTimer stops a timer. // It reports whether t was stopped before being run. +// //go:linkname stopTimer time.stopTimer func stopTimer(t *timer) bool { return deltimer(t) } // resetTimer resets an inactive timer, adding it to the heap. -//go:linkname resetTimer time.resetTimer +// // Reports whether the timer was modified before it was run. +// +//go:linkname resetTimer time.resetTimer func resetTimer(t *timer, when int64) bool { if raceenabled { racerelease(unsafe.Pointer(t)) @@ -231,6 +236,7 @@ func resetTimer(t *timer, when int64) bool { } // modTimer modifies an existing timer. +// //go:linkname modTimer time.modTimer func modTimer(t *timer, when, period int64, f func(any, uintptr), arg any, seq uintptr) { modtimer(t, when, period, f, arg, seq) @@ -737,6 +743,7 @@ func addAdjustedTimers(pp *p, moved []*timer) { // should wake up the netpoller. It returns 0 if there are no timers. // This function is invoked when dropping a P, and must run without // any write barriers. +// //go:nowritebarrierrec func nobarrierWakeTime(pp *p) int64 { next := int64(atomic.Load64(&pp.timer0When)) @@ -753,6 +760,7 @@ func nobarrierWakeTime(pp *p) int64 { // when the first timer should run. // The caller must have locked the timers for pp. // If a timer is run, this will temporarily unlock the timers. +// //go:systemstack func runtimer(pp *p, now int64) int64 { for { @@ -819,6 +827,7 @@ func runtimer(pp *p, now int64) int64 { // runOneTimer runs a single timer. // The caller must have locked the timers for pp. // This will temporarily unlock the timers while running the timer function. +// //go:systemstack func runOneTimer(pp *p, t *timer, now int64) { if raceenabled { diff --git a/src/runtime/time_fake.go b/src/runtime/time_fake.go index b5e0463588..9e24f70931 100644 --- a/src/runtime/time_fake.go +++ b/src/runtime/time_fake.go @@ -44,6 +44,7 @@ func time_now() (sec int64, nsec int32, mono int64) { // write is like the Unix write system call. // We have to avoid write barriers to avoid potential deadlock // on write calls. +// //go:nowritebarrierrec func write(fd uintptr, p unsafe.Pointer, n int32) int32 { if !(fd == 1 || fd == 2) { diff --git a/src/runtime/trace/annotation.go b/src/runtime/trace/annotation.go index bf3dbc3d79..9171633b07 100644 --- a/src/runtime/trace/annotation.go +++ b/src/runtime/trace/annotation.go @@ -28,13 +28,13 @@ type traceContextKey struct{} // If the end function is called multiple times, only the first // call is used in the latency measurement. // -// ctx, task := trace.NewTask(ctx, "awesomeTask") -// trace.WithRegion(ctx, "preparation", prepWork) -// // preparation of the task -// go func() { // continue processing the task in a separate goroutine. -// defer task.End() -// trace.WithRegion(ctx, "remainingWork", remainingWork) -// }() +// ctx, task := trace.NewTask(ctx, "awesomeTask") +// trace.WithRegion(ctx, "preparation", prepWork) +// // preparation of the task +// go func() { // continue processing the task in a separate goroutine. +// defer task.End() +// trace.WithRegion(ctx, "remainingWork", remainingWork) +// }() func NewTask(pctx context.Context, taskType string) (ctx context.Context, task *Task) { pid := fromContext(pctx).id id := newID() @@ -148,7 +148,7 @@ func WithRegion(ctx context.Context, regionType string, fn func()) { // after this region must be ended before this region can be ended. // Recommended usage is // -// defer trace.StartRegion(ctx, "myTracedRegion").End() +// defer trace.StartRegion(ctx, "myTracedRegion").End() func StartRegion(ctx context.Context, regionType string) *Region { if !IsEnabled() { return noopRegion diff --git a/src/runtime/trace/trace.go b/src/runtime/trace/trace.go index b34aef03c5..e0c3ca7a1e 100644 --- a/src/runtime/trace/trace.go +++ b/src/runtime/trace/trace.go @@ -5,7 +5,7 @@ // Package trace contains facilities for programs to generate traces // for the Go execution tracer. // -// Tracing runtime activities +// # Tracing runtime activities // // The execution trace captures a wide range of execution events such as // goroutine creation/blocking/unblocking, syscall enter/exit/block, @@ -19,7 +19,7 @@ // command runs the test in the current directory and writes the trace // file (trace.out). // -// go test -trace=trace.out +// go test -trace=trace.out // // This runtime/trace package provides APIs to add equivalent tracing // support to a standalone program. See the Example that demonstrates @@ -29,12 +29,12 @@ // following line will install a handler under the /debug/pprof/trace URL // to download a live trace: // -// import _ "net/http/pprof" +// import _ "net/http/pprof" // // See the net/http/pprof package for more details about all of the // debug endpoints installed by this import. // -// User annotation +// # User annotation // // Package trace provides user annotation APIs that can be used to // log interesting events during execution. @@ -55,16 +55,16 @@ // trace to trace the durations of sequential steps in a cappuccino making // operation. // -// trace.WithRegion(ctx, "makeCappuccino", func() { +// trace.WithRegion(ctx, "makeCappuccino", func() { // -// // orderID allows to identify a specific order -// // among many cappuccino order region records. -// trace.Log(ctx, "orderID", orderID) +// // orderID allows to identify a specific order +// // among many cappuccino order region records. +// trace.Log(ctx, "orderID", orderID) // -// trace.WithRegion(ctx, "steamMilk", steamMilk) -// trace.WithRegion(ctx, "extractCoffee", extractCoffee) -// trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee) -// }) +// trace.WithRegion(ctx, "steamMilk", steamMilk) +// trace.WithRegion(ctx, "extractCoffee", extractCoffee) +// trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee) +// }) // // A task is a higher-level component that aids tracing of logical // operations such as an RPC request, an HTTP request, or an @@ -80,27 +80,26 @@ // the trace tool can identify the goroutines involved in a specific // cappuccino order. // -// ctx, task := trace.NewTask(ctx, "makeCappuccino") -// trace.Log(ctx, "orderID", orderID) +// ctx, task := trace.NewTask(ctx, "makeCappuccino") +// trace.Log(ctx, "orderID", orderID) // -// milk := make(chan bool) -// espresso := make(chan bool) -// -// go func() { -// trace.WithRegion(ctx, "steamMilk", steamMilk) -// milk <- true -// }() -// go func() { -// trace.WithRegion(ctx, "extractCoffee", extractCoffee) -// espresso <- true -// }() -// go func() { -// defer task.End() // When assemble is done, the order is complete. -// <-espresso -// <-milk -// trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee) -// }() +// milk := make(chan bool) +// espresso := make(chan bool) // +// go func() { +// trace.WithRegion(ctx, "steamMilk", steamMilk) +// milk <- true +// }() +// go func() { +// trace.WithRegion(ctx, "extractCoffee", extractCoffee) +// espresso <- true +// }() +// go func() { +// defer task.End() // When assemble is done, the order is complete. +// <-espresso +// <-milk +// trace.WithRegion(ctx, "mixMilkCoffee", mixMilkCoffee) +// }() // // The trace tool computes the latency of a task by measuring the // time between the task creation and the task end and provides diff --git a/src/runtime/type.go b/src/runtime/type.go index a00394f3b3..b650d6d795 100644 --- a/src/runtime/type.go +++ b/src/runtime/type.go @@ -14,6 +14,7 @@ import ( // tflag is documented in reflect/type.go. // // tflag values must be kept in sync with copies in: +// // cmd/compile/internal/reflectdata/reflect.go // cmd/link/internal/ld/decodesym.go // reflect/type.go @@ -126,7 +127,14 @@ func (t *_type) name() string { } s := t.string() i := len(s) - 1 - for i >= 0 && s[i] != '.' { + sqBrackets := 0 + for i >= 0 && (s[i] != '.' || sqBrackets != 0) { + switch s[i] { + case ']': + sqBrackets++ + case '[': + sqBrackets-- + } i-- } return s[i+1:] diff --git a/src/runtime/vdso_linux.go b/src/runtime/vdso_linux.go index cff2000767..2ebdd44e94 100644 --- a/src/runtime/vdso_linux.go +++ b/src/runtime/vdso_linux.go @@ -280,6 +280,7 @@ func vdsoauxv(tag, val uintptr) { } // vdsoMarker reports whether PC is on the VDSO page. +// //go:nosplit func inVDSOPage(pc uintptr) bool { for _, k := range vdsoSymbolKeys { diff --git a/src/runtime/vlrt.go b/src/runtime/vlrt.go index 1dcb125aef..4b12f593c8 100644 --- a/src/runtime/vlrt.go +++ b/src/runtime/vlrt.go @@ -296,11 +296,14 @@ func slowdodiv(n, d uint64) (q, r uint64) { // Floating point control word values. // Bits 0-5 are bits to disable floating-point exceptions. // Bits 8-9 are the precision control: -// 0 = single precision a.k.a. float32 -// 2 = double precision a.k.a. float64 +// +// 0 = single precision a.k.a. float32 +// 2 = double precision a.k.a. float64 +// // Bits 10-11 are the rounding mode: -// 0 = round to nearest (even on a tie) -// 3 = round toward zero +// +// 0 = round to nearest (even on a tie) +// 3 = round toward zero var ( controlWord64 uint16 = 0x3f + 2<<8 + 0<<10 controlWord64trunc uint16 = 0x3f + 2<<8 + 3<<10 diff --git a/src/sort/example_multi_test.go b/src/sort/example_multi_test.go index de6ec142d1..93f2d3ec57 100644 --- a/src/sort/example_multi_test.go +++ b/src/sort/example_multi_test.go @@ -126,7 +126,7 @@ func Example_sortMultiKeys() { // By user: [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}] // By user,lines: [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken Go 200} {ken C 150} {r C 150} {r Go 100} {rsc Go 200}] - // By language,= %s[%s])", name, i, name, j) + }, "Less": func(name, i, j string) string { return fmt.Sprintf("(%s[%s] < %s[%s])", name, i, name, j) }, @@ -103,6 +106,9 @@ func main() { ExtraArg: ", less", DataType: "[]E", Funcs: template.FuncMap{ + "GreaterOrEqual": func(name, i, j string) string { + return fmt.Sprintf("!less(%s[%s], %s[%s])", name, i, name, j) + }, "Less": func(name, i, j string) string { return fmt.Sprintf("less(%s[%s], %s[%s])", name, i, name, j) }, @@ -123,6 +129,9 @@ func main() { ExtraArg: "", DataType: "Interface", Funcs: template.FuncMap{ + "GreaterOrEqual": func(name, i, j string) string { + return fmt.Sprintf("!%s.Less(%s, %s)", name, i, j) + }, "Less": func(name, i, j string) string { return fmt.Sprintf("%s.Less(%s, %s)", name, i, j) }, @@ -143,6 +152,9 @@ func main() { ExtraArg: "", DataType: "lessSwap", Funcs: template.FuncMap{ + "GreaterOrEqual": func(name, i, j string) string { + return fmt.Sprintf("!%s.Less(%s, %s)", name, i, j) + }, "Less": func(name, i, j string) string { return fmt.Sprintf("%s.Less(%s, %s)", name, i, j) }, @@ -210,7 +222,7 @@ func siftDown{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, lo, hi, first int if child+1 < hi && {{Less "data" "first+child" "first+child+1"}} { child++ } - if !{{Less "data" "first+root" "first+child"}} { + if {{GreaterOrEqual "data" "first+root" "first+child"}} { return } {{Swap "data" "first+root" "first+child"}} @@ -235,24 +247,278 @@ func heapSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.Extra } } -// Quicksort, loosely following Bentley and McIlroy, -// "Engineering a Sort Function" SP&E November 1993. +// pdqsort{{.FuncSuffix}} sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, limit int {{.ExtraParam}}) { + const maxInsertion = 12 -// medianOfThree{{.FuncSuffix}} moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. -func medianOfThree{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, m1, m0, m2 int {{.ExtraParam}}) { - // sort 3 elements - if {{Less "data" "m1" "m0"}} { - {{Swap "data" "m1" "m0"}} - } - // data[m0] <= data[m1] - if {{Less "data" "m2" "m1"}} { - {{Swap "data" "m2" "m1"}} - // data[m0] <= data[m2] && data[m1] < data[m2] - if {{Less "data" "m1" "m0"}} { - {{Swap "data" "m1" "m0"}} + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatterns{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + limit-- + } + + pivot, hint := choosePivot{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + if hint == decreasingHint { + reverseRange{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && {{GreaterOrEqual "data" "a-1" "pivot"}} { + mid := partitionEqual{{.FuncSuffix}}(data, a, b, pivot {{.ExtraArg}}) + a = mid + continue + } + + mid, alreadyPartitioned := partition{{.FuncSuffix}}(data, a, b, pivot {{.ExtraArg}}) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsort{{.FuncSuffix}}(data, a, mid, limit {{.ExtraArg}}) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsort{{.FuncSuffix}}(data, mid+1, b, limit {{.ExtraArg}}) + b = mid } } - // now data[m0] <= data[m1] <= data[m2] +} + +// partition{{.FuncSuffix}} does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partition{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, pivot int {{.ExtraParam}}) (newpivot int, alreadyPartitioned bool) { + {{Swap "data" "a" "pivot"}} + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && {{Less "data" "i" "a"}} { + i++ + } + for i <= j && {{GreaterOrEqual "data" "j" "a"}} { + j-- + } + if i > j { + {{Swap "data" "j" "a"}} + return j, true + } + {{Swap "data" "i" "j"}} + i++ + j-- + + for { + for i <= j && {{Less "data" "i" "a"}} { + i++ + } + for i <= j && {{GreaterOrEqual "data" "j" "a"}} { + j-- + } + if i > j { + break + } + {{Swap "data" "i" "j"}} + i++ + j-- + } + {{Swap "data" "j" "a"}} + return j, false +} + +// partitionEqual{{.FuncSuffix}} partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqual{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, pivot int {{.ExtraParam}}) (newpivot int) { + {{Swap "data" "a" "pivot"}} + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && {{GreaterOrEqual "data" "a" "i"}} { + i++ + } + for i <= j && {{Less "data" "a" "j"}} { + j-- + } + if i > j { + break + } + {{Swap "data" "i" "j"}} + i++ + j-- + } + return i +} + +// partialInsertionSort{{.FuncSuffix}} partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && {{GreaterOrEqual "data" "i" "i-1"}} { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + {{Swap "data" "i" "i-1"}} + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if {{GreaterOrEqual "data" "j" "j-1"}} { + break + } + {{Swap "data" "j" "j-1"}} + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if {{GreaterOrEqual "data" "j" "j-1"}} { + break + } + {{Swap "data" "j" "j-1"}} + } + } + } + return false +} + +// breakPatterns{{.FuncSuffix}} scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatterns{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a + (length/4)*2 + 1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + {{Swap "data" "idx" "a+other"}} + } + } +} + +// choosePivot{{.FuncSuffix}} chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivot{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacent{{.FuncSuffix}}(data, i, &swaps {{.ExtraArg}}) + j = medianAdjacent{{.FuncSuffix}}(data, j, &swaps {{.ExtraArg}}) + k = medianAdjacent{{.FuncSuffix}}(data, k, &swaps {{.ExtraArg}}) + } + // Find the median among i, j, k and stores it into j. + j = median{{.FuncSuffix}}(data, i, j, k, &swaps {{.ExtraArg}}) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2{{.FuncSuffix}} returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int, swaps *int {{.ExtraParam}}) (int, int) { + if {{Less "data" "b" "a"}} { + *swaps++ + return b, a + } + return a, b +} + +// median{{.FuncSuffix}} returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func median{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, c int, swaps *int {{.ExtraParam}}) int { + a, b = order2{{.FuncSuffix}}(data, a, b, swaps {{.ExtraArg}}) + b, c = order2{{.FuncSuffix}}(data, b, c, swaps {{.ExtraArg}}) + a, b = order2{{.FuncSuffix}}(data, a, b, swaps {{.ExtraArg}}) + return b +} + +// medianAdjacent{{.FuncSuffix}} finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacent{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a int, swaps *int {{.ExtraParam}}) int { + return median{{.FuncSuffix}}(data, a-1, a, a+1, swaps {{.ExtraArg}}) +} + +func reverseRange{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) { + i := a + j := b - 1 + for i < j { + {{Swap "data" "i" "j"}} + i++ + j-- + } } func swapRange{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, n int {{.ExtraParam}}) { @@ -261,123 +527,6 @@ func swapRange{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, n int {{.E } } -func doPivot{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, lo, hi int {{.ExtraParam}}) (midlo, midhi int) { - m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow. - if hi-lo > 40 { - // Tukey's "Ninther" median of three medians of three. - s := (hi - lo) / 8 - medianOfThree{{.FuncSuffix}}(data, lo, lo+s, lo+2*s {{.ExtraArg}}) - medianOfThree{{.FuncSuffix}}(data, m, m-s, m+s {{.ExtraArg}}) - medianOfThree{{.FuncSuffix}}(data, hi-1, hi-1-s, hi-1-2*s {{.ExtraArg}}) - } - medianOfThree{{.FuncSuffix}}(data, lo, m, hi-1 {{.ExtraArg}}) - - // Invariants are: - // data[lo] = pivot (set up by ChoosePivot) - // data[lo < i < a] < pivot - // data[a <= i < b] <= pivot - // data[b <= i < c] unexamined - // data[c <= i < hi-1] > pivot - // data[hi-1] >= pivot - pivot := lo - a, c := lo+1, hi-1 - - for ; a < c && {{Less "data" "a" "pivot"}}; a++ { - } - b := a - for { - for ; b < c && !{{Less "data" "pivot" "b"}}; b++ { // data[b] <= pivot - } - for ; b < c && {{Less "data" "pivot" "c-1"}}; c-- { // data[c-1] > pivot - } - if b >= c { - break - } - // data[b] > pivot; data[c-1] <= pivot - {{Swap "data" "b" "c-1"}} - b++ - c-- - } - // If hi-c<3 then there are duplicates (by property of median of nine). - // Let's be a bit more conservative, and set border to 5. - protect := hi-c < 5 - if !protect && hi-c < (hi-lo)/4 { - // Lets test some points for equality to pivot - dups := 0 - if !{{Less "data" "pivot" "hi-1"}} { // data[hi-1] = pivot - {{Swap "data" "c" "hi-1"}} - c++ - dups++ - } - if !{{Less "data" "b-1" "pivot"}} { // data[b-1] = pivot - b-- - dups++ - } - // m-lo = (hi-lo)/2 > 6 - // b-lo > (hi-lo)*3/4-1 > 8 - // ==> m < b ==> data[m] <= pivot - if !{{Less "data" "m" "pivot"}} { // data[m] = pivot - {{Swap "data" "m" "b-1"}} - b-- - dups++ - } - // if at least 2 points are equal to pivot, assume skewed distribution - protect = dups > 1 - } - if protect { - // Protect against a lot of duplicates - // Add invariant: - // data[a <= i < b] unexamined - // data[b <= i < c] = pivot - for { - for ; a < b && !{{Less "data" "b-1" "pivot"}}; b-- { // data[b] == pivot - } - for ; a < b && {{Less "data" "a" "pivot"}}; a++ { // data[a] < pivot - } - if a >= b { - break - } - // data[a] == pivot; data[b-1] < pivot - {{Swap "data" "a" "b-1"}} - a++ - b-- - } - } - // Swap pivot into middle - {{Swap "data" "pivot" "b-1"}} - return b - 1, c -} - -func quickSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, maxDepth int {{.ExtraParam}}) { - for b-a > 12 { // Use ShellSort for slices <= 12 elements - if maxDepth == 0 { - heapSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) - return - } - maxDepth-- - mlo, mhi := doPivot{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) - // Avoiding recursion on the larger subproblem guarantees - // a stack depth of at most lg(b-a). - if mlo-a < b-mhi { - quickSort{{.FuncSuffix}}(data, a, mlo, maxDepth {{.ExtraArg}}) - a = mhi // i.e., quickSort{{.FuncSuffix}}(data, mhi, b) - } else { - quickSort{{.FuncSuffix}}(data, mhi, b, maxDepth {{.ExtraArg}}) - b = mlo // i.e., quickSort{{.FuncSuffix}}(data, a, mlo) - } - } - if b-a > 1 { - // Do ShellSort pass with gap 6 - // It could be written in this simplified form cause b-a <= 12 - for i := a + 6; i < b; i++ { - if {{Less "data" "i" "i-6"}} { - {{Swap "data" "i" "i-6"}} - } - } - insertionSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) - } -} - func stable{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, n int {{.ExtraParam}}) { blockSize := 20 // must be > 0 a, b := 0, blockSize @@ -457,7 +606,7 @@ func symMerge{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, m, b int {{.Ex j := m for i < j { h := int(uint(i+j) >> 1) - if !{{Less "data" "m" "h"}} { + if {{GreaterOrEqual "data" "m" "h"}} { i = h + 1 } else { j = h @@ -484,7 +633,7 @@ func symMerge{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, m, b int {{.Ex for start < r { c := int(uint(start+r) >> 1) - if !{{Less "data" "p-c" "c"}} { + if {{GreaterOrEqual "data" "p-c" "c"}} { start = c + 1 } else { r = c diff --git a/src/sort/search.go b/src/sort/search.go index 601557a94b..434349416e 100644 --- a/src/sort/search.go +++ b/src/sort/search.go @@ -72,6 +72,47 @@ func Search(n int, f func(int) bool) int { return i } +// Find uses binary search to find and return the smallest index i in [0, n) +// at which cmp(i) <= 0. If there is no such index i, Find returns i = n. +// The found result is true if i < n and cmp(i) == 0. +// Find calls cmp(i) only for i in the range [0, n). +// +// To permit binary search, Find requires that cmp(i) > 0 for a leading +// prefix of the range, cmp(i) == 0 in the middle, and cmp(i) < 0 for +// the final suffix of the range. (Each subrange could be empty.) +// The usual way to establish this condition is to interpret cmp(i) +// as a comparison of a desired target value t against entry i in an +// underlying indexed data structure x, returning <0, 0, and >0 +// when t < x[i], t == x[i], and t > x[i], respectively. +// +// For example, to look for a particular string in a sorted, random-access +// list of strings: +// i, found := sort.Find(x.Len(), func(i int) int { +// return strings.Compare(target, x.At(i)) +// }) +// if found { +// fmt.Printf("found %s at entry %d\n", target, i) +// } else { +// fmt.Printf("%s not found, would insert at %d", target, i) +// } +func Find(n int, cmp func(int) int) (i int, found bool) { + // The invariants here are similar to the ones in Search. + // Define cmp(-1) > 0 and cmp(n) <= 0 + // Invariant: cmp(i-1) > 0, cmp(j) <= 0 + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) // avoid overflow when computing h + // i ≤ h < j + if cmp(h) > 0 { + i = h + 1 // preserves cmp(i-1) > 0 + } else { + j = h // preserves cmp(j) <= 0 + } + } + // i == j, cmp(i-1) > 0 and cmp(j) <= 0 + return i, i < n && cmp(i) == 0 +} + // Convenience wrappers for common cases. // SearchInts searches for x in a sorted slice of ints and returns the index diff --git a/src/sort/search_test.go b/src/sort/search_test.go index f06897ee21..49813eaecb 100644 --- a/src/sort/search_test.go +++ b/src/sort/search_test.go @@ -7,6 +7,7 @@ package sort_test import ( "runtime" . "sort" + stringspkg "strings" "testing" ) @@ -57,6 +58,80 @@ func TestSearch(t *testing.T) { } } +func TestFind(t *testing.T) { + str1 := []string{"foo"} + str2 := []string{"ab", "ca"} + str3 := []string{"mo", "qo", "vo"} + str4 := []string{"ab", "ad", "ca", "xy"} + + // slice with repeating elements + strRepeats := []string{"ba", "ca", "da", "da", "da", "ka", "ma", "ma", "ta"} + + // slice with all element equal + strSame := []string{"xx", "xx", "xx"} + + tests := []struct { + data []string + target string + wantPos int + wantFound bool + }{ + {[]string{}, "foo", 0, false}, + {[]string{}, "", 0, false}, + + {str1, "foo", 0, true}, + {str1, "bar", 0, false}, + {str1, "zx", 1, false}, + + {str2, "aa", 0, false}, + {str2, "ab", 0, true}, + {str2, "ad", 1, false}, + {str2, "ca", 1, true}, + {str2, "ra", 2, false}, + + {str3, "bb", 0, false}, + {str3, "mo", 0, true}, + {str3, "nb", 1, false}, + {str3, "qo", 1, true}, + {str3, "tr", 2, false}, + {str3, "vo", 2, true}, + {str3, "xr", 3, false}, + + {str4, "aa", 0, false}, + {str4, "ab", 0, true}, + {str4, "ac", 1, false}, + {str4, "ad", 1, true}, + {str4, "ax", 2, false}, + {str4, "ca", 2, true}, + {str4, "cc", 3, false}, + {str4, "dd", 3, false}, + {str4, "xy", 3, true}, + {str4, "zz", 4, false}, + + {strRepeats, "da", 2, true}, + {strRepeats, "db", 5, false}, + {strRepeats, "ma", 6, true}, + {strRepeats, "mb", 8, false}, + + {strSame, "xx", 0, true}, + {strSame, "ab", 0, false}, + {strSame, "zz", 3, false}, + } + + for _, tt := range tests { + t.Run(tt.target, func(t *testing.T) { + cmp := func(i int) int { + return stringspkg.Compare(tt.target, tt.data[i]) + } + + pos, found := Find(len(tt.data), cmp) + if pos != tt.wantPos || found != tt.wantFound { + t.Errorf("Find got (%v, %v), want (%v, %v)", pos, found, tt.wantPos, tt.wantFound) + } + }) + } +} + // log2 computes the binary logarithm of x, rounded up to the next integer. // (log2(0) == 0, log2(1) == 0, log2(2) == 1, log2(3) == 2, etc.) func log2(x int) int { @@ -158,3 +233,34 @@ func TestSearchExhaustive(t *testing.T) { } } } + +// Abstract exhaustive test for Find. +func TestFindExhaustive(t *testing.T) { + // Test Find for different sequence sizes and search targets. + // For each size, we have a (unmaterialized) sequence of integers: + // 2,4...size*2 + // And we're looking for every possible integer between 1 and size*2 + 1. + for size := 0; size <= 100; size++ { + for x := 1; x <= size*2+1; x++ { + var wantFound bool + var wantPos int + + cmp := func(i int) int { + // Encodes the unmaterialized sequence with elem[i] == (i+1)*2 + return x - (i+1)*2 + } + pos, found := Find(size, cmp) + + if x%2 == 0 { + wantPos = x/2 - 1 + wantFound = true + } else { + wantPos = x / 2 + wantFound = false + } + if found != wantFound || pos != wantPos { + t.Errorf("Find(%d, %d): got (%v, %v), want (%v, %v)", size, x, pos, found, wantPos, wantFound) + } + } + } +} diff --git a/src/sort/slice.go b/src/sort/slice.go index ba5c2e2f3d..443182b42e 100644 --- a/src/sort/slice.go +++ b/src/sort/slice.go @@ -4,6 +4,8 @@ package sort +import "math/bits" + // Slice sorts the slice x given the provided less function. // It panics if x is not a slice. // @@ -17,7 +19,8 @@ func Slice(x any, less func(i, j int) bool) { rv := reflectValueOf(x) swap := reflectSwapper(x) length := rv.Len() - quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length)) + limit := bits.Len(uint(length)) + pdqsort_func(lessSwap{less, swap}, 0, length, limit) } // SliceStable sorts the slice x using the provided less diff --git a/src/sort/sort.go b/src/sort/sort.go index aed0eaba30..68e2f0d082 100644 --- a/src/sort/sort.go +++ b/src/sort/sort.go @@ -7,6 +7,8 @@ // Package sort provides primitives for sorting slices and user-defined collections. package sort +import "math/bits" + // An implementation of Interface can be sorted by the routines in this package. // The methods refer to elements of the underlying collection by integer index. type Interface interface { @@ -39,17 +41,34 @@ type Interface interface { // data.Less and data.Swap. The sort is not guaranteed to be stable. func Sort(data Interface) { n := data.Len() - quickSort(data, 0, n, maxDepth(n)) + if n <= 1 { + return + } + limit := bits.Len(uint(n)) + pdqsort(data, 0, n, limit) } -// maxDepth returns a threshold at which quicksort should switch -// to heapsort. It returns 2*ceil(lg(n+1)). -func maxDepth(n int) int { - var depth int - for i := n; i > 0; i >>= 1 { - depth++ - } - return depth * 2 +type sortedHint int // hint for pdqsort when choosing the pivot + +const ( + unknownHint sortedHint = iota + increasingHint + decreasingHint +) + +// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf +type xorshift uint64 + +func (r *xorshift) Next() uint64 { + *r ^= *r << 13 + *r ^= *r >> 17 + *r ^= *r << 5 + return uint64(*r) +} + +func nextPowerOfTwo(length int) uint { + shift := uint(bits.Len(uint(length))) + return uint(1 << shift) } // lessSwap is a pair of Less and Swap function for use with the diff --git a/src/sort/sort_test.go b/src/sort/sort_test.go index bfff3528d3..862bba2d44 100644 --- a/src/sort/sort_test.go +++ b/src/sort/sort_test.go @@ -122,6 +122,37 @@ func TestReverseSortIntSlice(t *testing.T) { } } +func TestBreakPatterns(t *testing.T) { + // Special slice used to trigger breakPatterns. + data := make([]int, 30) + for i := range data { + data[i] = 10 + } + data[(len(data)/4)*1] = 0 + data[(len(data)/4)*2] = 1 + data[(len(data)/4)*3] = 2 + Sort(IntSlice(data)) +} + +func TestReverseRange(t *testing.T) { + data := []int{1, 2, 3, 4, 5, 6, 7} + ReverseRange(IntSlice(data), 0, len(data)) + for i := len(data) - 1; i > 0; i-- { + if data[i] > data[i-1] { + t.Fatalf("reverseRange didn't work") + } + } + + data1 := []int{1, 2, 3, 4, 5, 6, 7} + data2 := []int{1, 2, 5, 4, 3, 6, 7} + ReverseRange(IntSlice(data1), 2, 5) + for i, v := range data1 { + if v != data2[i] { + t.Fatalf("reverseRange didn't work") + } + } +} + type nonDeterministicTestingData struct { r *rand.Rand } @@ -220,6 +251,45 @@ func BenchmarkSortInt1K(b *testing.B) { } } +func BenchmarkSortInt1K_Sorted(b *testing.B) { + b.StopTimer() + for i := 0; i < b.N; i++ { + data := make([]int, 1<<10) + for i := 0; i < len(data); i++ { + data[i] = i + } + b.StartTimer() + Ints(data) + b.StopTimer() + } +} + +func BenchmarkSortInt1K_Reversed(b *testing.B) { + b.StopTimer() + for i := 0; i < b.N; i++ { + data := make([]int, 1<<10) + for i := 0; i < len(data); i++ { + data[i] = len(data) - i + } + b.StartTimer() + Ints(data) + b.StopTimer() + } +} + +func BenchmarkSortInt1K_Mod8(b *testing.B) { + b.StopTimer() + for i := 0; i < b.N; i++ { + data := make([]int, 1<<10) + for i := 0; i < len(data); i++ { + data[i] = i % 8 + } + b.StartTimer() + Ints(data) + b.StopTimer() + } +} + func BenchmarkStableInt1K(b *testing.B) { b.StopTimer() unsorted := make([]int, 1<<10) diff --git a/src/sort/zsortfunc.go b/src/sort/zsortfunc.go index 80c8a77995..49b6169b97 100644 --- a/src/sort/zsortfunc.go +++ b/src/sort/zsortfunc.go @@ -52,24 +52,278 @@ func heapSort_func(data lessSwap, a, b int) { } } -// Quicksort, loosely following Bentley and McIlroy, -// "Engineering a Sort Function" SP&E November 1993. +// pdqsort_func sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsort_func(data lessSwap, a, b, limit int) { + const maxInsertion = 12 -// medianOfThree_func moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. -func medianOfThree_func(data lessSwap, m1, m0, m2 int) { - // sort 3 elements - if data.Less(m1, m0) { - data.Swap(m1, m0) - } - // data[m0] <= data[m1] - if data.Less(m2, m1) { - data.Swap(m2, m1) - // data[m0] <= data[m2] && data[m1] < data[m2] - if data.Less(m1, m0) { - data.Swap(m1, m0) + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSort_func(data, a, b) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSort_func(data, a, b) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatterns_func(data, a, b) + limit-- + } + + pivot, hint := choosePivot_func(data, a, b) + if hint == decreasingHint { + reverseRange_func(data, a, b) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSort_func(data, a, b) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !data.Less(a-1, pivot) { + mid := partitionEqual_func(data, a, b, pivot) + a = mid + continue + } + + mid, alreadyPartitioned := partition_func(data, a, b, pivot) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsort_func(data, a, mid, limit) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsort_func(data, mid+1, b, limit) + b = mid } } - // now data[m0] <= data[m1] <= data[m2] +} + +// partition_func does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partition_func(data lessSwap, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { + data.Swap(a, pivot) + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && data.Less(i, a) { + i++ + } + for i <= j && !data.Less(j, a) { + j-- + } + if i > j { + data.Swap(j, a) + return j, true + } + data.Swap(i, j) + i++ + j-- + + for { + for i <= j && data.Less(i, a) { + i++ + } + for i <= j && !data.Less(j, a) { + j-- + } + if i > j { + break + } + data.Swap(i, j) + i++ + j-- + } + data.Swap(j, a) + return j, false +} + +// partitionEqual_func partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqual_func(data lessSwap, a, b, pivot int) (newpivot int) { + data.Swap(a, pivot) + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !data.Less(a, i) { + i++ + } + for i <= j && data.Less(a, j) { + j-- + } + if i > j { + break + } + data.Swap(i, j) + i++ + j-- + } + return i +} + +// partialInsertionSort_func partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSort_func(data lessSwap, a, b int) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !data.Less(i, i-1) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data.Swap(i, i-1) + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !data.Less(j, j-1) { + break + } + data.Swap(j, j-1) + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !data.Less(j, j-1) { + break + } + data.Swap(j, j-1) + } + } + } + return false +} + +// breakPatterns_func scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatterns_func(data lessSwap, a, b int) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data.Swap(idx, a+other) + } + } +} + +// choosePivot_func chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivot_func(data lessSwap, a, b int) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacent_func(data, i, &swaps) + j = medianAdjacent_func(data, j, &swaps) + k = medianAdjacent_func(data, k, &swaps) + } + // Find the median among i, j, k and stores it into j. + j = median_func(data, i, j, k, &swaps) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2_func returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2_func(data lessSwap, a, b int, swaps *int) (int, int) { + if data.Less(b, a) { + *swaps++ + return b, a + } + return a, b +} + +// median_func returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func median_func(data lessSwap, a, b, c int, swaps *int) int { + a, b = order2_func(data, a, b, swaps) + b, c = order2_func(data, b, c, swaps) + a, b = order2_func(data, a, b, swaps) + return b +} + +// medianAdjacent_func finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacent_func(data lessSwap, a int, swaps *int) int { + return median_func(data, a-1, a, a+1, swaps) +} + +func reverseRange_func(data lessSwap, a, b int) { + i := a + j := b - 1 + for i < j { + data.Swap(i, j) + i++ + j-- + } } func swapRange_func(data lessSwap, a, b, n int) { @@ -78,123 +332,6 @@ func swapRange_func(data lessSwap, a, b, n int) { } } -func doPivot_func(data lessSwap, lo, hi int) (midlo, midhi int) { - m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow. - if hi-lo > 40 { - // Tukey's "Ninther" median of three medians of three. - s := (hi - lo) / 8 - medianOfThree_func(data, lo, lo+s, lo+2*s) - medianOfThree_func(data, m, m-s, m+s) - medianOfThree_func(data, hi-1, hi-1-s, hi-1-2*s) - } - medianOfThree_func(data, lo, m, hi-1) - - // Invariants are: - // data[lo] = pivot (set up by ChoosePivot) - // data[lo < i < a] < pivot - // data[a <= i < b] <= pivot - // data[b <= i < c] unexamined - // data[c <= i < hi-1] > pivot - // data[hi-1] >= pivot - pivot := lo - a, c := lo+1, hi-1 - - for ; a < c && data.Less(a, pivot); a++ { - } - b := a - for { - for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot - } - for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot - } - if b >= c { - break - } - // data[b] > pivot; data[c-1] <= pivot - data.Swap(b, c-1) - b++ - c-- - } - // If hi-c<3 then there are duplicates (by property of median of nine). - // Let's be a bit more conservative, and set border to 5. - protect := hi-c < 5 - if !protect && hi-c < (hi-lo)/4 { - // Lets test some points for equality to pivot - dups := 0 - if !data.Less(pivot, hi-1) { // data[hi-1] = pivot - data.Swap(c, hi-1) - c++ - dups++ - } - if !data.Less(b-1, pivot) { // data[b-1] = pivot - b-- - dups++ - } - // m-lo = (hi-lo)/2 > 6 - // b-lo > (hi-lo)*3/4-1 > 8 - // ==> m < b ==> data[m] <= pivot - if !data.Less(m, pivot) { // data[m] = pivot - data.Swap(m, b-1) - b-- - dups++ - } - // if at least 2 points are equal to pivot, assume skewed distribution - protect = dups > 1 - } - if protect { - // Protect against a lot of duplicates - // Add invariant: - // data[a <= i < b] unexamined - // data[b <= i < c] = pivot - for { - for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot - } - for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot - } - if a >= b { - break - } - // data[a] == pivot; data[b-1] < pivot - data.Swap(a, b-1) - a++ - b-- - } - } - // Swap pivot into middle - data.Swap(pivot, b-1) - return b - 1, c -} - -func quickSort_func(data lessSwap, a, b, maxDepth int) { - for b-a > 12 { // Use ShellSort for slices <= 12 elements - if maxDepth == 0 { - heapSort_func(data, a, b) - return - } - maxDepth-- - mlo, mhi := doPivot_func(data, a, b) - // Avoiding recursion on the larger subproblem guarantees - // a stack depth of at most lg(b-a). - if mlo-a < b-mhi { - quickSort_func(data, a, mlo, maxDepth) - a = mhi // i.e., quickSort_func(data, mhi, b) - } else { - quickSort_func(data, mhi, b, maxDepth) - b = mlo // i.e., quickSort_func(data, a, mlo) - } - } - if b-a > 1 { - // Do ShellSort pass with gap 6 - // It could be written in this simplified form cause b-a <= 12 - for i := a + 6; i < b; i++ { - if data.Less(i, i-6) { - data.Swap(i, i-6) - } - } - insertionSort_func(data, a, b) - } -} - func stable_func(data lessSwap, n int) { blockSize := 20 // must be > 0 a, b := 0, blockSize diff --git a/src/sort/zsortinterface.go b/src/sort/zsortinterface.go index e0d7093678..51fa5032e9 100644 --- a/src/sort/zsortinterface.go +++ b/src/sort/zsortinterface.go @@ -52,24 +52,278 @@ func heapSort(data Interface, a, b int) { } } -// Quicksort, loosely following Bentley and McIlroy, -// "Engineering a Sort Function" SP&E November 1993. +// pdqsort sorts data[a:b]. +// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort. +// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf +// C++ implementation: https://github.com/orlp/pdqsort +// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ +// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort. +func pdqsort(data Interface, a, b, limit int) { + const maxInsertion = 12 -// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. -func medianOfThree(data Interface, m1, m0, m2 int) { - // sort 3 elements - if data.Less(m1, m0) { - data.Swap(m1, m0) - } - // data[m0] <= data[m1] - if data.Less(m2, m1) { - data.Swap(m2, m1) - // data[m0] <= data[m2] && data[m1] < data[m2] - if data.Less(m1, m0) { - data.Swap(m1, m0) + var ( + wasBalanced = true // whether the last partitioning was reasonably balanced + wasPartitioned = true // whether the slice was already partitioned + ) + + for { + length := b - a + + if length <= maxInsertion { + insertionSort(data, a, b) + return + } + + // Fall back to heapsort if too many bad choices were made. + if limit == 0 { + heapSort(data, a, b) + return + } + + // If the last partitioning was imbalanced, we need to breaking patterns. + if !wasBalanced { + breakPatterns(data, a, b) + limit-- + } + + pivot, hint := choosePivot(data, a, b) + if hint == decreasingHint { + reverseRange(data, a, b) + // The chosen pivot was pivot-a elements after the start of the array. + // After reversing it is pivot-a elements before the end of the array. + // The idea came from Rust's implementation. + pivot = (b - 1) - (pivot - a) + hint = increasingHint + } + + // The slice is likely already sorted. + if wasBalanced && wasPartitioned && hint == increasingHint { + if partialInsertionSort(data, a, b) { + return + } + } + + // Probably the slice contains many duplicate elements, partition the slice into + // elements equal to and elements greater than the pivot. + if a > 0 && !data.Less(a-1, pivot) { + mid := partitionEqual(data, a, b, pivot) + a = mid + continue + } + + mid, alreadyPartitioned := partition(data, a, b, pivot) + wasPartitioned = alreadyPartitioned + + leftLen, rightLen := mid-a, b-mid + balanceThreshold := length / 8 + if leftLen < rightLen { + wasBalanced = leftLen >= balanceThreshold + pdqsort(data, a, mid, limit) + a = mid + 1 + } else { + wasBalanced = rightLen >= balanceThreshold + pdqsort(data, mid+1, b, limit) + b = mid } } - // now data[m0] <= data[m1] <= data[m2] +} + +// partition does one quicksort partition. +// Let p = data[pivot] +// Moves elements in data[a:b] around, so that data[i]

=p for inewpivot. +// On return, data[newpivot] = p +func partition(data Interface, a, b, pivot int) (newpivot int, alreadyPartitioned bool) { + data.Swap(a, pivot) + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for i <= j && data.Less(i, a) { + i++ + } + for i <= j && !data.Less(j, a) { + j-- + } + if i > j { + data.Swap(j, a) + return j, true + } + data.Swap(i, j) + i++ + j-- + + for { + for i <= j && data.Less(i, a) { + i++ + } + for i <= j && !data.Less(j, a) { + j-- + } + if i > j { + break + } + data.Swap(i, j) + i++ + j-- + } + data.Swap(j, a) + return j, false +} + +// partitionEqual partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot]. +// It assumed that data[a:b] does not contain elements smaller than the data[pivot]. +func partitionEqual(data Interface, a, b, pivot int) (newpivot int) { + data.Swap(a, pivot) + i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned + + for { + for i <= j && !data.Less(a, i) { + i++ + } + for i <= j && data.Less(a, j) { + j-- + } + if i > j { + break + } + data.Swap(i, j) + i++ + j-- + } + return i +} + +// partialInsertionSort partially sorts a slice, returns true if the slice is sorted at the end. +func partialInsertionSort(data Interface, a, b int) bool { + const ( + maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted + shortestShifting = 50 // don't shift any elements on short arrays + ) + i := a + 1 + for j := 0; j < maxSteps; j++ { + for i < b && !data.Less(i, i-1) { + i++ + } + + if i == b { + return true + } + + if b-a < shortestShifting { + return false + } + + data.Swap(i, i-1) + + // Shift the smaller one to the left. + if i-a >= 2 { + for j := i - 1; j >= 1; j-- { + if !data.Less(j, j-1) { + break + } + data.Swap(j, j-1) + } + } + // Shift the greater one to the right. + if b-i >= 2 { + for j := i + 1; j < b; j++ { + if !data.Less(j, j-1) { + break + } + data.Swap(j, j-1) + } + } + } + return false +} + +// breakPatterns scatters some elements around in an attempt to break some patterns +// that might cause imbalanced partitions in quicksort. +func breakPatterns(data Interface, a, b int) { + length := b - a + if length >= 8 { + random := xorshift(length) + modulus := nextPowerOfTwo(length) + + for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ { + other := int(uint(random.Next()) & (modulus - 1)) + if other >= length { + other -= length + } + data.Swap(idx, a+other) + } + } +} + +// choosePivot chooses a pivot in data[a:b]. +// +// [0,8): chooses a static pivot. +// [8,shortestNinther): uses the simple median-of-three method. +// [shortestNinther,∞): uses the Tukey ninther method. +func choosePivot(data Interface, a, b int) (pivot int, hint sortedHint) { + const ( + shortestNinther = 50 + maxSwaps = 4 * 3 + ) + + l := b - a + + var ( + swaps int + i = a + l/4*1 + j = a + l/4*2 + k = a + l/4*3 + ) + + if l >= 8 { + if l >= shortestNinther { + // Tukey ninther method, the idea came from Rust's implementation. + i = medianAdjacent(data, i, &swaps) + j = medianAdjacent(data, j, &swaps) + k = medianAdjacent(data, k, &swaps) + } + // Find the median among i, j, k and stores it into j. + j = median(data, i, j, k, &swaps) + } + + switch swaps { + case 0: + return j, increasingHint + case maxSwaps: + return j, decreasingHint + default: + return j, unknownHint + } +} + +// order2 returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a. +func order2(data Interface, a, b int, swaps *int) (int, int) { + if data.Less(b, a) { + *swaps++ + return b, a + } + return a, b +} + +// median returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c. +func median(data Interface, a, b, c int, swaps *int) int { + a, b = order2(data, a, b, swaps) + b, c = order2(data, b, c, swaps) + a, b = order2(data, a, b, swaps) + return b +} + +// medianAdjacent finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a. +func medianAdjacent(data Interface, a int, swaps *int) int { + return median(data, a-1, a, a+1, swaps) +} + +func reverseRange(data Interface, a, b int) { + i := a + j := b - 1 + for i < j { + data.Swap(i, j) + i++ + j-- + } } func swapRange(data Interface, a, b, n int) { @@ -78,123 +332,6 @@ func swapRange(data Interface, a, b, n int) { } } -func doPivot(data Interface, lo, hi int) (midlo, midhi int) { - m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow. - if hi-lo > 40 { - // Tukey's "Ninther" median of three medians of three. - s := (hi - lo) / 8 - medianOfThree(data, lo, lo+s, lo+2*s) - medianOfThree(data, m, m-s, m+s) - medianOfThree(data, hi-1, hi-1-s, hi-1-2*s) - } - medianOfThree(data, lo, m, hi-1) - - // Invariants are: - // data[lo] = pivot (set up by ChoosePivot) - // data[lo < i < a] < pivot - // data[a <= i < b] <= pivot - // data[b <= i < c] unexamined - // data[c <= i < hi-1] > pivot - // data[hi-1] >= pivot - pivot := lo - a, c := lo+1, hi-1 - - for ; a < c && data.Less(a, pivot); a++ { - } - b := a - for { - for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot - } - for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot - } - if b >= c { - break - } - // data[b] > pivot; data[c-1] <= pivot - data.Swap(b, c-1) - b++ - c-- - } - // If hi-c<3 then there are duplicates (by property of median of nine). - // Let's be a bit more conservative, and set border to 5. - protect := hi-c < 5 - if !protect && hi-c < (hi-lo)/4 { - // Lets test some points for equality to pivot - dups := 0 - if !data.Less(pivot, hi-1) { // data[hi-1] = pivot - data.Swap(c, hi-1) - c++ - dups++ - } - if !data.Less(b-1, pivot) { // data[b-1] = pivot - b-- - dups++ - } - // m-lo = (hi-lo)/2 > 6 - // b-lo > (hi-lo)*3/4-1 > 8 - // ==> m < b ==> data[m] <= pivot - if !data.Less(m, pivot) { // data[m] = pivot - data.Swap(m, b-1) - b-- - dups++ - } - // if at least 2 points are equal to pivot, assume skewed distribution - protect = dups > 1 - } - if protect { - // Protect against a lot of duplicates - // Add invariant: - // data[a <= i < b] unexamined - // data[b <= i < c] = pivot - for { - for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot - } - for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot - } - if a >= b { - break - } - // data[a] == pivot; data[b-1] < pivot - data.Swap(a, b-1) - a++ - b-- - } - } - // Swap pivot into middle - data.Swap(pivot, b-1) - return b - 1, c -} - -func quickSort(data Interface, a, b, maxDepth int) { - for b-a > 12 { // Use ShellSort for slices <= 12 elements - if maxDepth == 0 { - heapSort(data, a, b) - return - } - maxDepth-- - mlo, mhi := doPivot(data, a, b) - // Avoiding recursion on the larger subproblem guarantees - // a stack depth of at most lg(b-a). - if mlo-a < b-mhi { - quickSort(data, a, mlo, maxDepth) - a = mhi // i.e., quickSort(data, mhi, b) - } else { - quickSort(data, mhi, b, maxDepth) - b = mlo // i.e., quickSort(data, a, mlo) - } - } - if b-a > 1 { - // Do ShellSort pass with gap 6 - // It could be written in this simplified form cause b-a <= 12 - for i := a + 6; i < b; i++ { - if data.Less(i, i-6) { - data.Swap(i, i-6) - } - } - insertionSort(data, a, b) - } -} - func stable(data Interface, n int) { blockSize := 20 // must be > 0 a, b := 0, blockSize diff --git a/src/strconv/atof.go b/src/strconv/atof.go index 57556c7047..60098efed0 100644 --- a/src/strconv/atof.go +++ b/src/strconv/atof.go @@ -420,9 +420,11 @@ var float32pow10 = []float32{1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1 // If possible to convert decimal representation to 64-bit float f exactly, // entirely in floating-point math, do so, avoiding the expense of decimalToFloatBits. // Three common cases: +// // value is exact integer // value is exact integer * exact power of ten // value is exact integer / exact power of ten +// // These all produce potentially inexact but correctly rounded answers. func atof64exact(mantissa uint64, exp int, neg bool) (f float64, ok bool) { if mantissa>>float64info.mantbits != 0 { diff --git a/src/strconv/doc.go b/src/strconv/doc.go index 8db725f96a..769ecd9a21 100644 --- a/src/strconv/doc.go +++ b/src/strconv/doc.go @@ -5,7 +5,7 @@ // Package strconv implements conversions to and from string representations // of basic data types. // -// Numeric Conversions +// # Numeric Conversions // // The most common numeric conversions are Atoi (string to int) and Itoa (int to string). // @@ -40,7 +40,7 @@ // AppendBool, AppendFloat, AppendInt, and AppendUint are similar but // append the formatted value to a destination slice. // -// String Conversions +// # String Conversions // // Quote and QuoteToASCII convert strings to quoted Go string literals. // The latter guarantees that the result is an ASCII string, by escaping @@ -53,5 +53,4 @@ // return quoted Go rune literals. // // Unquote and UnquoteChar unquote Go string and rune literals. -// package strconv diff --git a/src/strconv/eisel_lemire.go b/src/strconv/eisel_lemire.go index fecd1b9345..03842e5079 100644 --- a/src/strconv/eisel_lemire.go +++ b/src/strconv/eisel_lemire.go @@ -176,8 +176,8 @@ const ( // detailedPowersOfTen contains 128-bit mantissa approximations (rounded down) // to the powers of 10. For example: // -// - 1e43 ≈ (0xE596B7B0_C643C719 * (2 ** 79)) -// - 1e43 = (0xE596B7B0_C643C719_6D9CCD05_D0000000 * (2 ** 15)) +// - 1e43 ≈ (0xE596B7B0_C643C719 * (2 ** 79)) +// - 1e43 = (0xE596B7B0_C643C719_6D9CCD05_D0000000 * (2 ** 15)) // // The mantissas are explicitly listed. The exponents are implied by a linear // expression with slope 217706.0/65536.0 ≈ log(10)/log(2). diff --git a/src/strconv/ftoaryu.go b/src/strconv/ftoaryu.go index f2e74bed17..b975cdc9b9 100644 --- a/src/strconv/ftoaryu.go +++ b/src/strconv/ftoaryu.go @@ -487,8 +487,9 @@ func ryuDigits32(d *decimalSlice, lower, central, upper uint32, // The returned boolean is true if all trimmed bits were zero. // // That is: -// m*2^e2 * round(10^q) = resM * 2^resE + ε -// exact = ε == 0 +// +// m*2^e2 * round(10^q) = resM * 2^resE + ε +// exact = ε == 0 func mult64bitPow10(m uint32, e2, q int) (resM uint32, resE int, exact bool) { if q == 0 { // P == 1<<63 @@ -515,8 +516,9 @@ func mult64bitPow10(m uint32, e2, q int) (resM uint32, resE int, exact bool) { // The returned boolean is true is all trimmed bits were zero. // // That is: -// m*2^e2 * round(10^q) = resM * 2^resE + ε -// exact = ε == 0 +// +// m*2^e2 * round(10^q) = resM * 2^resE + ε +// exact = ε == 0 func mult128bitPow10(m uint64, e2, q int) (resM uint64, resE int, exact bool) { if q == 0 { // P == 1<<127 diff --git a/src/strconv/quote.go b/src/strconv/quote.go index 6c022846c0..1b5bddfeae 100644 --- a/src/strconv/quote.go +++ b/src/strconv/quote.go @@ -249,10 +249,10 @@ func unhex(b byte) (v rune, ok bool) { // or character literal represented by the string s. // It returns four values: // -// 1) value, the decoded Unicode code point or byte value; -// 2) multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; -// 3) tail, the remainder of the string after the character; and -// 4) an error that will be nil if the character is syntactically valid. +// 1. value, the decoded Unicode code point or byte value; +// 2. multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; +// 3. tail, the remainder of the string after the character; and +// 4. an error that will be nil if the character is syntactically valid. // // The second argument, quote, specifies the type of literal being parsed // and therefore which escaped quote character is permitted. diff --git a/src/strings/builder.go b/src/strings/builder.go index ba4df618bf..3caddabd4e 100644 --- a/src/strings/builder.go +++ b/src/strings/builder.go @@ -22,6 +22,7 @@ type Builder struct { // noescape is inlined and currently compiles down to zero instructions. // USE CAREFULLY! // This was copied from the runtime; see issues 23382 and 7921. +// //go:nosplit //go:nocheckptr func noescape(p unsafe.Pointer) unsafe.Pointer { diff --git a/src/strings/replace.go b/src/strings/replace.go index ee728bb22b..73bc78a07e 100644 --- a/src/strings/replace.go +++ b/src/strings/replace.go @@ -107,14 +107,14 @@ func (r *Replacer) WriteString(w io.Writer, s string) (n int, err error) { // and values may be empty. For example, the trie containing keys "ax", "ay", // "bcbc", "x" and "xy" could have eight nodes: // -// n0 - -// n1 a- -// n2 .x+ -// n3 .y+ -// n4 b- -// n5 .cbc+ -// n6 x+ -// n7 .y+ +// n0 - +// n1 a- +// n2 .x+ +// n3 .y+ +// n4 b- +// n5 .cbc+ +// n6 x+ +// n7 .y+ // // n0 is the root node, and its children are n1, n4 and n6; n1's children are // n2 and n3; n4's child is n5; n6's child is n7. Nodes n0, n1 and n4 (marked diff --git a/src/strings/strings.go b/src/strings/strings.go index 74e505338e..a563f37cf5 100644 --- a/src/strings/strings.go +++ b/src/strings/strings.go @@ -267,9 +267,10 @@ func genSplit(s, sep string, sepSave, n int) []string { // the substrings between those separators. // // The count determines the number of substrings to return: -// n > 0: at most n substrings; the last substring will be the unsplit remainder. -// n == 0: the result is nil (zero substrings) -// n < 0: all substrings +// +// n > 0: at most n substrings; the last substring will be the unsplit remainder. +// n == 0: the result is nil (zero substrings) +// n < 0: all substrings // // Edge cases for s and sep (for example, empty strings) are handled // as described in the documentation for Split. @@ -281,9 +282,10 @@ func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) } // returns a slice of those substrings. // // The count determines the number of substrings to return: -// n > 0: at most n substrings; the last substring will be the unsplit remainder. -// n == 0: the result is nil (zero substrings) -// n < 0: all substrings +// +// n > 0: at most n substrings; the last substring will be the unsplit remainder. +// n == 0: the result is nil (zero substrings) +// n < 0: all substrings // // Edge cases for s and sep (for example, empty strings) are handled // as described in the documentation for SplitAfter. @@ -1043,8 +1045,6 @@ func ReplaceAll(s, old, new string) string { // EqualFold reports whether s and t, interpreted as UTF-8 strings, // are equal under simple Unicode case-folding, which is a more general // form of case-insensitivity. -// -// EqualFold(s, t) is equivalent to Tolower(s) == Tolower(t). func EqualFold(s, t string) bool { for s != "" && t != "" { // Extract first rune from each string. diff --git a/src/sync/atomic/atomic_test.go b/src/sync/atomic/atomic_test.go index 8a53094cb7..09f93a4fe3 100644 --- a/src/sync/atomic/atomic_test.go +++ b/src/sync/atomic/atomic_test.go @@ -1155,9 +1155,10 @@ func hammerStoreLoadUintptr(t *testing.T, paddr unsafe.Pointer) { StoreUintptr(addr, new) } -//go:nocheckptr // This code is just testing that LoadPointer/StorePointer operate // atomically; it's not actually calculating pointers. +// +//go:nocheckptr func hammerStoreLoadPointer(t *testing.T, paddr unsafe.Pointer) { addr := (*unsafe.Pointer)(paddr) v := uintptr(LoadPointer(addr)) diff --git a/src/sync/cond.go b/src/sync/cond.go index d86ebc8b50..841be96896 100644 --- a/src/sync/cond.go +++ b/src/sync/cond.go @@ -42,12 +42,12 @@ func NewCond(l Locker) *Cond { // typically cannot assume that the condition is true when // Wait returns. Instead, the caller should Wait in a loop: // -// c.L.Lock() -// for !condition() { -// c.Wait() -// } -// ... make use of condition ... -// c.L.Unlock() +// c.L.Lock() +// for !condition() { +// c.Wait() +// } +// ... make use of condition ... +// c.L.Unlock() func (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) diff --git a/src/sync/once.go b/src/sync/once.go index e5ba257d87..38373160b9 100644 --- a/src/sync/once.go +++ b/src/sync/once.go @@ -23,7 +23,9 @@ type Once struct { // Do calls the function f if and only if Do is being called for the // first time for this instance of Once. In other words, given -// var once Once +// +// var once Once +// // if once.Do(f) is called multiple times, only the first call will invoke f, // even if f has a different value in each invocation. A new instance of // Once is required for each function to execute. @@ -31,7 +33,8 @@ type Once struct { // Do is intended for initialization that must be run exactly once. Since f // is niladic, it may be necessary to use a function literal to capture the // arguments to a function to be invoked by Do: -// config.once.Do(func() { config.init(filename) }) +// +// config.once.Do(func() { config.init(filename) }) // // Because no call to Do returns until the one call to f returns, if f causes // Do to be called, it will deadlock. diff --git a/src/sync/pool_test.go b/src/sync/pool_test.go index bb20043a54..5e38597441 100644 --- a/src/sync/pool_test.go +++ b/src/sync/pool_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // Pool is no-op under race detector, so all these tests do not work. +// //go:build !race package sync_test diff --git a/src/syscall/dir_plan9.go b/src/syscall/dir_plan9.go index 4ed052de76..1667cbc02f 100644 --- a/src/syscall/dir_plan9.go +++ b/src/syscall/dir_plan9.go @@ -184,6 +184,7 @@ func gbit8(b []byte) (uint8, []byte) { } // gbit16 reads a 16-bit number in little-endian order from b and returns it with the remaining slice of b. +// //go:nosplit func gbit16(b []byte) (uint16, []byte) { return uint16(b[0]) | uint16(b[1])<<8, b[2:] diff --git a/src/syscall/exec_bsd.go b/src/syscall/exec_bsd.go index 530b48cb70..4762ae751a 100644 --- a/src/syscall/exec_bsd.go +++ b/src/syscall/exec_bsd.go @@ -49,6 +49,7 @@ func runtime_AfterForkInChild() // For the same reason compiler does not race instrument it. // The calls to RawSyscall are okay because they are assembly // functions that do not grow the stack. +// //go:norace func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { // Declare all variables at top in case any diff --git a/src/syscall/exec_freebsd.go b/src/syscall/exec_freebsd.go index 90793fe83f..851b8fbd06 100644 --- a/src/syscall/exec_freebsd.go +++ b/src/syscall/exec_freebsd.go @@ -54,6 +54,7 @@ func runtime_AfterForkInChild() // For the same reason compiler does not race instrument it. // The calls to RawSyscall are okay because they are assembly // functions that do not grow the stack. +// //go:norace func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { // Declare all variables at top in case any diff --git a/src/syscall/exec_libc.go b/src/syscall/exec_libc.go index c8549c4964..aee1b8c98a 100644 --- a/src/syscall/exec_libc.go +++ b/src/syscall/exec_libc.go @@ -75,6 +75,7 @@ func init() { // because we need to avoid lazy-loading the functions (might malloc, // split the stack, or acquire mutexes). We can't call RawSyscall // because it's not safe even for BSD-subsystem calls. +// //go:norace func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { // Declare all variables at top in case any diff --git a/src/syscall/exec_libc2.go b/src/syscall/exec_libc2.go index 91a39ba1b8..9eb61a5d35 100644 --- a/src/syscall/exec_libc2.go +++ b/src/syscall/exec_libc2.go @@ -50,6 +50,7 @@ func runtime_AfterForkInChild() // For the same reason compiler does not race instrument it. // The calls to rawSyscall are okay because they are assembly // functions that do not grow the stack. +// //go:norace func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { // Declare all variables at top in case any diff --git a/src/syscall/exec_linux.go b/src/syscall/exec_linux.go index 0f0dee8ea5..6d4b6939ad 100644 --- a/src/syscall/exec_linux.go +++ b/src/syscall/exec_linux.go @@ -77,6 +77,7 @@ func runtime_AfterForkInChild() // For the same reason compiler does not race instrument it. // The calls to RawSyscall are okay because they are assembly // functions that do not grow the stack. +// //go:norace func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr *ProcAttr, sys *SysProcAttr, pipe int) (pid int, err Errno) { // Set up and fork. This returns immediately in the parent or diff --git a/src/syscall/exec_plan9.go b/src/syscall/exec_plan9.go index c469fe1812..6680e6f2ef 100644 --- a/src/syscall/exec_plan9.go +++ b/src/syscall/exec_plan9.go @@ -19,6 +19,7 @@ var ForkLock sync.RWMutex // gstringb reads a non-empty string from b, prefixed with a 16-bit length in little-endian order. // It returns the string as a byte slice, or nil if b is too short to contain the length or // the full string. +// //go:nosplit func gstringb(b []byte) []byte { if len(b) < 2 { @@ -37,6 +38,7 @@ const nameOffset = 39 // gdirname returns the first filename from a buffer of directory entries, // and a slice containing the remaining directory entries. // If the buffer doesn't start with a valid directory entry, the returned name is nil. +// //go:nosplit func gdirname(buf []byte) (name []byte, rest []byte) { if len(buf) < 2 { @@ -119,6 +121,7 @@ var dupdev, _ = BytePtrFromString("#d") // no rescheduling, no malloc calls, and no new stack segments. // The calls to RawSyscall are okay because they are assembly // functions that do not grow the stack. +// //go:norace func forkAndExecInChild(argv0 *byte, argv []*byte, envv []envItem, dir *byte, attr *ProcAttr, pipe int, rflag int) (pid int, err error) { // Declare all variables at top in case any @@ -302,6 +305,7 @@ childerror1: } // close the numbered file descriptor, unless it is fd1, fd2, or a member of fds. +// //go:nosplit func closeFdExcept(n int, fd1 int, fd2 int, fds []int) { if n == fd1 || n == fd2 { diff --git a/src/syscall/exec_windows.go b/src/syscall/exec_windows.go index 41e58d4355..92464e089c 100644 --- a/src/syscall/exec_windows.go +++ b/src/syscall/exec_windows.go @@ -19,11 +19,11 @@ var ForkLock sync.RWMutex // in https://msdn.microsoft.com/en-us/library/ms880421. // This function returns "" (2 double quotes) if s is empty. // Alternatively, these transformations are done: -// - every back slash (\) is doubled, but only if immediately -// followed by double quote ("); -// - every double quote (") is escaped by back slash (\); -// - finally, s is wrapped with double quotes (arg -> "arg"), -// but only if there is space or tab inside s. +// - every back slash (\) is doubled, but only if immediately +// followed by double quote ("); +// - every double quote (") is escaped by back slash (\); +// - finally, s is wrapped with double quotes (arg -> "arg"), +// but only if there is space or tab inside s. func EscapeArg(s string) string { if len(s) == 0 { return `""` diff --git a/src/syscall/js/js.go b/src/syscall/js/js.go index a5210faf7f..2f4f5adda0 100644 --- a/src/syscall/js/js.go +++ b/src/syscall/js/js.go @@ -136,16 +136,16 @@ func Global() Value { // ValueOf returns x as a JavaScript value: // -// | Go | JavaScript | -// | ---------------------- | ---------------------- | -// | js.Value | [its value] | -// | js.Func | function | -// | nil | null | -// | bool | boolean | -// | integers and floats | number | -// | string | string | -// | []interface{} | new array | -// | map[string]interface{} | new object | +// | Go | JavaScript | +// | ---------------------- | ---------------------- | +// | js.Value | [its value] | +// | js.Func | function | +// | nil | null | +// | bool | boolean | +// | integers and floats | number | +// | string | string | +// | []interface{} | new array | +// | map[string]interface{} | new object | // // Panics if x is not one of the expected types. func ValueOf(x any) Value { diff --git a/src/syscall/syscall.go b/src/syscall/syscall.go index 98e3005253..62bfa449cf 100644 --- a/src/syscall/syscall.go +++ b/src/syscall/syscall.go @@ -23,7 +23,6 @@ // That is also where updates required by new systems or versions // should be applied. See https://golang.org/s/go1.4-syscall for more // information. -// package syscall import "internal/bytealg" diff --git a/src/syscall/syscall_js.go b/src/syscall/syscall_js.go index cd95499063..c9c6522980 100644 --- a/src/syscall/syscall_js.go +++ b/src/syscall/syscall_js.go @@ -41,6 +41,7 @@ const PathMax = 256 // An Errno is an unsigned number describing an error condition. // It implements the error interface. The zero Errno is by convention // a non-error, so code to convert from Errno to error should use: +// // err = nil // if errno != 0 { // err = errno diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go index f74a79c285..74322caea1 100644 --- a/src/syscall/syscall_linux.go +++ b/src/syscall/syscall_linux.go @@ -109,7 +109,7 @@ func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) { gid = Getgid() } - if uint32(gid) == st.Gid || isGroupMember(gid) { + if uint32(gid) == st.Gid || isGroupMember(int(st.Gid)) { fmode = (st.Mode >> 3) & 7 } else { fmode = st.Mode & 7 @@ -968,6 +968,7 @@ func Getpgrp() (pid int) { // Provided by runtime.syscall_runtime_doAllThreadsSyscall which stops the // world and invokes the syscall on each OS thread. Once this function returns, // all threads are in sync. +// //go:uintptrescapes func runtime_doAllThreadsSyscall(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) @@ -986,6 +987,7 @@ func runtime_doAllThreadsSyscall(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, // AllThreadsSyscall is unaware of any threads that are launched // explicitly by cgo linked code, so the function always returns // ENOTSUP in binaries that use cgo. +// //go:uintptrescapes func AllThreadsSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { if cgo_libc_setegid != nil { @@ -997,6 +999,7 @@ func AllThreadsSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) { // AllThreadsSyscall6 is like AllThreadsSyscall, but extended to six // arguments. +// //go:uintptrescapes func AllThreadsSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) { if cgo_libc_setegid != nil { @@ -1007,6 +1010,7 @@ func AllThreadsSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, e } // linked by runtime.cgocall.go +// //go:uintptrescapes func cgocaller(unsafe.Pointer, ...uintptr) uintptr diff --git a/src/syscall/syscall_unix.go b/src/syscall/syscall_unix.go index 56d21b4ec1..e12f024fe7 100644 --- a/src/syscall/syscall_unix.go +++ b/src/syscall/syscall_unix.go @@ -101,6 +101,7 @@ func (m *mmapper) Munmap(data []byte) (err error) { // An Errno is an unsigned number describing an error condition. // It implements the error interface. The zero Errno is by convention // a non-error, so code to convert from Errno to error should use: +// // err = nil // if errno != 0 { // err = errno diff --git a/src/testing/fuzz.go b/src/testing/fuzz.go index b5e1339deb..b9f3a3d159 100644 --- a/src/testing/fuzz.go +++ b/src/testing/fuzz.go @@ -189,7 +189,7 @@ var supportedTypes = map[reflect.Type]bool{ // whose remaining arguments are the types to be fuzzed. // For example: // -// f.Fuzz(func(t *testing.T, b []byte, i int) { ... }) +// f.Fuzz(func(t *testing.T, b []byte, i int) { ... }) // // The following types are allowed: []byte, string, bool, byte, rune, float32, // float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64. diff --git a/src/testing/quick/quick.go b/src/testing/quick/quick.go index e73d307c13..95a635bade 100644 --- a/src/testing/quick/quick.go +++ b/src/testing/quick/quick.go @@ -251,15 +251,15 @@ func (s *CheckEqualError) Error() string { // Check returns that input as a *CheckError. // For example: // -// func TestOddMultipleOfThree(t *testing.T) { -// f := func(x int) bool { -// y := OddMultipleOfThree(x) -// return y%2 == 1 && y%3 == 0 -// } -// if err := quick.Check(f, nil); err != nil { -// t.Error(err) -// } -// } +// func TestOddMultipleOfThree(t *testing.T) { +// f := func(x int) bool { +// y := OddMultipleOfThree(x) +// return y%2 == 1 && y%3 == 0 +// } +// if err := quick.Check(f, nil); err != nil { +// t.Error(err) +// } +// } func Check(f any, config *Config) error { if config == nil { config = &defaultConfig diff --git a/src/testing/testing.go b/src/testing/testing.go index 05d8f22aff..1f701e0b21 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -5,7 +5,9 @@ // Package testing provides support for automated testing of Go packages. // It is intended to be used in concert with the "go test" command, which automates // execution of any function of the form -// func TestXxx(*testing.T) +// +// func TestXxx(*testing.T) +// // where Xxx does not start with a lowercase letter. The function name // serves to identify the test routine. // @@ -19,17 +21,19 @@ // // A simple test function looks like this: // -// func TestAbs(t *testing.T) { -// got := Abs(-1) -// if got != 1 { -// t.Errorf("Abs(-1) = %d; want 1", got) -// } -// } +// func TestAbs(t *testing.T) { +// got := Abs(-1) +// if got != 1 { +// t.Errorf("Abs(-1) = %d; want 1", got) +// } +// } // -// Benchmarks +// # Benchmarks // // Functions of the form -// func BenchmarkXxx(*testing.B) +// +// func BenchmarkXxx(*testing.B) +// // are considered benchmarks, and are executed by the "go test" command when // its -bench flag is provided. Benchmarks are run sequentially. // @@ -37,43 +41,46 @@ // https://golang.org/cmd/go/#hdr-Testing_flags. // // A sample benchmark function looks like this: -// func BenchmarkRandInt(b *testing.B) { -// for i := 0; i < b.N; i++ { -// rand.Int() -// } -// } +// +// func BenchmarkRandInt(b *testing.B) { +// for i := 0; i < b.N; i++ { +// rand.Int() +// } +// } // // The benchmark function must run the target code b.N times. // During benchmark execution, b.N is adjusted until the benchmark function lasts // long enough to be timed reliably. The output -// BenchmarkRandInt-8 68453040 17.8 ns/op +// +// BenchmarkRandInt-8 68453040 17.8 ns/op +// // means that the loop ran 68453040 times at a speed of 17.8 ns per loop. // // If a benchmark needs some expensive setup before running, the timer // may be reset: // -// func BenchmarkBigLen(b *testing.B) { -// big := NewBig() -// b.ResetTimer() -// for i := 0; i < b.N; i++ { -// big.Len() -// } -// } +// func BenchmarkBigLen(b *testing.B) { +// big := NewBig() +// b.ResetTimer() +// for i := 0; i < b.N; i++ { +// big.Len() +// } +// } // // If a benchmark needs to test performance in a parallel setting, it may use // the RunParallel helper function; such benchmarks are intended to be used with // the go test -cpu flag: // -// func BenchmarkTemplateParallel(b *testing.B) { -// templ := template.Must(template.New("test").Parse("Hello, {{.}}!")) -// b.RunParallel(func(pb *testing.PB) { -// var buf bytes.Buffer -// for pb.Next() { -// buf.Reset() -// templ.Execute(&buf, "World") -// } -// }) -// } +// func BenchmarkTemplateParallel(b *testing.B) { +// templ := template.Must(template.New("test").Parse("Hello, {{.}}!")) +// b.RunParallel(func(pb *testing.PB) { +// var buf bytes.Buffer +// for pb.Next() { +// buf.Reset() +// templ.Execute(&buf, "World") +// } +// }) +// } // // A detailed specification of the benchmark results format is given // in https://golang.org/design/14313-benchmark-format. @@ -83,90 +90,92 @@ // In particular, https://golang.org/x/perf/cmd/benchstat performs // statistically robust A/B comparisons. // -// Examples +// # Examples // // The package also runs and verifies example code. Example functions may // include a concluding line comment that begins with "Output:" and is compared with // the standard output of the function when the tests are run. (The comparison // ignores leading and trailing space.) These are examples of an example: // -// func ExampleHello() { -// fmt.Println("hello") -// // Output: hello -// } +// func ExampleHello() { +// fmt.Println("hello") +// // Output: hello +// } // -// func ExampleSalutations() { -// fmt.Println("hello, and") -// fmt.Println("goodbye") -// // Output: -// // hello, and -// // goodbye -// } +// func ExampleSalutations() { +// fmt.Println("hello, and") +// fmt.Println("goodbye") +// // Output: +// // hello, and +// // goodbye +// } // // The comment prefix "Unordered output:" is like "Output:", but matches any // line order: // -// func ExamplePerm() { -// for _, value := range Perm(5) { -// fmt.Println(value) -// } -// // Unordered output: 4 -// // 2 -// // 1 -// // 3 -// // 0 -// } +// func ExamplePerm() { +// for _, value := range Perm(5) { +// fmt.Println(value) +// } +// // Unordered output: 4 +// // 2 +// // 1 +// // 3 +// // 0 +// } // // Example functions without output comments are compiled but not executed. // // The naming convention to declare examples for the package, a function F, a type T and // method M on type T are: // -// func Example() { ... } -// func ExampleF() { ... } -// func ExampleT() { ... } -// func ExampleT_M() { ... } +// func Example() { ... } +// func ExampleF() { ... } +// func ExampleT() { ... } +// func ExampleT_M() { ... } // // Multiple example functions for a package/type/function/method may be provided by // appending a distinct suffix to the name. The suffix must start with a // lower-case letter. // -// func Example_suffix() { ... } -// func ExampleF_suffix() { ... } -// func ExampleT_suffix() { ... } -// func ExampleT_M_suffix() { ... } +// func Example_suffix() { ... } +// func ExampleF_suffix() { ... } +// func ExampleT_suffix() { ... } +// func ExampleT_M_suffix() { ... } // // The entire test file is presented as the example when it contains a single // example function, at least one other function, type, variable, or constant // declaration, and no test or benchmark functions. // -// Fuzzing +// # Fuzzing // // 'go test' and the testing package support fuzzing, a testing technique where // a function is called with randomly generated inputs to find bugs not // anticipated by unit tests. // // Functions of the form -// func FuzzXxx(*testing.F) +// +// func FuzzXxx(*testing.F) +// // are considered fuzz tests. // // For example: // -// func FuzzHex(f *testing.F) { -// for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { -// f.Add(seed) -// } -// f.Fuzz(func(t *testing.T, in []byte) { -// enc := hex.EncodeToString(in) -// out, err := hex.DecodeString(enc) -// if err != nil { -// t.Fatalf("%v: decode: %v", in, err) -// } -// if !bytes.Equal(in, out) { -// t.Fatalf("%v: not equal after round trip: %v", in, out) -// } -// }) -// } +// func FuzzHex(f *testing.F) { +// for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} { +// f.Add(seed) +// } +// f.Fuzz(func(t *testing.T, in []byte) { +// enc := hex.EncodeToString(in) +// out, err := hex.DecodeString(enc) +// if err != nil { +// t.Fatalf("%v: decode: %v", in, err) +// } +// if !bytes.Equal(in, out) { +// t.Fatalf("%v: not equal after round trip: %v", in, out) +// } +// }) +// } // // A fuzz test maintains a seed corpus, or a set of inputs which are run by // default, and can seed input generation. Seed inputs may be registered by @@ -204,47 +213,47 @@ // // See https://go.dev/doc/fuzz for documentation about fuzzing. // -// Skipping +// # Skipping // // Tests or benchmarks may be skipped at run time with a call to // the Skip method of *T or *B: // -// func TestTimeConsuming(t *testing.T) { -// if testing.Short() { -// t.Skip("skipping test in short mode.") -// } -// ... -// } +// func TestTimeConsuming(t *testing.T) { +// if testing.Short() { +// t.Skip("skipping test in short mode.") +// } +// ... +// } // // The Skip method of *T can be used in a fuzz target if the input is invalid, // but should not be considered a failing input. For example: // -// func FuzzJSONMarshalling(f *testing.F) { -// f.Fuzz(func(t *testing.T, b []byte) { -// var v interface{} -// if err := json.Unmarshal(b, &v); err != nil { -// t.Skip() -// } -// if _, err := json.Marshal(v); err != nil { -// t.Error("Marshal: %v", err) -// } -// }) -// } +// func FuzzJSONMarshaling(f *testing.F) { +// f.Fuzz(func(t *testing.T, b []byte) { +// var v interface{} +// if err := json.Unmarshal(b, &v); err != nil { +// t.Skip() +// } +// if _, err := json.Marshal(v); err != nil { +// t.Error("Marshal: %v", err) +// } +// }) +// } // -// Subtests and Sub-benchmarks +// # Subtests and Sub-benchmarks // // The Run methods of T and B allow defining subtests and sub-benchmarks, // without having to define separate functions for each. This enables uses // like table-driven benchmarks and creating hierarchical tests. // It also provides a way to share common setup and tear-down code: // -// func TestFoo(t *testing.T) { -// // -// t.Run("A=1", func(t *testing.T) { ... }) -// t.Run("A=2", func(t *testing.T) { ... }) -// t.Run("B=1", func(t *testing.T) { ... }) -// // -// } +// func TestFoo(t *testing.T) { +// // +// t.Run("A=1", func(t *testing.T) { ... }) +// t.Run("A=2", func(t *testing.T) { ... }) +// t.Run("B=1", func(t *testing.T) { ... }) +// // +// } // // Each subtest and sub-benchmark has a unique name: the combination of the name // of the top-level test and the sequence of names passed to Run, separated by @@ -257,15 +266,16 @@ // empty expression matches any string. // For example, using "matching" to mean "whose name contains": // -// go test -run '' # Run all tests. -// go test -run Foo # Run top-level tests matching "Foo", such as "TestFooBar". -// go test -run Foo/A= # For top-level tests matching "Foo", run subtests matching "A=". -// go test -run /A=1 # For all top-level tests, run subtests matching "A=1". -// go test -fuzz FuzzFoo # Fuzz the target matching "FuzzFoo" +// go test -run '' # Run all tests. +// go test -run Foo # Run top-level tests matching "Foo", such as "TestFooBar". +// go test -run Foo/A= # For top-level tests matching "Foo", run subtests matching "A=". +// go test -run /A=1 # For all top-level tests, run subtests matching "A=1". +// go test -fuzz FuzzFoo # Fuzz the target matching "FuzzFoo" // // The -run argument can also be used to run a specific value in the seed // corpus, for debugging. For example: -// go test -run=FuzzFoo/9ddb952d9814 +// +// go test -run=FuzzFoo/9ddb952d9814 // // The -fuzz and -run flags can both be set, in order to fuzz a target but // skip the execution of all other tests. @@ -275,15 +285,15 @@ // run in parallel with each other, and only with each other, regardless of // other top-level tests that may be defined: // -// func TestGroupedParallel(t *testing.T) { -// for _, tc := range tests { -// tc := tc // capture range variable -// t.Run(tc.Name, func(t *testing.T) { -// t.Parallel() -// ... -// }) -// } -// } +// func TestGroupedParallel(t *testing.T) { +// for _, tc := range tests { +// tc := tc // capture range variable +// t.Run(tc.Name, func(t *testing.T) { +// t.Parallel() +// ... +// }) +// } +// } // // The race detector kills the program if it exceeds 8128 concurrent goroutines, // so use care when running parallel tests with the -race flag set. @@ -291,17 +301,17 @@ // Run does not return until parallel subtests have completed, providing a way // to clean up after a group of parallel tests: // -// func TestTeardownParallel(t *testing.T) { -// // This Run will not return until the parallel tests finish. -// t.Run("group", func(t *testing.T) { -// t.Run("Test1", parallelTest1) -// t.Run("Test2", parallelTest2) -// t.Run("Test3", parallelTest3) -// }) -// // -// } +// func TestTeardownParallel(t *testing.T) { +// // This Run will not return until the parallel tests finish. +// t.Run("group", func(t *testing.T) { +// t.Run("Test1", parallelTest1) +// t.Run("Test2", parallelTest2) +// t.Run("Test3", parallelTest3) +// }) +// // +// } // -// Main +// # Main // // It is sometimes necessary for a test or benchmark program to do extra setup or teardown // before or after it executes. It is also sometimes necessary to control diff --git a/src/text/template/funcs.go b/src/text/template/funcs.go index 1f63b361f8..390d47ebbb 100644 --- a/src/text/template/funcs.go +++ b/src/text/template/funcs.go @@ -751,7 +751,9 @@ func URLQueryEscaper(args ...any) string { } // evalArgs formats the list of arguments into a string. It is therefore equivalent to +// // fmt.Sprint(args...) +// // except that each argument is indirected (if a pointer), as required, // using the same rules as the default string evaluation during template // execution. diff --git a/src/text/template/helper.go b/src/text/template/helper.go index 57905e613a..48af3928b3 100644 --- a/src/text/template/helper.go +++ b/src/text/template/helper.go @@ -19,6 +19,7 @@ import ( // Must is a helper that wraps a call to a function returning (*Template, error) // and panics if the error is non-nil. It is intended for use in variable // initializations such as +// // var t = template.Must(template.New("name").Parse("text")) func Must(t *Template, err error) *Template { if err != nil { diff --git a/src/text/template/option.go b/src/text/template/option.go index 8d7d436bd0..ea2fd80c06 100644 --- a/src/text/template/option.go +++ b/src/text/template/option.go @@ -30,6 +30,7 @@ type option struct { // // missingkey: Control the behavior during execution if a map is // indexed with a key that is not present in the map. +// // "missingkey=default" or "missingkey=invalid" // The default behavior: Do nothing and continue execution. // If printed, the result of the index operation is the string diff --git a/src/text/template/parse/lex.go b/src/text/template/parse/lex.go index 40d0411121..078f714ccf 100644 --- a/src/text/template/parse/lex.go +++ b/src/text/template/parse/lex.go @@ -541,13 +541,25 @@ func (l *lexer) atTerminator() bool { case eof, '.', ',', '|', ':', ')', '(': return true } - // Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will - // succeed but should fail) but only in extremely rare cases caused by willfully - // bad choice of delimiter. - if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r { - return true + // Are we at a right delimiter? TODO: This is harder than it should be + // because lookahead is only one rune. + rightDelim := l.rightDelim + defer func(pos Pos, line int) { + l.pos = pos + l.line = line + }(l.pos, l.line) + for len(rightDelim) > 0 { + rNext := l.next() + if rNext == eof { + return false + } + rDelim, size := utf8.DecodeRuneInString(rightDelim) + if rNext != rDelim { + return false + } + rightDelim = rightDelim[size:] } - return false + return true } // lexChar scans a character constant. The initial quote is already diff --git a/src/text/template/parse/lex_test.go b/src/text/template/parse/lex_test.go index df6aabffb2..fcb7e8eacd 100644 --- a/src/text/template/parse/lex_test.go +++ b/src/text/template/parse/lex_test.go @@ -469,6 +469,22 @@ func TestDelims(t *testing.T) { } } +func TestDelimsAlphaNumeric(t *testing.T) { + test := lexTest{"right delimiter with alphanumeric start", "{{hub .host hub}}", []item{ + mkItem(itemLeftDelim, "{{hub"), + mkItem(itemSpace, " "), + mkItem(itemField, ".host"), + mkItem(itemSpace, " "), + mkItem(itemRightDelim, "hub}}"), + tEOF, + }} + items := collect(&test, "{{hub", "hub}}") + + if !equal(items, test.items, false) { + t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) + } +} + var lexPosTests = []lexTest{ {"empty", "", []item{{itemEOF, 0, "", 1}}}, {"punctuation", "{{,@%#}}", []item{ diff --git a/src/text/template/parse/parse.go b/src/text/template/parse/parse.go index ce548b0886..67e2f5b2f4 100644 --- a/src/text/template/parse/parse.go +++ b/src/text/template/parse/parse.go @@ -341,7 +341,9 @@ func (t *Tree) parseDefinition() { } // itemList: +// // textOrAction* +// // Terminates at {{end}} or {{else}}, returned separately. func (t *Tree) itemList() (list *ListNode, next Node) { list = t.newList(t.peekNonSpace().pos) @@ -358,6 +360,7 @@ func (t *Tree) itemList() (list *ListNode, next Node) { } // textOrAction: +// // text | comment | action func (t *Tree) textOrAction() Node { switch token := t.nextNonSpace(); token.typ { @@ -380,8 +383,10 @@ func (t *Tree) clearActionLine() { } // Action: +// // control // command ("|" command)* +// // Left delim is past. Now get actions. // First word could be a keyword such as range. func (t *Tree) action() (n Node) { @@ -412,7 +417,9 @@ func (t *Tree) action() (n Node) { } // Break: +// // {{break}} +// // Break keyword is past. func (t *Tree) breakControl(pos Pos, line int) Node { if token := t.nextNonSpace(); token.typ != itemRightDelim { @@ -425,7 +432,9 @@ func (t *Tree) breakControl(pos Pos, line int) Node { } // Continue: +// // {{continue}} +// // Continue keyword is past. func (t *Tree) continueControl(pos Pos, line int) Node { if token := t.nextNonSpace(); token.typ != itemRightDelim { @@ -438,6 +447,7 @@ func (t *Tree) continueControl(pos Pos, line int) Node { } // Pipeline: +// // declarations? command ('|' command)* func (t *Tree) pipeline(context string, end itemType) (pipe *PipeNode) { token := t.peekNonSpace() @@ -549,16 +559,20 @@ func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int } // If: +// // {{if pipeline}} itemList {{end}} // {{if pipeline}} itemList {{else}} itemList {{end}} +// // If keyword is past. func (t *Tree) ifControl() Node { return t.newIf(t.parseControl(true, "if")) } // Range: +// // {{range pipeline}} itemList {{end}} // {{range pipeline}} itemList {{else}} itemList {{end}} +// // Range keyword is past. func (t *Tree) rangeControl() Node { r := t.newRange(t.parseControl(false, "range")) @@ -566,22 +580,28 @@ func (t *Tree) rangeControl() Node { } // With: +// // {{with pipeline}} itemList {{end}} // {{with pipeline}} itemList {{else}} itemList {{end}} +// // If keyword is past. func (t *Tree) withControl() Node { return t.newWith(t.parseControl(false, "with")) } // End: +// // {{end}} +// // End keyword is past. func (t *Tree) endControl() Node { return t.newEnd(t.expect(itemRightDelim, "end").pos) } // Else: +// // {{else}} +// // Else keyword is past. func (t *Tree) elseControl() Node { // Special case for "else if". @@ -595,7 +615,9 @@ func (t *Tree) elseControl() Node { } // Block: +// // {{block stringValue pipeline}} +// // Block keyword is past. // The name must be something that can evaluate to a string. // The pipeline is mandatory. @@ -623,7 +645,9 @@ func (t *Tree) blockControl() Node { } // Template: +// // {{template stringValue pipeline}} +// // Template keyword is past. The name must be something that can evaluate // to a string. func (t *Tree) templateControl() Node { @@ -654,7 +678,9 @@ func (t *Tree) parseTemplateName(token item, context string) (name string) { } // command: +// // operand (space operand)* +// // space-separated arguments up to a pipeline character or right delimiter. // we consume the pipe character but leave the right delim to terminate the action. func (t *Tree) command() *CommandNode { @@ -684,7 +710,9 @@ func (t *Tree) command() *CommandNode { } // operand: +// // term .Field* +// // An operand is a space-separated component of a command, // a term possibly followed by field accesses. // A nil return means the next item is not an operand. @@ -718,12 +746,14 @@ func (t *Tree) operand() Node { } // term: +// // literal (number, string, nil, boolean) // function (identifier) // . // .Field // $ // '(' pipeline ')' +// // A term is a simple "expression". // A nil return means the next item is not a term. func (t *Tree) term() Node { diff --git a/src/time/format.go b/src/time/format.go index 95fe08b772..2f66df668b 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -8,12 +8,16 @@ import "errors" // These are predefined layouts for use in Time.Format and time.Parse. // The reference time used in these layouts is the specific time stamp: +// // 01/02 03:04:05PM '06 -0700 +// // (January 2, 15:04:05, 2006, in time zone seven hours west of GMT). // That value is recorded as the constant named Layout, listed below. As a Unix // time, this is 1136239445. Since MST is GMT-0700, the reference would be // printed by the Unix date command as: +// // Mon Jan 2 15:04:05 MST 2006 +// // It is a regrettable historic error that the date uses the American convention // of putting the numerical month before the day. // @@ -59,12 +63,15 @@ import "errors" // AM/PM mark: "PM" // // Numeric time zone offsets format as follows: +// // "-0700" ±hhmm // "-07:00" ±hh:mm // "-07" ±hh +// // Replacing the sign in the format with a Z triggers // the ISO 8601 behavior of printing Z instead of an // offset for the UTC zone. Thus: +// // "Z0700" Z or ±hhmm // "Z07:00" Z or ±hh:mm // "Z07" Z or ±hh @@ -484,6 +491,7 @@ func formatNano(b []byte, nanosec uint, std int) []byte { } // String returns the time formatted using the format string +// // "2006-01-02 15:04:05.999999999 -0700 MST" // // If the time has a monotonic clock reading, the returned string diff --git a/src/time/sleep.go b/src/time/sleep.go index 1ffaabec67..cdab4782ad 100644 --- a/src/time/sleep.go +++ b/src/time/sleep.go @@ -62,9 +62,9 @@ type Timer struct { // return value and drain the channel. // For example, assuming the program has not received from t.C already: // -// if !t.Stop() { -// <-t.C -// } +// if !t.Stop() { +// <-t.C +// } // // This cannot be done concurrent to other receives from the Timer's // channel or other calls to the Timer's Stop method. @@ -110,10 +110,10 @@ func NewTimer(d Duration) *Timer { // the timer must be stopped and—if Stop reports that the timer expired // before being stopped—the channel explicitly drained: // -// if !t.Stop() { -// <-t.C -// } -// t.Reset(d) +// if !t.Stop() { +// <-t.C +// } +// t.Reset(d) // // This should not be done concurrent to other receives from the Timer's // channel. diff --git a/src/time/tick.go b/src/time/tick.go index babf865aeb..dcfeca8783 100644 --- a/src/time/tick.go +++ b/src/time/tick.go @@ -6,7 +6,7 @@ package time import "errors" -// A Ticker holds a channel that delivers ``ticks'' of a clock +// A Ticker holds a channel that delivers “ticks” of a clock // at intervals. type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. diff --git a/src/time/time.go b/src/time/time.go index 88301ec16b..95963b6bf3 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -7,7 +7,7 @@ // The calendrical calculations always assume a Gregorian calendar, with // no leap seconds. // -// Monotonic Clocks +// # Monotonic Clocks // // Operating systems provide both a “wall clock,” which is subject to // changes for clock synchronization, and a “monotonic clock,” which is @@ -72,7 +72,6 @@ // For debugging, the result of t.String does include the monotonic // clock reading if present. If t != u because of different monotonic clock readings, // that difference will be visible when printing t.String() and u.String(). -// package time import ( @@ -596,10 +595,12 @@ const ( // to avoid confusion across daylight savings time zone transitions. // // To count the number of units in a Duration, divide: +// // second := time.Second // fmt.Print(int64(second/time.Millisecond)) // prints 1000 // // To convert an integer number of units to a Duration, multiply: +// // seconds := 10 // fmt.Print(time.Duration(seconds)*time.Second) // prints 10s const ( @@ -1068,6 +1069,7 @@ func daysSinceEpoch(year int) uint64 { func now() (sec int64, nsec int32, mono int64) // runtimeNano returns the current value of the runtime clock in nanoseconds. +// //go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 @@ -1378,6 +1380,7 @@ func isLeap(year int) bool { } // norm returns nhi, nlo such that +// // hi * base + lo == nhi * base + nlo // 0 <= nlo < base func norm(hi, lo, base int) (nhi, nlo int) { @@ -1395,7 +1398,9 @@ func norm(hi, lo, base int) (nhi, nlo int) { } // Date returns the Time corresponding to +// // yyyy-mm-dd hh:mm:ss + nsec nanoseconds +// // in the appropriate zone for that time in the given location. // // The month, day, hour, min, sec, and nsec values may be outside diff --git a/src/time/tzdata/tzdata.go b/src/time/tzdata/tzdata.go index 25725bd84d..324de5cd85 100644 --- a/src/time/tzdata/tzdata.go +++ b/src/time/tzdata/tzdata.go @@ -29,6 +29,7 @@ import ( ) // registerLoadFromEmbeddedTZData is defined in package time. +// //go:linkname registerLoadFromEmbeddedTZData time.registerLoadFromEmbeddedTZData func registerLoadFromEmbeddedTZData(func(string) (string, error)) diff --git a/src/time/zoneinfo.go b/src/time/zoneinfo.go index 9bcb183d77..b3313583d8 100644 --- a/src/time/zoneinfo.go +++ b/src/time/zoneinfo.go @@ -197,14 +197,14 @@ func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, // The reference implementation in localtime.c from // https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz // implements the following algorithm for these cases: -// 1) If the first zone is unused by the transitions, use it. -// 2) Otherwise, if there are transition times, and the first +// 1. If the first zone is unused by the transitions, use it. +// 2. Otherwise, if there are transition times, and the first // transition is to a zone in daylight time, find the first // non-daylight-time zone before and closest to the first transition // zone. -// 3) Otherwise, use the first zone that is not daylight time, if +// 3. Otherwise, use the first zone that is not daylight time, if // there is one. -// 4) Otherwise, use the first zone. +// 4. Otherwise, use the first zone. func (l *Location) lookupFirstZone() int { // Case 1. if !l.firstZoneUsed() { diff --git a/src/unicode/graphic.go b/src/unicode/graphic.go index ca6241949a..2af29778bf 100644 --- a/src/unicode/graphic.go +++ b/src/unicode/graphic.go @@ -120,7 +120,9 @@ func IsPunct(r rune) bool { // IsSpace reports whether the rune is a space character as defined // by Unicode's White Space property; in the Latin-1 space // this is +// // '\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL), U+00A0 (NBSP). +// // Other definitions of spacing characters are set by category // Z and property Pattern_White_Space. func IsSpace(r rune) bool { diff --git a/src/unicode/letter.go b/src/unicode/letter.go index f4c950a883..f3f8e52964 100644 --- a/src/unicode/letter.go +++ b/src/unicode/letter.go @@ -49,7 +49,9 @@ type Range32 struct { // means the character is in the corresponding case. There is a special // case representing sequences of alternating corresponding Upper and Lower // pairs. It appears with a fixed Delta of +// // {UpperLower, UpperLower, UpperLower} +// // The constant UpperLower has an otherwise impossible delta value. type CaseRange struct { Lo uint32 @@ -324,6 +326,7 @@ type foldPair struct { // If r is not a valid Unicode code point, SimpleFold(r) returns r. // // For example: +// // SimpleFold('A') = 'a' // SimpleFold('a') = 'A' // diff --git a/src/unsafe/unsafe.go b/src/unsafe/unsafe.go index ae69dea4af..da15902b29 100644 --- a/src/unsafe/unsafe.go +++ b/src/unsafe/unsafe.go @@ -20,10 +20,11 @@ type IntegerType int // Pointer represents a pointer to an arbitrary type. There are four special operations // available for type Pointer that are not available for other types: -// - A pointer value of any type can be converted to a Pointer. -// - A Pointer can be converted to a pointer value of any type. -// - A uintptr can be converted to a Pointer. -// - A Pointer can be converted to a uintptr. +// - A pointer value of any type can be converted to a Pointer. +// - A Pointer can be converted to a pointer value of any type. +// - A uintptr can be converted to a Pointer. +// - A Pointer can be converted to a uintptr. +// // Pointer therefore allows a program to defeat the type system and read and write // arbitrary memory. It should be used with extreme care. // diff --git a/test/codegen/bmi.go b/test/codegen/bmi.go index 2908d1b796..3b125a1b59 100644 --- a/test/codegen/bmi.go +++ b/test/codegen/bmi.go @@ -46,7 +46,49 @@ func blsr32(x int32) int32 { return x & (x - 1) } -func shlrx64(x []uint64, i int, s uint64) uint64 { +func sarx64(x, y int64) int64 { + // amd64/v3:"SARXQ" + return x >> y +} + +func sarx32(x, y int32) int32 { + // amd64/v3:"SARXL" + return x >> y +} + +func sarx64_load(x []int64, i int) int64 { + // amd64/v3: `SARXQ\t[A-Z]+[0-9]*, \([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*8\), [A-Z]+[0-9]*` + s := x[i] >> (i & 63) + // amd64/v3: `SARXQ\t[A-Z]+[0-9]*, 8\([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*8\), [A-Z]+[0-9]*` + s = x[i+1] >> (s & 63) + return s +} + +func sarx32_load(x []int32, i int) int32 { + // amd64/v3: `SARXL\t[A-Z]+[0-9]*, \([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*4\), [A-Z]+[0-9]*` + s := x[i] >> (i & 63) + // amd64/v3: `SARXL\t[A-Z]+[0-9]*, 4\([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*4\), [A-Z]+[0-9]*` + s = x[i+1] >> (s & 63) + return s +} + +func shlrx64(x, y uint64) uint64 { + // amd64/v3:"SHRXQ" + s := x >> y + // amd64/v3:"SHLXQ" + s = s << y + return s +} + +func shlrx32(x, y uint32) uint32 { + // amd64/v3:"SHRXL" + s := x >> y + // amd64/v3:"SHLXL" + s = s << y + return s +} + +func shlrx64_load(x []uint64, i int, s uint64) uint64 { // amd64/v3: `SHRXQ\t[A-Z]+[0-9]*, \([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*8\), [A-Z]+[0-9]*` s = x[i] >> i // amd64/v3: `SHLXQ\t[A-Z]+[0-9]*, 8\([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*8\), [A-Z]+[0-9]*` @@ -54,7 +96,7 @@ func shlrx64(x []uint64, i int, s uint64) uint64 { return s } -func shlrx32(x []uint32, i int, s uint32) uint32 { +func shlrx32_load(x []uint32, i int, s uint32) uint32 { // amd64/v3: `SHRXL\t[A-Z]+[0-9]*, \([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*4\), [A-Z]+[0-9]*` s = x[i] >> i // amd64/v3: `SHLXL\t[A-Z]+[0-9]*, 4\([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*4\), [A-Z]+[0-9]*` diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go index ad42538dcd..0292d7f0f3 100644 --- a/test/codegen/memcombine.go +++ b/test/codegen/memcombine.go @@ -11,98 +11,94 @@ import ( "runtime" ) -var sink64 uint64 -var sink32 uint32 -var sink16 uint16 - // ------------- // // Loading // // ------------- // -func load_le64(b []byte) { +func load_le64(b []byte) uint64 { // amd64:`MOVQ\s\(.*\),`,-`MOV[BWL]\t[^$]`,-`OR` // s390x:`MOVDBR\s\(.*\),` // arm64:`MOVD\s\(R[0-9]+\),`,-`MOV[BHW]` // ppc64le:`MOVD\s`,-`MOV[BHW]Z` - sink64 = binary.LittleEndian.Uint64(b) + return binary.LittleEndian.Uint64(b) } -func load_le64_idx(b []byte, idx int) { +func load_le64_idx(b []byte, idx int) uint64 { // amd64:`MOVQ\s\(.*\)\(.*\*1\),`,-`MOV[BWL]\t[^$]`,-`OR` // s390x:`MOVDBR\s\(.*\)\(.*\*1\),` // arm64:`MOVD\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[BHW]` // ppc64le:`MOVD\s`,-`MOV[BHW]Z\s` - sink64 = binary.LittleEndian.Uint64(b[idx:]) + return binary.LittleEndian.Uint64(b[idx:]) } -func load_le32(b []byte) { +func load_le32(b []byte) uint32 { // amd64:`MOVL\s\(.*\),`,-`MOV[BW]`,-`OR` // 386:`MOVL\s\(.*\),`,-`MOV[BW]`,-`OR` // s390x:`MOVWBR\s\(.*\),` // arm64:`MOVWU\s\(R[0-9]+\),`,-`MOV[BH]` // ppc64le:`MOVWZ\s`,-`MOV[BH]Z\s` - sink32 = binary.LittleEndian.Uint32(b) + return binary.LittleEndian.Uint32(b) } -func load_le32_idx(b []byte, idx int) { +func load_le32_idx(b []byte, idx int) uint32 { // amd64:`MOVL\s\(.*\)\(.*\*1\),`,-`MOV[BW]`,-`OR` // 386:`MOVL\s\(.*\)\(.*\*1\),`,-`MOV[BW]`,-`OR` // s390x:`MOVWBR\s\(.*\)\(.*\*1\),` // arm64:`MOVWU\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[BH]` // ppc64le:`MOVWZ\s`,-`MOV[BH]Z\s` - sink32 = binary.LittleEndian.Uint32(b[idx:]) + return binary.LittleEndian.Uint32(b[idx:]) } -func load_le16(b []byte) { +func load_le16(b []byte) uint16 { // amd64:`MOVWLZX\s\(.*\),`,-`MOVB`,-`OR` // ppc64le:`MOVHZ\s`,-`MOVBZ` // arm64:`MOVHU\s\(R[0-9]+\),`,-`MOVB` // s390x:`MOVHBR\s\(.*\),` - sink16 = binary.LittleEndian.Uint16(b) + return binary.LittleEndian.Uint16(b) } -func load_le16_idx(b []byte, idx int) { +func load_le16_idx(b []byte, idx int) uint16 { // amd64:`MOVWLZX\s\(.*\),`,-`MOVB`,-`OR` // ppc64le:`MOVHZ\s`,-`MOVBZ` // arm64:`MOVHU\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOVB` // s390x:`MOVHBR\s\(.*\)\(.*\*1\),` - sink16 = binary.LittleEndian.Uint16(b[idx:]) + return binary.LittleEndian.Uint16(b[idx:]) } -func load_be64(b []byte) { +func load_be64(b []byte) uint64 { // 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` - sink64 = binary.BigEndian.Uint64(b) + return binary.BigEndian.Uint64(b) } -func load_be64_idx(b []byte, idx int) { +func load_be64_idx(b []byte, idx int) uint64 { // amd64/v1,amd64/v2:`BSWAPQ`,-`MOV[BWL]\t[^$]`,-`OR` - // amd64/v3: `MOVBEQ` + // amd64/v3: `MOVBEQ\t\([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*1\), [A-Z]+[0-9]*` // s390x:`MOVD\s\(.*\)\(.*\*1\),` // arm64:`REV`,`MOVD\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[WHB]`,-`REVW`,-`REV16W` // ppc64le:`MOVDBR`,-`MOV[BHW]Z` - sink64 = binary.BigEndian.Uint64(b[idx:]) + return binary.BigEndian.Uint64(b[idx:]) } -func load_be32(b []byte) { +func load_be32(b []byte) uint32 { // 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` - sink32 = binary.BigEndian.Uint32(b) + return binary.BigEndian.Uint32(b) } -func load_be32_idx(b []byte, idx int) { +func load_be32_idx(b []byte, idx int) uint32 { // amd64/v1,amd64/v2:`BSWAPL`,-`MOV[BW]`,-`OR` - // amd64/v3: `MOVBEL` + // amd64/v3: `MOVBEL\t\([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*1\), [A-Z]+[0-9]*` // s390x:`MOVWZ\s\(.*\)\(.*\*1\),` // arm64:`REVW`,`MOVWU\s\(R[0-9]+\)\(R[0-9]+\),`,-`MOV[HB]`,-`REV16W` // ppc64le:`MOVWBR`,-`MOV[BH]Z` - sink32 = binary.BigEndian.Uint32(b[idx:]) + return binary.BigEndian.Uint32(b[idx:]) } func load_be16(b []byte) uint16 { @@ -357,20 +353,20 @@ func safe_point(p, q *[2]*int) { // Storing // // ------------- // -func store_le64(b []byte) { +func store_le64(b []byte, x uint64) { // amd64:`MOVQ\s.*\(.*\)$`,-`SHR.` // arm64:`MOVD`,-`MOV[WBH]` // ppc64le:`MOVD\s`,-`MOV[BHW]\s` // s390x:`MOVDBR\s.*\(.*\)$` - binary.LittleEndian.PutUint64(b, sink64) + binary.LittleEndian.PutUint64(b, x) } -func store_le64_idx(b []byte, idx int) { +func store_le64_idx(b []byte, x uint64, idx int) { // amd64:`MOVQ\s.*\(.*\)\(.*\*1\)$`,-`SHR.` // arm64:`MOVD\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BHW]` // ppc64le:`MOVD\s`,-`MOV[BHW]\s` // s390x:`MOVDBR\s.*\(.*\)\(.*\*1\)$` - binary.LittleEndian.PutUint64(b[idx:], sink64) + binary.LittleEndian.PutUint64(b[idx:], x) } func store_le64_load(b []byte, x *[8]byte) { @@ -382,63 +378,63 @@ func store_le64_load(b []byte, x *[8]byte) { binary.LittleEndian.PutUint64(b, binary.LittleEndian.Uint64(x[:])) } -func store_le32(b []byte) { +func store_le32(b []byte, x uint32) { // amd64:`MOVL\s` // arm64:`MOVW`,-`MOV[BH]` // ppc64le:`MOVW\s` // s390x:`MOVWBR\s.*\(.*\)$` - binary.LittleEndian.PutUint32(b, sink32) + binary.LittleEndian.PutUint32(b, x) } -func store_le32_idx(b []byte, idx int) { +func store_le32_idx(b []byte, x uint32, idx int) { // amd64:`MOVL\s` // arm64:`MOVW\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOV[BH]` // ppc64le:`MOVW\s` // s390x:`MOVWBR\s.*\(.*\)\(.*\*1\)$` - binary.LittleEndian.PutUint32(b[idx:], sink32) + binary.LittleEndian.PutUint32(b[idx:], x) } -func store_le16(b []byte) { +func store_le16(b []byte, x uint16) { // amd64:`MOVW\s` // arm64:`MOVH`,-`MOVB` // ppc64le:`MOVH\s` // s390x:`MOVHBR\s.*\(.*\)$` - binary.LittleEndian.PutUint16(b, sink16) + binary.LittleEndian.PutUint16(b, x) } -func store_le16_idx(b []byte, idx int) { +func store_le16_idx(b []byte, x uint16, idx int) { // amd64:`MOVW\s` // arm64:`MOVH\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,-`MOVB` // ppc64le:`MOVH\s` // s390x:`MOVHBR\s.*\(.*\)\(.*\*1\)$` - binary.LittleEndian.PutUint16(b[idx:], sink16) + binary.LittleEndian.PutUint16(b[idx:], x) } -func store_be64(b []byte) { +func store_be64(b []byte, x uint64) { // 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` - binary.BigEndian.PutUint64(b, sink64) + binary.BigEndian.PutUint64(b, x) } -func store_be64_idx(b []byte, idx int) { +func store_be64_idx(b []byte, x uint64, idx int) { // amd64/v1,amd64/v2:`BSWAPQ`,-`SHR.` - // amd64/v3:`MOVBEQ` + // amd64/v3:`MOVBEQ\t[A-Z]+[0-9]*, \([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*1\)` // 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` - binary.BigEndian.PutUint64(b[idx:], sink64) + binary.BigEndian.PutUint64(b[idx:], x) } -func store_be32(b []byte) { +func store_be32(b []byte, x uint32) { // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.` // amd64/v3:`MOVBEL` // arm64:`MOVW`,`REVW`,-`MOV[BH]`,-`REV16W` // ppc64le:`MOVWBR` // s390x:`MOVW\s.*\(.*\)$`,-`SRW\s`,-`SRD\s` - binary.BigEndian.PutUint32(b, sink32) + binary.BigEndian.PutUint32(b, x) } func store_be64_load(b, x *[8]byte) { @@ -453,31 +449,31 @@ func store_be32_load(b, x *[8]byte) { binary.BigEndian.PutUint32(b[:], binary.BigEndian.Uint32(x[:])) } -func store_be32_idx(b []byte, idx int) { +func store_be32_idx(b []byte, x uint32, idx int) { // amd64/v1,amd64/v2:`BSWAPL`,-`SHR.` - // amd64/v3:`MOVBEL` + // amd64/v3:`MOVBEL\t[A-Z]+[0-9]*, \([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*1\)` // 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` - binary.BigEndian.PutUint32(b[idx:], sink32) + binary.BigEndian.PutUint32(b[idx:], x) } -func store_be16(b []byte) { +func store_be16(b []byte, x uint16) { // amd64/v1,amd64/v2:`ROLW\s\$8`,-`SHR.` // amd64/v3:`MOVBEW`,-`ROLW` // arm64:`MOVH`,`REV16W`,-`MOVB` // ppc64le:`MOVHBR` // s390x:`MOVH\s.*\(.*\)$`,-`SRW\s`,-`SRD\s` - binary.BigEndian.PutUint16(b, sink16) + binary.BigEndian.PutUint16(b, x) } -func store_be16_idx(b []byte, idx int) { +func store_be16_idx(b []byte, x uint16, idx int) { // amd64/v1,amd64/v2:`ROLW\s\$8`,-`SHR.` - // amd64/v3: `MOVBEW` + // amd64/v3:`MOVBEW\t[A-Z]+[0-9]*, \([A-Z]+[0-9]*\)\([A-Z]+[0-9]*\*1\)` // arm64:`MOVH\sR[0-9]+,\s\(R[0-9]+\)\(R[0-9]+\)`,`REV16W`,-`MOVB` // ppc64le:`MOVHBR` // s390x:`MOVH\s.*\(.*\)\(.*\*1\)$`,-`SRW\s`,-`SRD\s` - binary.BigEndian.PutUint16(b[idx:], sink16) + binary.BigEndian.PutUint16(b[idx:], x) } func store_le_byte_2(b []byte, val uint16) { diff --git a/test/codegen/switch.go b/test/codegen/switch.go index 2ac817d14c..a6566834a8 100644 --- a/test/codegen/switch.go +++ b/test/codegen/switch.go @@ -20,3 +20,53 @@ func f(x string) int { return -3 } } + +// use jump tables for 8+ int cases +func square(x int) int { + // amd64:`JMP\s\(.*\)\(.*\)$` + switch x { + case 1: + return 1 + case 2: + return 4 + case 3: + return 9 + case 4: + return 16 + case 5: + return 25 + case 6: + return 36 + case 7: + return 49 + case 8: + return 64 + default: + return x * x + } +} + +// use jump tables for 8+ string lengths +func length(x string) int { + // amd64:`JMP\s\(.*\)\(.*\)$` + switch x { + case "a": + return 1 + case "bb": + return 2 + case "ccc": + return 3 + case "dddd": + return 4 + case "eeeee": + return 5 + case "ffffff": + return 6 + case "ggggggg": + return 7 + case "hhhhhhhh": + return 8 + default: + return len(x) + } +} diff --git a/test/fixedbugs/issue52127.go b/test/fixedbugs/issue52127.go new file mode 100644 index 0000000000..7738c3fabf --- /dev/null +++ b/test/fixedbugs/issue52127.go @@ -0,0 +1,62 @@ +// run +//go:build !js +// +build !js + +// Copyright 2022 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. + +// Issue 52127: Too many syntax errors in many files can +// cause deadlocks instead of displaying error messages +// correctly. + +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" +) + +func main() { + dir, err := os.MkdirTemp("", "issue52127") + if err != nil { + panic(err) + } + defer os.RemoveAll(dir) + + args := []string{"go", "build"} + write := func(prefix string, i int, data string) { + filename := filepath.Join(dir, fmt.Sprintf("%s%d.go", prefix, i)) + if err := os.WriteFile(filename, []byte(data), 0o644); err != nil { + panic(err) + } + args = append(args, filename) + } + + for i := 0; i < 100; i++ { + write("a", i, `package p +`) + } + for i := 0; i < 100; i++ { + write("b", i, `package p +var +var +var +var +var +`) + } + + cmd := exec.Command(args[0], args[1:]...) + output, err := cmd.CombinedOutput() + if err == nil { + panic("compile succeeded unexpectedly") + } + if !bytes.Contains(output, []byte("syntax error:")) { + panic(fmt.Sprintf(`missing "syntax error" in compiler output; got: +%s`, output)) + } +} \ No newline at end of file diff --git a/test/fixedbugs/issue52278.go b/test/fixedbugs/issue52278.go new file mode 100644 index 0000000000..56169e6871 --- /dev/null +++ b/test/fixedbugs/issue52278.go @@ -0,0 +1,12 @@ +// compile + +// Copyright 2022 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 + +func main() { +_: +_: +} diff --git a/test/inline.go b/test/inline.go index cb8403e9ce..400898bcee 100644 --- a/test/inline.go +++ b/test/inline.go @@ -160,6 +160,51 @@ func switchType(x interface{}) int { // ERROR "can inline switchType" "x does no } } +// Test that switches on constant things, with constant cases, only cost anything for +// the case that matches. See issue 50253. +func switchConst1(p func(string)) { // ERROR "can inline switchConst" "p does not escape" + const c = 1 + switch c { + case 0: + p("zero") + case 1: + p("one") + case 2: + p("two") + default: + p("other") + } +} + +func switchConst2() string { // ERROR "can inline switchConst2" + switch runtime.GOOS { + case "linux": + return "Leenooks" + case "windows": + return "Windoze" + case "darwin": + return "MackBone" + case "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100": + return "Numbers" + default: + return "oh nose!" + } +} +func switchConst3() string { // ERROR "can inline switchConst3" + switch runtime.GOOS { + case "Linux": + panic("Linux") + case "Windows": + panic("Windows") + case "Darwin": + panic("Darwin") + case "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100": + panic("Numbers") + default: + return "oh nose!" + } +} + func inlineRangeIntoMe(data []int) { // ERROR "can inline inlineRangeIntoMe" "data does not escape" rangeFunc(data, 12) // ERROR "inlining call to rangeFunc" } diff --git a/test/nosplit.go b/test/nosplit.go index 7c7e1bfd99..9cedb93ec3 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -51,7 +51,8 @@ var tests = ` start 0 # Large frame marked nosplit is always wrong. -start 10000 nosplit +# Frame is so large it overflows cmd/link's int16. +start 100000 nosplit REJECT # Calling a large frame is okay. @@ -70,6 +71,18 @@ start 0 call start start 0 nosplit call start REJECT +# Non-trivial recursion runs out of space. +start 0 call f1 +f1 0 nosplit call f2 +f2 0 nosplit call f1 +REJECT +# Same but cycle starts below nosplit entry. +start 0 call f1 +f1 0 nosplit call f2 +f2 0 nosplit call f3 +f3 0 nosplit call f2 +REJECT + # Chains of ordinary functions okay. start 0 call f1 f1 80 call f2 @@ -105,6 +118,14 @@ f8 16 nosplit call end end 1000 REJECT +# Two paths both go over the stack limit. +start 0 call f1 +f1 80 nosplit call f2 call f3 +f2 40 nosplit call f4 +f3 96 nosplit +f4 40 nosplit +REJECT + # Test cases near the 128-byte limit. # Ordinary stack split frame is always okay. @@ -292,12 +313,13 @@ TestCases: fmt.Fprintf(&gobuf, "func main() { main0() }\n") fmt.Fprintf(&buf, "TEXT ·main0(SB),0,$0-0\n\tCALL ·start(SB)\n") + adjusted := false for _, line := range strings.Split(lines, "\n") { line = strings.TrimSpace(line) if line == "" { continue } - for i, subline := range strings.Split(line, ";") { + for _, subline := range strings.Split(line, ";") { subline = strings.TrimSpace(subline) if subline == "" { continue @@ -311,10 +333,19 @@ TestCases: name := m[1] size, _ := strconv.Atoi(m[2]) + if size%ptrSize == 4 { + continue TestCases + } + nosplit := m[3] + body := m[4] + // The limit was originally 128 but is now 800 (928-128). // Instead of rewriting the test cases above, adjust - // the first stack frame to use up the extra bytes. - if i == 0 { + // the first nosplit frame to use up the extra bytes. + // This isn't exactly right because we could have + // nosplit -> split -> nosplit, but it's good enough. + if !adjusted && nosplit != "" { + adjusted = true size += (928 - 128) - 128 // Noopt builds have a larger stackguard. // See ../src/cmd/dist/buildruntime.go:stackGuardMultiplier @@ -326,12 +357,6 @@ TestCases: } } - if size%ptrSize == 4 { - continue TestCases - } - nosplit := m[3] - body := m[4] - if nosplit != "" { nosplit = ",7" } else { diff --git a/test/run.go b/test/run.go index 468379b4a9..45cd086fc4 100644 --- a/test/run.go +++ b/test/run.go @@ -14,6 +14,7 @@ import ( "flag" "fmt" "go/build" + "go/build/constraint" "hash/fnv" "io" "io/fs" @@ -462,40 +463,24 @@ func shouldTest(src string, goos, goarch string) (ok bool, whyNot string) { return true, "" } for _, line := range strings.Split(src, "\n") { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "//") { - line = line[2:] - } else { - continue - } - line = strings.TrimSpace(line) - if len(line) == 0 || line[0] != '+' { - continue - } - gcFlags := os.Getenv("GO_GCFLAGS") - ctxt := &context{ - GOOS: goos, - GOARCH: goarch, - cgoEnabled: cgoEnabled, - noOptEnv: strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"), + if strings.HasPrefix(line, "package ") { + break } - words := strings.Fields(line) - if words[0] == "+build" { - ok := false - for _, word := range words[1:] { - if ctxt.match(word) { - ok = true - break - } + if expr, err := constraint.Parse(line); err == nil { + gcFlags := os.Getenv("GO_GCFLAGS") + ctxt := &context{ + GOOS: goos, + GOARCH: goarch, + cgoEnabled: cgoEnabled, + noOptEnv: strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"), } - if !ok { - // no matching tag found. + + if !expr.Eval(ctxt.match) { return false, line } } } - // no build tags return true, "" } @@ -503,16 +488,6 @@ func (ctxt *context) match(name string) bool { if name == "" { return false } - if first, rest, ok := strings.Cut(name, ","); ok { - // comma-separated list - return ctxt.match(first) && ctxt.match(rest) - } - if strings.HasPrefix(name, "!!") { // bad syntax, reject always - return false - } - if strings.HasPrefix(name, "!") { // negation - return len(name) > 1 && !ctxt.match(name[1:]) - } // Tags must be letters, digits, underscores or dots. // Unlike in Go identifiers, all digits are fine (e.g., "386"). diff --git a/test/shift3.go b/test/shift3.go new file mode 100644 index 0000000000..bed2fd66ef --- /dev/null +++ b/test/shift3.go @@ -0,0 +1,41 @@ +// run + +// Copyright 2022 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 that the compiler's noder uses the correct type +// for RHS shift operands that are untyped. Must compile; +// run for good measure. + +package main + +import ( + "fmt" + "math" +) + +func f(x, y int) { + if x != y { + panic(fmt.Sprintf("%d != %d", x, y)) + } +} + +func main() { + var x int = 1 + f(x<<1, 2) + f(x<<1., 2) + f(x<<(1+0i), 2) + f(x<<0i, 1) + + f(x<<(1<