From 07b0c57a75d4b8c94b16983c5a78026ba46377ac Mon Sep 17 00:00:00 2001 From: Dan Kortschak Date: Mon, 3 May 2021 13:17:04 +0930 Subject: [PATCH 001/137] crypto/subtle: note that input length mismatch makes ConstantTimeCompare return immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: Id1ae6c8fbb8c2f31b251ba141dc2bbedae189006 Reviewed-on: https://go-review.googlesource.com/c/go/+/316169 Trust: Daniel Martí Reviewed-by: Daniel Martí Trust: Johan Brandhorst-Satzkorn --- src/crypto/subtle/constant_time.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/crypto/subtle/constant_time.go b/src/crypto/subtle/constant_time.go index 7c3cf05c462c4..4e0527f9d5a61 100644 --- a/src/crypto/subtle/constant_time.go +++ b/src/crypto/subtle/constant_time.go @@ -8,7 +8,8 @@ package subtle // ConstantTimeCompare returns 1 if the two slices, x and y, have equal contents // and 0 otherwise. The time taken is a function of the length of the slices and -// is independent of the contents. +// is independent of the contents. If the lengths of x and y do not match it +// returns 0 immediately. func ConstantTimeCompare(x, y []byte) int { if len(x) != len(y) { return 0 From eec47d06c1bb7ae6156c3910bfec8a0b5244d157 Mon Sep 17 00:00:00 2001 From: Dmitri Shuralyov Date: Mon, 4 Apr 2022 22:49:34 -0400 Subject: [PATCH 002/137] api: add x509.CertPool.Equal to next/46057.txt CL 388915 added an exported API but was authored (and tested) before the API check became stricter. Updates #46057. Change-Id: Iee6e4969ade77fb0539fa97fcef0047389fb2cf6 Reviewed-on: https://go-review.googlesource.com/c/go/+/398237 Trust: Dmitri Shuralyov Run-TryBot: Dmitri Shuralyov TryBot-Result: Gopher Robot Reviewed-by: Roland Shoemaker --- api/next/46057.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 api/next/46057.txt diff --git a/api/next/46057.txt b/api/next/46057.txt new file mode 100644 index 0000000000000..d971aa7ffd993 --- /dev/null +++ b/api/next/46057.txt @@ -0,0 +1 @@ +pkg crypto/x509, method (*CertPool) Equal(*CertPool) bool #46057 From a041a752955d772a3b5e1080ee7951a66c2be12b Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Wed, 30 Mar 2022 00:18:48 +0200 Subject: [PATCH 003/137] crypto/elliptic: fix BenchmarkMarshalUnmarshal/Compressed Change-Id: Ifbf4a95e5f315a88633ec0170625cadb087167c0 Reviewed-on: https://go-review.googlesource.com/c/go/+/396934 Run-TryBot: Filippo Valsorda Trust: Filippo Valsorda TryBot-Result: Gopher Robot Reviewed-by: Roland Shoemaker --- src/crypto/elliptic/elliptic_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/elliptic/elliptic_test.go b/src/crypto/elliptic/elliptic_test.go index 5481929db1540..eb5f0546c45c6 100644 --- a/src/crypto/elliptic/elliptic_test.go +++ b/src/crypto/elliptic/elliptic_test.go @@ -364,8 +364,8 @@ func BenchmarkMarshalUnmarshal(b *testing.B) { b.Run("Compressed", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { - buf := Marshal(curve, x, y) - xx, yy := Unmarshal(curve, buf) + buf := MarshalCompressed(curve, x, y) + xx, yy := UnmarshalCompressed(curve, buf) if xx.Cmp(x) != 0 || yy.Cmp(y) != 0 { b.Error("Unmarshal output different from Marshal input") } From a5f801f39d8c9d4df2edfd6ffd9171b9216445d1 Mon Sep 17 00:00:00 2001 From: Filippo Valsorda Date: Sun, 27 Mar 2022 03:07:30 +0200 Subject: [PATCH 004/137] crypto/elliptic: delete outdated fuzz test It had not been doing anything since CL 233939, because the Params method was getting upgraded to the assembly one. We could make it use genericParamsForCurve, but really we need lower-level, targeted Go 1.18 fuzz targets in nistec now. Change-Id: I5b198a309aa90ecef9c04aaa6c107d5c0a41a44b Reviewed-on: https://go-review.googlesource.com/c/go/+/396254 Run-TryBot: Filippo Valsorda Trust: Filippo Valsorda TryBot-Result: Gopher Robot Reviewed-by: Roland Shoemaker --- src/crypto/elliptic/fuzz_test.go | 53 -------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 src/crypto/elliptic/fuzz_test.go diff --git a/src/crypto/elliptic/fuzz_test.go b/src/crypto/elliptic/fuzz_test.go deleted file mode 100644 index 2b5ddae1d9e96..0000000000000 --- a/src/crypto/elliptic/fuzz_test.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build amd64 || arm64 || ppc64le - -package elliptic - -import ( - "crypto/rand" - "testing" - "time" -) - -func TestFuzz(t *testing.T) { - p256 := P256() - p256Generic := p256.Params() - - var scalar1 [32]byte - var scalar2 [32]byte - var timeout *time.Timer - - if testing.Short() { - timeout = time.NewTimer(10 * time.Millisecond) - } else { - timeout = time.NewTimer(2 * time.Second) - } - - for { - select { - case <-timeout.C: - return - default: - } - - rand.Read(scalar1[:]) - rand.Read(scalar2[:]) - - x, y := p256.ScalarBaseMult(scalar1[:]) - x2, y2 := p256Generic.ScalarBaseMult(scalar1[:]) - - xx, yy := p256.ScalarMult(x, y, scalar2[:]) - xx2, yy2 := p256Generic.ScalarMult(x2, y2, scalar2[:]) - - if x.Cmp(x2) != 0 || y.Cmp(y2) != 0 { - t.Fatalf("ScalarBaseMult does not match reference result with scalar: %x, please report this error to security@golang.org", scalar1) - } - - if xx.Cmp(xx2) != 0 || yy.Cmp(yy2) != 0 { - t.Fatalf("ScalarMult does not match reference result with scalars: %x and %x, please report this error to security@golang.org", scalar1, scalar2) - } - } -} From 62bceae32de7bad48fbdc1fe3fae14c81cd093d2 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Fri, 1 Apr 2022 14:54:39 -0400 Subject: [PATCH 005/137] cmd/go: quote fragments in CGO_ env variables reported by 'go env' These fields have been parsed as quoted fields since CL 334732, but we missed the unparsing side in 'go env'. Certain scripts (notably make.ba{sh,t}) expect to be able to set the environment to exactly what 'go env' reports, so for round-trip purposes it is important to match the marshaling and unmarshaling functions. (Noticed while debugging #52009.) Updates #41400 Change-Id: I0ff39b7a6e1328111c285c97cd23f79b723f3c73 Reviewed-on: https://go-review.googlesource.com/c/go/+/398058 Trust: Bryan Mills Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/cmd/go/internal/envcmd/env.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go index fcabc8d1c71a3..aab21af8557c4 100644 --- a/src/cmd/go/internal/envcmd/env.go +++ b/src/cmd/go/internal/envcmd/env.go @@ -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:])}, } } From 592078ff3f7d938b2fbcd98ddcb72e0d2748fdb1 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Fri, 1 Apr 2022 15:55:56 -0400 Subject: [PATCH 006/137] cmd/go/internal/work: omit modinfo line from cache key when empty Cache keys are dumped in case of mismatch; an empty modinfo string adds noise to that dump without a corresponding benefit. For #52009. Change-Id: I1b4cd85fa5ff920973552fc94977954f93622a32 Reviewed-on: https://go-review.googlesource.com/c/go/+/398059 Trust: Bryan Mills Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/cmd/go/internal/work/exec.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 4252209f1002f..0c8e1dcdafd12 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 { From 5210a7128563e64952d432a0efd2d38c32cd090c Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Fri, 1 Apr 2022 15:36:17 -0400 Subject: [PATCH 007/137] run.bat: use cmd/dist instead of 'go install' to rebuild std and cmd cmd/dist may set and/or unset variables before building, and at any rate it is fragile to run 'go install' before sourcing env.bat. The build-stamp information embedded by the 'go' command is currently sensitive to whether CGO_* variables are implicit or explicit, so running 'go install' before env.bat may cause stamped metadata to become stale. (Explicitly setting to the default arguably ought to produce the same metadata as leaving the variables unset, but that's a separate issue and a bigger cleanup.) Moreover, run.bat is supposed to parallel run.bash, and run.bash already hasn't invoked 'go install' itself since CL 6531! For #52009 Change-Id: Ie35217913f02cc7e0c3f9b12874abd7416473478 Reviewed-on: https://go-review.googlesource.com/c/go/+/398060 Trust: Bryan Mills Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov --- src/run.bat | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/run.bat b/src/run.bat index edcaf5265931b..1f16c493bbcb4 100644 --- a/src/run.bat +++ b/src/run.bat @@ -24,26 +24,20 @@ 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 if errorlevel 1 goto fail call env.bat del env.bat -echo. -..\bin\go tool dist test +if x%1==x--no-rebuild goto norebuild +..\bin\go tool dist test --rebuild if errorlevel 1 goto fail -echo. +goto end +:norebuild +..\bin\go tool dist test +if errorlevel 1 goto fail goto end :fail From 1a955bcdb701d788b048a39d7273621729e257bc Mon Sep 17 00:00:00 2001 From: vpachkov Date: Thu, 2 Dec 2021 16:18:29 +0300 Subject: [PATCH 008/137] reflectdata: unroll a loop in array equal function generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As josharian mentioned, a compare function could benefit from unrolling a loop for arrays. This commit introduces such functionality. name old time/op new time/op delta EqArrayOfStrings5-12 12.5ns ± 1% 8.4ns ± 1% -33.05% (p=0.008 n=5+5) EqArrayOfStrings64-12 71.7ns ± 1% 64.1ns ± 1% -10.57% (p=0.008 n=5+5) EqArrayOfStrings1024-12 1.12µs ± 1% 1.01µs ± 0% -9.77% (p=0.008 n=5+5) [Geo mean] 100ns 81ns -18.56% name old time/op new time/op delta EqArrayOfFloats5-12 4.50ns ± 2% 3.32ns ± 1% -26.09% (p=0.008 n=5+5) EqArrayOfFloats64-12 41.3ns ± 1% 35.7ns ± 0% -13.63% (p=0.016 n=5+4) EqArrayOfFloats1024-12 619ns ± 1% 557ns ± 1% -9.95% (p=0.008 n=5+5) [Geo mean] 48.6ns 40.4ns -16.85% Change-Id: If1b69c5cf3fb246bb0275a292118b0b93ad9c9a8 Reviewed-on: https://go-review.googlesource.com/c/go/+/368614 Reviewed-by: Keith Randall Trust: Emmanuel Odeke Run-TryBot: Emmanuel Odeke TryBot-Result: Gopher Robot --- src/cmd/compile/internal/reflectdata/alg.go | 92 ++++++++++++------- .../compile/internal/reflectdata/alg_test.go | 76 +++++++++++++++ 2 files changed, 135 insertions(+), 33 deletions(-) create mode 100644 src/cmd/compile/internal/reflectdata/alg_test.go diff --git a/src/cmd/compile/internal/reflectdata/alg.go b/src/cmd/compile/internal/reflectdata/alg.go index d000618bd64f5..526315d5570c3 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,46 +445,69 @@ 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) + + // 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 last { - fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(true))) + + 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)))) + } } switch t.Elem().Kind() { 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) 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 0000000000000..1e57b913fdab3 --- /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 + } +} From 65153e478e302eaf3556372ff257ebfc893943c1 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 2 Mar 2022 13:20:02 -0800 Subject: [PATCH 009/137] crypto/x509: rework path building This change does four things: * removes the chain cache * during path building, equality is determined by checking if the subjects and public keys match, rather than checking if the entire certificates are equal * enforces EKU suitability during path building * enforces name constraints on intermediates and roots which have SANs during path building The chain cache is removed as it was causing duplicate chains to be returned, in some cases shadowing better, shorter chains if a longer chain was found first. Checking equality using the subjects and public keys, rather than the entire certificates, allows the path builder to ignore chains which contain cross-signature loops. EKU checking is done during path building, as the previous behavior of only checking EKUs once the path had been built caused the path builder to incorrectly ignore valid paths when it encountered a path which would later be ruled invalid because of unacceptable EKU usage. Name constraints are applied uniformly across all certificates, not just leaves, in order to be more consistent. Fixes #48869 Fixes #45856 Change-Id: I4ca1cd43510d061e148f953d6c1ed935100fdb10 Reviewed-on: https://go-review.googlesource.com/c/go/+/389555 Reviewed-by: Damien Neil Trust: Cherry Mui Trust: Roland Shoemaker Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker TryBot-Result: Gopher Robot --- src/crypto/x509/verify.go | 208 +++++++-------- src/crypto/x509/verify_test.go | 449 +++++++++++++++++++++++++++++++++ 2 files changed, 556 insertions(+), 101 deletions(-) diff --git a/src/crypto/x509/verify.go b/src/crypto/x509/verify.go index 4be4eb60959d5..77ad6868fa65f 100644 --- a/src/crypto/x509/verify.go +++ b/src/crypto/x509/verify.go @@ -6,6 +6,7 @@ package x509 import ( "bytes" + "crypto" "errors" "fmt" "net" @@ -597,73 +598,101 @@ func (c *Certificate) isValid(certType int, currentChain []*Certificate, opts *V leaf = currentChain[0] } - if (certType == intermediateCertificate || certType == rootCertificate) && - c.hasNameConstraints() && leaf.hasSANExtension() { - err := forEachSAN(leaf.getSANExtension(), func(tag int, data []byte) error { - switch tag { - case nameTypeEmail: - name := string(data) - mailbox, ok := parseRFC2821Mailbox(name) - if !ok { - return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox) - } - - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, - func(parsedName, constraint any) (bool, error) { - return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) - }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { - return err - } - - case nameTypeDNS: - name := string(data) - if _, ok := domainToReverseLabels(name); !ok { - return fmt.Errorf("x509: cannot parse dnsName %q", name) - } - - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, - func(parsedName, constraint any) (bool, error) { - return matchDomainConstraint(parsedName.(string), constraint.(string)) - }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { - return err - } - - case nameTypeURI: - name := string(data) - uri, err := url.Parse(name) - if err != nil { - return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name) - } - - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, - func(parsedName, constraint any) (bool, error) { - return matchURIConstraint(parsedName.(*url.URL), constraint.(string)) - }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { - return err + if (len(c.ExtKeyUsage) > 0 || len(c.UnknownExtKeyUsage) > 0) && len(opts.KeyUsages) > 0 { + acceptableUsage := false + um := make(map[ExtKeyUsage]bool, len(opts.KeyUsages)) + for _, u := range opts.KeyUsages { + um[u] = true + } + if !um[ExtKeyUsageAny] { + for _, u := range c.ExtKeyUsage { + if u == ExtKeyUsageAny || um[u] { + acceptableUsage = true + break } + } + if !acceptableUsage { + return CertificateInvalidError{c, IncompatibleUsage, ""} + } + } + } - case nameTypeIP: - ip := net.IP(data) - if l := len(ip); l != net.IPv4len && l != net.IPv6len { - return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data) + if (certType == intermediateCertificate || certType == rootCertificate) && + c.hasNameConstraints() { + toCheck := []*Certificate{} + if leaf.hasSANExtension() { + toCheck = append(toCheck, leaf) + } + if c.hasSANExtension() { + toCheck = append(toCheck, c) + } + for _, sanCert := range toCheck { + err := forEachSAN(sanCert.getSANExtension(), func(tag int, data []byte) error { + switch tag { + case nameTypeEmail: + name := string(data) + mailbox, ok := parseRFC2821Mailbox(name) + if !ok { + return fmt.Errorf("x509: cannot parse rfc822Name %q", mailbox) + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "email address", name, mailbox, + func(parsedName, constraint any) (bool, error) { + return matchEmailConstraint(parsedName.(rfc2821Mailbox), constraint.(string)) + }, c.PermittedEmailAddresses, c.ExcludedEmailAddresses); err != nil { + return err + } + + case nameTypeDNS: + name := string(data) + if _, ok := domainToReverseLabels(name); !ok { + return fmt.Errorf("x509: cannot parse dnsName %q", name) + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "DNS name", name, name, + func(parsedName, constraint any) (bool, error) { + return matchDomainConstraint(parsedName.(string), constraint.(string)) + }, c.PermittedDNSDomains, c.ExcludedDNSDomains); err != nil { + return err + } + + case nameTypeURI: + name := string(data) + uri, err := url.Parse(name) + if err != nil { + return fmt.Errorf("x509: internal error: URI SAN %q failed to parse", name) + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "URI", name, uri, + func(parsedName, constraint any) (bool, error) { + return matchURIConstraint(parsedName.(*url.URL), constraint.(string)) + }, c.PermittedURIDomains, c.ExcludedURIDomains); err != nil { + return err + } + + case nameTypeIP: + ip := net.IP(data) + if l := len(ip); l != net.IPv4len && l != net.IPv6len { + return fmt.Errorf("x509: internal error: IP SAN %x failed to parse", data) + } + + if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, + func(parsedName, constraint any) (bool, error) { + return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) + }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { + return err + } + + default: + // Unknown SAN types are ignored. } - if err := c.checkNameConstraints(&comparisonCount, maxConstraintComparisons, "IP address", ip.String(), ip, - func(parsedName, constraint any) (bool, error) { - return matchIPConstraint(parsedName.(net.IP), constraint.(*net.IPNet)) - }, c.PermittedIPRanges, c.ExcludedIPRanges); err != nil { - return err - } + return nil + }) - default: - // Unknown SAN types are ignored. + if err != nil { + return err } - - return nil - }) - - if err != nil { - return err } } @@ -767,6 +796,10 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e } } + if len(opts.KeyUsages) == 0 { + opts.KeyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth} + } + err = c.isValid(leafCertificate, nil, &opts) if err != nil { return @@ -779,38 +812,10 @@ func (c *Certificate) Verify(opts VerifyOptions) (chains [][]*Certificate, err e } } - var candidateChains [][]*Certificate if opts.Roots.contains(c) { - candidateChains = append(candidateChains, []*Certificate{c}) - } else { - if candidateChains, err = c.buildChains(nil, []*Certificate{c}, nil, &opts); err != nil { - return nil, err - } - } - - keyUsages := opts.KeyUsages - if len(keyUsages) == 0 { - keyUsages = []ExtKeyUsage{ExtKeyUsageServerAuth} - } - - // If any key usage is acceptable then we're done. - for _, usage := range keyUsages { - if usage == ExtKeyUsageAny { - return candidateChains, nil - } - } - - for _, candidate := range candidateChains { - if checkChainForKeyUsage(candidate, keyUsages) { - chains = append(chains, candidate) - } - } - - if len(chains) == 0 { - return nil, CertificateInvalidError{c, IncompatibleUsage, ""} + return [][]*Certificate{{c}}, nil } - - return chains, nil + return c.buildChains([]*Certificate{c}, nil, &opts) } func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate { @@ -826,15 +831,22 @@ func appendToFreshChain(chain []*Certificate, cert *Certificate) []*Certificate // for failed checks due to different intermediates having the same Subject. const maxChainSignatureChecks = 100 -func (c *Certificate) buildChains(cache map[*Certificate][][]*Certificate, currentChain []*Certificate, sigChecks *int, opts *VerifyOptions) (chains [][]*Certificate, err error) { +func (c *Certificate) buildChains(currentChain []*Certificate, sigChecks *int, opts *VerifyOptions) (chains [][]*Certificate, err error) { var ( hintErr error hintCert *Certificate ) + type pubKeyEqual interface { + Equal(crypto.PublicKey) bool + } + considerCandidate := func(certType int, candidate *Certificate) { for _, cert := range currentChain { - if cert.Equal(candidate) { + // If a certificate already appeared in the chain we've built, don't + // reconsider it. This prevents loops, for isntance those created by + // mutual cross-signatures, or other cross-signature bridges oddities. + if bytes.Equal(cert.RawSubject, candidate.RawSubject) && cert.PublicKey.(pubKeyEqual).Equal(candidate.PublicKey) { return } } @@ -865,14 +877,8 @@ func (c *Certificate) buildChains(cache map[*Certificate][][]*Certificate, curre case rootCertificate: chains = append(chains, appendToFreshChain(currentChain, candidate)) case intermediateCertificate: - if cache == nil { - cache = make(map[*Certificate][][]*Certificate) - } - childChains, ok := cache[candidate] - if !ok { - childChains, err = candidate.buildChains(cache, appendToFreshChain(currentChain, candidate), sigChecks, opts) - cache[candidate] = childChains - } + var childChains [][]*Certificate + childChains, err = candidate.buildChains(appendToFreshChain(currentChain, candidate), sigChecks, opts) chains = append(chains, childChains...) } } diff --git a/src/crypto/x509/verify_test.go b/src/crypto/x509/verify_test.go index 100a8ff0f94ae..1b2cbe34dd2f8 100644 --- a/src/crypto/x509/verify_test.go +++ b/src/crypto/x509/verify_test.go @@ -15,7 +15,9 @@ import ( "fmt" "internal/testenv" "math/big" + "reflect" "runtime" + "sort" "strings" "testing" "time" @@ -1910,3 +1912,450 @@ func TestIssue51759(t *testing.T) { } }) } + +type trustGraphEdge struct { + Issuer string + Subject string + Type int + MutateTemplate func(*Certificate) +} + +type trustGraphDescription struct { + Roots []string + Leaf string + Graph []trustGraphEdge +} + +func genCertEdge(t *testing.T, subject string, key crypto.Signer, mutateTmpl func(*Certificate), certType int, issuer *Certificate, signer crypto.Signer) *Certificate { + t.Helper() + + serial, err := rand.Int(rand.Reader, big.NewInt(100)) + if err != nil { + t.Fatalf("failed to generate test serial: %s", err) + } + tmpl := &Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: subject}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + } + if certType == rootCertificate || certType == intermediateCertificate { + tmpl.IsCA, tmpl.BasicConstraintsValid = true, true + tmpl.KeyUsage = KeyUsageCertSign + } else if certType == leafCertificate { + tmpl.DNSNames = []string{"localhost"} + } + if mutateTmpl != nil { + mutateTmpl(tmpl) + } + + if certType == rootCertificate { + issuer = tmpl + signer = key + } + + d, err := CreateCertificate(rand.Reader, tmpl, issuer, key.Public(), signer) + if err != nil { + t.Fatalf("failed to generate test cert: %s", err) + } + c, err := ParseCertificate(d) + if err != nil { + t.Fatalf("failed to parse test cert: %s", err) + } + return c +} + +func buildTrustGraph(t *testing.T, d trustGraphDescription) (*CertPool, *CertPool, *Certificate) { + t.Helper() + + certs := map[string]*Certificate{} + keys := map[string]crypto.Signer{} + roots := []*Certificate{} + for _, r := range d.Roots { + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate test key: %s", err) + } + root := genCertEdge(t, r, k, nil, rootCertificate, nil, nil) + roots = append(roots, root) + certs[r] = root + keys[r] = k + } + + intermediates := []*Certificate{} + var leaf *Certificate + for _, e := range d.Graph { + issuerCert, ok := certs[e.Issuer] + if !ok { + t.Fatalf("unknown issuer %s", e.Issuer) + } + issuerKey, ok := keys[e.Issuer] + if !ok { + t.Fatalf("unknown issuer %s", e.Issuer) + } + + k, ok := keys[e.Subject] + if !ok { + var err error + k, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate test key: %s", err) + } + keys[e.Subject] = k + } + cert := genCertEdge(t, e.Subject, k, e.MutateTemplate, e.Type, issuerCert, issuerKey) + certs[e.Subject] = cert + if e.Subject == d.Leaf { + leaf = cert + } else { + intermediates = append(intermediates, cert) + } + } + + rootPool, intermediatePool := NewCertPool(), NewCertPool() + for i := len(roots) - 1; i >= 0; i-- { + rootPool.AddCert(roots[i]) + } + for i := len(intermediates) - 1; i >= 0; i-- { + intermediatePool.AddCert(intermediates[i]) + } + + return rootPool, intermediatePool, leaf +} + +func chainsToStrings(chains [][]*Certificate) []string { + chainStrings := []string{} + for _, chain := range chains { + names := []string{} + for _, c := range chain { + names = append(names, c.Subject.String()) + } + chainStrings = append(chainStrings, strings.Join(names, " -> ")) + } + sort.Strings(chainStrings) + return chainStrings +} + +func TestPathBuilding(t *testing.T) { + tests := []struct { + name string + graph trustGraphDescription + expectedChains []string + expectedErr string + }{ + { + // Build the following graph from RFC 4158, figure 7 (note that in this graph edges represent + // certificates where the parent is the issuer and the child is the subject.) For the certificate + // C->B, use an unsupported ExtKeyUsage (in this case ExtKeyUsageCodeSigning) which invalidates + // the path Trust Anchor -> C -> B -> EE. The remaining valid paths should be: + // * Trust Anchor -> A -> B -> EE + // * Trust Anchor -> C -> A -> B -> EE + // + // +---------+ + // | Trust | + // | Anchor | + // +---------+ + // | | + // v v + // +---+ +---+ + // | A |<-->| C | + // +---+ +---+ + // | | + // | +---+ | + // +->| B |<-+ + // +---+ + // | + // v + // +----+ + // | EE | + // +----+ + name: "bad EKU", + graph: trustGraphDescription{ + Roots: []string{"root"}, + Leaf: "leaf", + Graph: []trustGraphEdge{ + { + Issuer: "root", + Subject: "inter a", + Type: intermediateCertificate, + }, + { + Issuer: "root", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter c", + Subject: "inter a", + Type: intermediateCertificate, + }, + { + Issuer: "inter a", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter c", + Subject: "inter b", + Type: intermediateCertificate, + MutateTemplate: func(t *Certificate) { + t.ExtKeyUsage = []ExtKeyUsage{ExtKeyUsageCodeSigning} + }, + }, + { + Issuer: "inter a", + Subject: "inter b", + Type: intermediateCertificate, + }, + { + Issuer: "inter b", + Subject: "leaf", + Type: leafCertificate, + }, + }, + }, + expectedChains: []string{ + "CN=leaf -> CN=inter b -> CN=inter a -> CN=inter c -> CN=root", + "CN=leaf -> CN=inter b -> CN=inter a -> CN=root", + }, + }, + { + // Build the following graph from RFC 4158, figure 7 (note that in this graph edges represent + // certificates where the parent is the issuer and the child is the subject.) For the certificate + // C->B, use a unconstrained SAN which invalidates the path Trust Anchor -> C -> B -> EE. The + // remaining valid paths should be: + // * Trust Anchor -> A -> B -> EE + // * Trust Anchor -> C -> A -> B -> EE + // + // +---------+ + // | Trust | + // | Anchor | + // +---------+ + // | | + // v v + // +---+ +---+ + // | A |<-->| C | + // +---+ +---+ + // | | + // | +---+ | + // +->| B |<-+ + // +---+ + // | + // v + // +----+ + // | EE | + // +----+ + name: "bad EKU", + graph: trustGraphDescription{ + Roots: []string{"root"}, + Leaf: "leaf", + Graph: []trustGraphEdge{ + { + Issuer: "root", + Subject: "inter a", + Type: intermediateCertificate, + }, + { + Issuer: "root", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter c", + Subject: "inter a", + Type: intermediateCertificate, + }, + { + Issuer: "inter a", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter c", + Subject: "inter b", + Type: intermediateCertificate, + MutateTemplate: func(t *Certificate) { + t.PermittedDNSDomains = []string{"good"} + t.DNSNames = []string{"bad"} + }, + }, + { + Issuer: "inter a", + Subject: "inter b", + Type: intermediateCertificate, + }, + { + Issuer: "inter b", + Subject: "leaf", + Type: leafCertificate, + }, + }, + }, + expectedChains: []string{ + "CN=leaf -> CN=inter b -> CN=inter a -> CN=inter c -> CN=root", + "CN=leaf -> CN=inter b -> CN=inter a -> CN=root", + }, + }, + { + // Build the following graph, we should find both paths: + // * Trust Anchor -> A -> C -> EE + // * Trust Anchor -> A -> B -> C -> EE + // + // +---------+ + // | Trust | + // | Anchor | + // +---------+ + // | + // v + // +---+ + // | A | + // +---+ + // | | + // | +----+ + // | v + // | +---+ + // | | B | + // | +---+ + // | | + // | +---v + // v v + // +---+ + // | C | + // +---+ + // | + // v + // +----+ + // | EE | + // +----+ + name: "all paths", + graph: trustGraphDescription{ + Roots: []string{"root"}, + Leaf: "leaf", + Graph: []trustGraphEdge{ + { + Issuer: "root", + Subject: "inter a", + Type: intermediateCertificate, + }, + { + Issuer: "inter a", + Subject: "inter b", + Type: intermediateCertificate, + }, + { + Issuer: "inter a", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter b", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter c", + Subject: "leaf", + Type: leafCertificate, + }, + }, + }, + expectedChains: []string{ + "CN=leaf -> CN=inter c -> CN=inter a -> CN=root", + "CN=leaf -> CN=inter c -> CN=inter b -> CN=inter a -> CN=root", + }, + }, + { + // Build the following graph, which contains a cross-signature loop + // (A and C cross sign each other). Paths that include the A -> C -> A + // (and vice versa) loop should be ignored, resulting in the paths: + // * Trust Anchor -> A -> B -> EE + // * Trust Anchor -> C -> B -> EE + // * Trust Anchor -> A -> C -> B -> EE + // * Trust Anchor -> C -> A -> B -> EE + // + // +---------+ + // | Trust | + // | Anchor | + // +---------+ + // | | + // v v + // +---+ +---+ + // | A |<-->| C | + // +---+ +---+ + // | | + // | +---+ | + // +->| B |<-+ + // +---+ + // | + // v + // +----+ + // | EE | + // +----+ + name: "ignore cross-sig loops", + graph: trustGraphDescription{ + Roots: []string{"root"}, + Leaf: "leaf", + Graph: []trustGraphEdge{ + { + Issuer: "root", + Subject: "inter a", + Type: intermediateCertificate, + }, + { + Issuer: "root", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter c", + Subject: "inter a", + Type: intermediateCertificate, + }, + { + Issuer: "inter a", + Subject: "inter c", + Type: intermediateCertificate, + }, + { + Issuer: "inter c", + Subject: "inter b", + Type: intermediateCertificate, + }, + { + Issuer: "inter a", + Subject: "inter b", + Type: intermediateCertificate, + }, + { + Issuer: "inter b", + Subject: "leaf", + Type: leafCertificate, + }, + }, + }, + expectedChains: []string{ + "CN=leaf -> CN=inter b -> CN=inter a -> CN=inter c -> CN=root", + "CN=leaf -> CN=inter b -> CN=inter a -> CN=root", + "CN=leaf -> CN=inter b -> CN=inter c -> CN=inter a -> CN=root", + "CN=leaf -> CN=inter b -> CN=inter c -> CN=root", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + roots, intermediates, leaf := buildTrustGraph(t, tc.graph) + chains, err := leaf.Verify(VerifyOptions{ + Roots: roots, + Intermediates: intermediates, + }) + if err != nil && err.Error() != tc.expectedErr { + t.Fatalf("unexpected error: got %q, want %q", err, tc.expectedErr) + } + gotChains := chainsToStrings(chains) + if !reflect.DeepEqual(gotChains, tc.expectedChains) { + t.Errorf("unexpected chains returned:\ngot:\n\t%s\nwant:\n\t%s", strings.Join(gotChains, "\n\t"), strings.Join(tc.expectedChains, "\n\t")) + } + }) + } +} From 7c45dafdb2aae2afaf09aa90b3b5338c992f6912 Mon Sep 17 00:00:00 2001 From: zhouguangyuan Date: Thu, 31 Mar 2022 20:16:23 +0800 Subject: [PATCH 010/137] cmd/internal/objabi: fix FuncID of runtime.rt0_go and runtime.systemstack_switch Fixes #52092 Change-Id: I774a6722c6e3ce6781e1d8bc16ac68efee6f9c70 Reviewed-on: https://go-review.googlesource.com/c/go/+/396797 Run-TryBot: Cherry Mui TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Trust: Ian Lance Taylor --- src/cmd/internal/objabi/funcid.go | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/cmd/internal/objabi/funcid.go b/src/cmd/internal/objabi/funcid.go index 084fcdf712159..c2eb4d545b7e2 100644 --- a/src/cmd/internal/objabi/funcid.go +++ b/src/cmd/internal/objabi/funcid.go @@ -49,26 +49,26 @@ const ( ) var funcIDs = map[string]FuncID{ - "abort": FuncID_abort, - "asmcgocall": FuncID_asmcgocall, - "asyncPreempt": FuncID_asyncPreempt, - "cgocallback": FuncID_cgocallback, - "debugCallV2": FuncID_debugCallV2, - "gcBgMarkWorker": FuncID_gcBgMarkWorker, - "go": FuncID_rt0_go, - "goexit": FuncID_goexit, - "gogo": FuncID_gogo, - "gopanic": FuncID_gopanic, - "handleAsyncEvent": FuncID_handleAsyncEvent, - "main": FuncID_runtime_main, - "mcall": FuncID_mcall, - "morestack": FuncID_morestack, - "mstart": FuncID_mstart, - "panicwrap": FuncID_panicwrap, - "runfinq": FuncID_runfinq, - "sigpanic": FuncID_sigpanic, - "switch": FuncID_systemstack_switch, - "systemstack": FuncID_systemstack, + "abort": FuncID_abort, + "asmcgocall": FuncID_asmcgocall, + "asyncPreempt": FuncID_asyncPreempt, + "cgocallback": FuncID_cgocallback, + "debugCallV2": FuncID_debugCallV2, + "gcBgMarkWorker": FuncID_gcBgMarkWorker, + "rt0_go": FuncID_rt0_go, + "goexit": FuncID_goexit, + "gogo": FuncID_gogo, + "gopanic": FuncID_gopanic, + "handleAsyncEvent": FuncID_handleAsyncEvent, + "main": FuncID_runtime_main, + "mcall": FuncID_mcall, + "morestack": FuncID_morestack, + "mstart": FuncID_mstart, + "panicwrap": FuncID_panicwrap, + "runfinq": FuncID_runfinq, + "sigpanic": FuncID_sigpanic, + "systemstack_switch": FuncID_systemstack_switch, + "systemstack": FuncID_systemstack, // Don't show in call stack but otherwise not special. "deferreturn": FuncID_wrapper, From b4cabaf8c094da8387ac6274706fe4850d77ebc6 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Tue, 5 Apr 2022 11:51:43 -0400 Subject: [PATCH 011/137] os/signal: run TestNotifyContextNotifications subtests in parallel If we run out of time on the first subtest, we don't want to start the second one with essentially no time remaining. (Moreover, there is no compelling reason not to run these tests in parallel, since they send signals to separate processes.) For #51054. Change-Id: I0424e08c3a9d2db986568d5a5c004859b52f7c51 Reviewed-on: https://go-review.googlesource.com/c/go/+/398454 Trust: Bryan Mills Run-TryBot: Bryan Mills Reviewed-by: Ian Lance Taylor Reviewed-by: Henrique Vicente de Oliveira Pinto TryBot-Result: Gopher Robot --- src/os/signal/signal_test.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/os/signal/signal_test.go b/src/os/signal/signal_test.go index 3182e83b4e9f7..086ecdbcd5e73 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) } }) From 81431c7aa7c5d782e72dec342442ea7664ef1783 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 3 Feb 2022 14:05:46 -0500 Subject: [PATCH 012/137] =?UTF-8?q?all:=20replace=20``=20and=20''=20with?= =?UTF-8?q?=20=E2=80=9C=20(U+201C)=20and=20=E2=80=9D=20(U+201D)=20in=20doc?= =?UTF-8?q?=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit go/doc in all its forms applies this replacement when rendering the comments. We are considering formatting doc comments, including doing this replacement as part of the formatting. Apply it to our source files ahead of time. For #51082. Change-Id: Ifcc1f5861abb57c5d14e7d8c2102dfb31b7a3a19 Reviewed-on: https://go-review.googlesource.com/c/go/+/384262 Trust: Russ Cox Run-TryBot: Russ Cox TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/cmd/go/internal/bug/bug.go | 2 +- src/cmd/go/internal/clean/clean.go | 2 +- src/cmd/go/internal/doc/doc.go | 2 +- src/cmd/go/internal/envcmd/env.go | 2 +- src/cmd/go/internal/fix/fix.go | 2 +- src/cmd/go/internal/fmtcmd/fmt.go | 2 +- src/cmd/go/internal/generate/generate.go | 2 +- src/cmd/go/internal/get/get.go | 2 +- src/cmd/go/internal/help/help.go | 2 +- src/cmd/go/internal/list/list.go | 2 +- src/cmd/go/internal/modcmd/mod.go | 2 +- src/cmd/go/internal/modget/get.go | 2 +- src/cmd/go/internal/run/run.go | 2 +- src/cmd/go/internal/tool/tool.go | 2 +- src/cmd/go/internal/version/version.go | 2 +- src/cmd/go/internal/vet/vet.go | 2 +- src/cmd/go/internal/workcmd/work.go | 2 +- src/cmd/link/internal/ld/data.go | 2 +- .../vendor/golang.org/x/arch/x86/x86asm/gnu.go | 2 +- .../vendor/golang.org/x/arch/x86/x86asm/inst.go | 2 +- src/compress/bzip2/bzip2.go | 4 ++-- src/compress/lzw/reader.go | 4 ++-- src/crypto/rsa/pkcs1v15.go | 4 ++-- src/debug/dwarf/entry.go | 4 ++-- src/debug/dwarf/type.go | 2 +- src/encoding/asn1/asn1.go | 2 +- src/encoding/base32/base32.go | 2 +- src/encoding/json/decode.go | 2 +- src/fmt/print.go | 2 +- src/go/doc/testdata/testing.go | 2 +- src/internal/testenv/testenv.go | 8 ++++---- src/math/big/floatconv.go | 6 +++--- src/math/big/int.go | 12 ++++++------ src/math/big/intconv.go | 4 ++-- src/math/big/natconv.go | 6 +++--- src/math/big/ratconv.go | 16 ++++++++-------- src/math/bits.go | 4 ++-- src/math/cmplx/isnan.go | 2 +- src/net/http/server.go | 4 ++-- src/net/url/url.go | 6 +++--- src/os/file_plan9.go | 2 +- src/os/file_unix.go | 2 +- src/os/file_windows.go | 2 +- src/path/filepath/path.go | 4 ++-- src/path/path.go | 4 ++-- src/reflect/deepequal.go | 2 +- src/regexp/exec_test.go | 4 ++-- src/regexp/syntax/prog.go | 2 +- src/runtime/float.go | 2 +- src/time/tick.go | 2 +- 50 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/cmd/go/internal/bug/bug.go b/src/cmd/go/internal/bug/bug.go index 702dc2a14acda..b4181b1e44adc 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/clean/clean.go b/src/cmd/go/internal/clean/clean.go index dc93cdf598312..8564411fb6c15 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 7741a9022c910..3b6cd94799ada 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 aab21af8557c4..529351dfbd8a7 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 ( diff --git a/src/cmd/go/internal/fix/fix.go b/src/cmd/go/internal/fix/fix.go index d8ba353de6595..3705b30ef9533 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 19656eab7fc66..3dc29d40b2f56 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 54ccfe78f24b6..0021bcc75aa2b 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 ( diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go index 8cf8fe6645f36..1bb67bcf51b55 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 2a07d2423bdd9..f73097af8455a 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/list/list.go b/src/cmd/go/internal/list/list.go index 5fc33989cd934..e039b9faa178d 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 ( diff --git a/src/cmd/go/internal/modcmd/mod.go b/src/cmd/go/internal/modcmd/mod.go index d72d0cacd68dd..125ba336a0edd 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 3d8463e892c69..751f15aaacb7b 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 diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go index 35c57833730c7..ebe161181901b 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/tool/tool.go b/src/cmd/go/internal/tool/tool.go index 4fe4c2baeda1f..e8b55092d83e8 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 1c0eb5407d960..5de7b83efaea8 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 d3e0dd8116f48..a0b11fdd3dc5f 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/workcmd/work.go b/src/cmd/go/internal/workcmd/work.go index 39c81e8f5dc05..c99cc2a3fa9d1 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/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 0ec1e526a9840..1e98452dc097b 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -2140,7 +2140,7 @@ func (state *dodataState) dodataSect(ctxt *Link, symn sym.SymKind, syms []loader // Non-ELF binary formats are not always flexible enough to // give us a place to put the Go build ID. On those systems, we put it // at the very beginning of the text segment. -// This ``header'' is read by cmd/go. +// This “header” is read by cmd/go. func (ctxt *Link) textbuildid() { if ctxt.IsELF || ctxt.BuildMode == BuildModePlugin || *flagBuildid == "" { return diff --git a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go index 75cff72b0390e..8eba1fd0cf76b 100644 --- a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go +++ b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go @@ -10,7 +10,7 @@ import ( ) // GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils. -// This general form is often called ``AT&T syntax'' as a reference to AT&T System V Unix. +// This general form is often called “AT&T syntax” as a reference to AT&T System V Unix. func GNUSyntax(inst Inst, pc uint64, symname SymLookup) string { // Rewrite instruction to mimic GNU peculiarities. // Note that inst has been passed by value and contains diff --git a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go index 4632b5064f9e7..e98f1a8418c58 100644 --- a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go +++ b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go @@ -144,7 +144,7 @@ type Arg interface { // the interface value instead of requiring an allocation. // A Reg is a single register. -// The zero Reg value has no name but indicates ``no register.'' +// The zero Reg value has no name but indicates “no register.” type Reg uint8 const ( diff --git a/src/compress/bzip2/bzip2.go b/src/compress/bzip2/bzip2.go index 0d8c286c1670c..51054ccabc9df 100644 --- a/src/compress/bzip2/bzip2.go +++ b/src/compress/bzip2/bzip2.go @@ -447,11 +447,11 @@ func (bz2 *reader) readBlock() (err error) { // inverseBWT implements the inverse Burrows-Wheeler transform as described in // http://www.hpl.hp.com/techreports/Compaq-DEC/SRC-RR-124.pdf, section 4.2. -// In that document, origPtr is called ``I'' and c is the ``C'' array after the +// In that document, origPtr is called “I” and c is the “C” array after the // first pass over the data. It's an argument here because we merge the first // pass with the Huffman decoding. // -// This also implements the ``single array'' method from the bzip2 source code +// This also implements the “single array” method from the bzip2 source code // which leaves the output, still shuffled, in the bottom 8 bits of tt with the // index of the next byte in the top 24-bits. The index of the first byte is // returned. diff --git a/src/compress/lzw/reader.go b/src/compress/lzw/reader.go index 952870a56a2c9..18df97029f2ea 100644 --- a/src/compress/lzw/reader.go +++ b/src/compress/lzw/reader.go @@ -3,8 +3,8 @@ // license that can be found in the LICENSE file. // Package lzw implements the Lempel-Ziv-Welch compressed data format, -// described in T. A. Welch, ``A Technique for High-Performance Data -// Compression'', Computer, 17(6) (June 1984), pp 8-19. +// described in T. A. Welch, “A Technique for High-Performance Data +// Compression”, Computer, 17(6) (June 1984), pp 8-19. // // In particular, it implements LZW as used by the GIF and PDF file // formats, which means variable-width codes up to 12 bits and the first diff --git a/src/crypto/rsa/pkcs1v15.go b/src/crypto/rsa/pkcs1v15.go index 0cbd6d004561c..601b323ff495e 100644 --- a/src/crypto/rsa/pkcs1v15.go +++ b/src/crypto/rsa/pkcs1v15.go @@ -96,8 +96,8 @@ func DecryptPKCS1v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) ([]byt // session key beforehand and continue the protocol with the resulting value. // This will remove any possibility that an attacker can learn any information // about the plaintext. -// See ``Chosen Ciphertext Attacks Against Protocols Based on the RSA -// Encryption Standard PKCS #1'', Daniel Bleichenbacher, Advances in Cryptology +// See “Chosen Ciphertext Attacks Against Protocols Based on the RSA +// Encryption Standard PKCS #1”, Daniel Bleichenbacher, Advances in Cryptology // (Crypto '98). // // Note that if the session key is too small then it may be possible for an diff --git a/src/debug/dwarf/entry.go b/src/debug/dwarf/entry.go index 9f3f4971e18b5..b1bb5910219a7 100644 --- a/src/debug/dwarf/entry.go +++ b/src/debug/dwarf/entry.go @@ -789,7 +789,7 @@ func (b *buf) entry(cu *Entry, atab abbrevTable, ubase Offset, vers int) *Entry return e } -// A Reader allows reading Entry structures from a DWARF ``info'' section. +// A Reader allows reading Entry structures from a DWARF “info” section. // The Entry structures are arranged in a tree. The Reader's Next function // return successive entries from a pre-order traversal of the tree. // If an entry has children, its Children field will be true, and the children @@ -806,7 +806,7 @@ type Reader struct { } // Reader returns a new Reader for Data. -// The reader is positioned at byte offset 0 in the DWARF ``info'' section. +// The reader is positioned at byte offset 0 in the DWARF “info” section. func (d *Data) Reader() *Reader { r := &Reader{d: d} r.Seek(0) diff --git a/src/debug/dwarf/type.go b/src/debug/dwarf/type.go index 9c15cfb920612..2049f46d708dd 100644 --- a/src/debug/dwarf/type.go +++ b/src/debug/dwarf/type.go @@ -372,7 +372,7 @@ type typeReader interface { AddressSize() int } -// Type reads the type at off in the DWARF ``info'' section. +// Type reads the type at off in the DWARF “info” section. func (d *Data) Type(off Offset) (Type, error) { return d.readType("info", d.Reader(), off, d.typeCache, nil) } diff --git a/src/encoding/asn1/asn1.go b/src/encoding/asn1/asn1.go index cad1d7b08f8e9..c90bba47dcf8e 100644 --- a/src/encoding/asn1/asn1.go +++ b/src/encoding/asn1/asn1.go @@ -5,7 +5,7 @@ // Package asn1 implements parsing of DER-encoded ASN.1 data structures, // as defined in ITU-T Rec X.690. // -// See also ``A Layman's Guide to a Subset of ASN.1, BER, and DER,'' +// See also “A Layman's Guide to a Subset of ASN.1, BER, and DER,” // http://luca.ntop.org/Teaching/Appunti/asn1.html. package asn1 diff --git a/src/encoding/base32/base32.go b/src/encoding/base32/base32.go index 3feea9ba473f7..5f3af4c8bbe28 100644 --- a/src/encoding/base32/base32.go +++ b/src/encoding/base32/base32.go @@ -56,7 +56,7 @@ func NewEncoding(encoder string) *Encoding { // RFC 4648. var StdEncoding = NewEncoding(encodeStd) -// HexEncoding is the ``Extended Hex Alphabet'' defined in RFC 4648. +// HexEncoding is the “Extended Hex Alphabet” defined in RFC 4648. // It is typically used in DNS. var HexEncoding = NewEncoding(encodeHex) diff --git a/src/encoding/json/decode.go b/src/encoding/json/decode.go index ce9675a62fc15..4319918d1e542 100644 --- a/src/encoding/json/decode.go +++ b/src/encoding/json/decode.go @@ -85,7 +85,7 @@ import ( // // The JSON null value unmarshals into an interface, map, pointer, or slice // by setting that Go value to nil. Because null is often used in JSON to mean -// ``not present,'' unmarshaling a JSON null into any other Go type has no effect +// “not present,” unmarshaling a JSON null into any other Go type has no effect // on the value and produces no error. // // When unmarshaling quoted strings, invalid UTF-8 or diff --git a/src/fmt/print.go b/src/fmt/print.go index 1c37c3cb7b389..33f5541629097 100644 --- a/src/fmt/print.go +++ b/src/fmt/print.go @@ -55,7 +55,7 @@ type Formatter interface { } // Stringer is implemented by any value that has a String method, -// which defines the ``native'' format for that value. +// which defines the “native” format for that value. // The String method is used to print values passed as an operand // to any format that accepts a string or to an unformatted printer // such as Print. diff --git a/src/go/doc/testdata/testing.go b/src/go/doc/testdata/testing.go index 80238df283a7e..6365ffceedc6d 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/internal/testenv/testenv.go b/src/internal/testenv/testenv.go index 6ef889b02a9db..1feb630cf5fa3 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/math/big/floatconv.go b/src/math/big/floatconv.go index 93f7195219f79..30b6dc4332e92 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 @@ -243,8 +243,8 @@ func (z *Float) pow5(n uint64) *Float { // 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 700d00d031ad9..a111451eaf6c2 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). @@ -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 diff --git a/src/math/big/intconv.go b/src/math/big/intconv.go index 2fe10ff0a24ca..a3a4023caa697 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/natconv.go b/src/math/big/natconv.go index 99488ac833a79..8fdf4b6f9e343 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 @@ -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. // diff --git a/src/math/big/ratconv.go b/src/math/big/ratconv.go index dadd4d7b8ee9e..794a51d007a70 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/bits.go b/src/math/bits.go index 77bcdbe1ce649..c5cb93b15945d 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/cmplx/isnan.go b/src/math/cmplx/isnan.go index d3382c05eefd8..fed442cb48367 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/net/http/server.go b/src/net/http/server.go index b91069f9a16bf..bd4ef1baccd00 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -2101,7 +2101,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 @@ -2394,7 +2394,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/url/url.go b/src/net/url/url.go index ecfd1d9e94b84..bff6513b85fc0 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} } @@ -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 { diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go index 887e1c889232b..93eb233e004a7 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 666143b0de35c..c30a6890de0f3 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 75b4707eaf7ff..ab5d6a493de90 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" diff --git a/src/path/filepath/path.go b/src/path/filepath/path.go index 0554deb2ffe72..668b87bb24b34 100644 --- a/src/path/filepath/path.go +++ b/src/path/filepath/path.go @@ -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/path.go b/src/path/path.go index f1f3499f63e00..5c5bc445ac080 100644 --- a/src/path/path.go +++ b/src/path/path.go @@ -64,8 +64,8 @@ func (b *lazybuf) string() string { // 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/reflect/deepequal.go b/src/reflect/deepequal.go index eaab101221537..50b436e5f6ec4 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/regexp/exec_test.go b/src/regexp/exec_test.go index a6e833050bb46..1694230345177 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/syntax/prog.go b/src/regexp/syntax/prog.go index 8583f55e5421d..ee71decb35f82 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/runtime/float.go b/src/runtime/float.go index 459e58dd7ef0a..7aef78a2ecc73 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 diff --git a/src/time/tick.go b/src/time/tick.go index babf865aeb086..dcfeca8783cb2 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. From 9839668b5619f45e293dd40339bf0ac614ea6bee Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 30 Jan 2022 20:13:43 -0500 Subject: [PATCH 013/137] all: separate doc comment from //go: directives A future change to gofmt will rewrite // Doc comment. //go:foo to // Doc comment. // //go:foo Apply that change preemptively to all comments (not necessarily just doc comments). For #51082. Change-Id: Iffe0285418d1e79d34526af3520b415a12203ca9 Reviewed-on: https://go-review.googlesource.com/c/go/+/384260 Trust: Russ Cox Run-TryBot: Russ Cox Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/cmd/compile/internal/ssa/value.go | 3 ++ src/cmd/compile/internal/test/float_test.go | 1 + .../internal/test/testdata/addressed_test.go | 4 +++ .../internal/test/testdata/arith_test.go | 1 + .../internal/test/testdata/ctl_test.go | 1 + .../compile/internal/test/testdata/fp_test.go | 2 ++ .../internal/test/testdata/loadstore_test.go | 1 + src/cmd/compile/internal/typecheck/builtin.go | 1 + .../compile/internal/typecheck/mkbuiltin.go | 1 + .../go/internal/lockedfile/lockedfile_test.go | 1 + .../go/internal/lockedfile/transform_test.go | 1 + src/cmd/internal/objabi/symkind.go | 1 + src/cmd/link/internal/ld/outbuf_darwin.go | 1 + src/cmd/link/internal/sym/symkind.go | 1 + src/crypto/aes/cbc_s390x.go | 1 + src/crypto/aes/cipher_s390x.go | 1 + src/crypto/aes/ctr_s390x.go | 1 + src/crypto/aes/gcm_s390x.go | 3 ++ src/crypto/ecdsa/ecdsa_s390x.go | 1 + .../internal/edwards25519/field/fe_amd64.go | 2 ++ src/crypto/elliptic/p256_asm.go | 12 ++++++++ .../x509/internal/macos/corefoundation.go | 1 + src/crypto/x509/internal/macos/security.go | 1 + src/crypto/x509/root.go | 1 + src/encoding/gob/debug.go | 1 + src/hash/crc32/crc32_amd64.go | 3 ++ src/hash/crc32/crc32_ppc64le.go | 1 + src/hash/crc32/crc32_s390x.go | 2 ++ src/internal/goarch/goarch.go | 1 + src/internal/goos/goos.go | 1 + src/internal/intern/intern.go | 1 + src/internal/poll/fcntl_libc.go | 1 + src/internal/poll/fd_opendir_darwin.go | 1 + src/internal/poll/fd_poll_runtime.go | 1 + src/internal/poll/fd_writev_darwin.go | 1 + src/internal/poll/sendfile_solaris.go | 1 + src/internal/reflectlite/value.go | 1 + src/internal/syscall/unix/nonblocking_libc.go | 1 + src/net/fcntl_libc_test.go | 1 + src/os/pipe_test.go | 1 + src/os/rawconn_test.go | 1 + src/plugin/plugin_dlopen.go | 1 + src/reflect/abi_test.go | 1 + src/reflect/makefunc.go | 1 + src/reflect/value.go | 8 +++++ src/runtime/asan.go | 1 + src/runtime/cgo/callbacks.go | 1 + src/runtime/cgo/callbacks_aix.go | 1 + src/runtime/cgo/openbsd.go | 1 + src/runtime/cgo_mmap.go | 3 ++ src/runtime/cgo_ppc64x.go | 1 + src/runtime/cgo_sigaction.go | 2 ++ src/runtime/cgocall.go | 4 +++ src/runtime/cgocheck.go | 5 ++++ src/runtime/chan.go | 2 ++ src/runtime/cpuprof.go | 2 ++ src/runtime/env_plan9.go | 4 +++ src/runtime/env_posix.go | 2 ++ src/runtime/export_test.go | 1 + src/runtime/histogram.go | 1 + src/runtime/internal/atomic/atomic_386.go | 1 + src/runtime/internal/atomic/atomic_amd64.go | 1 + src/runtime/internal/atomic/atomic_arm.go | 2 ++ src/runtime/internal/atomic/atomic_mipsx.go | 1 + src/runtime/internal/atomic/atomic_s390x.go | 1 + src/runtime/internal/atomic/atomic_wasm.go | 1 + src/runtime/lock_futex.go | 1 + src/runtime/lockrank_off.go | 2 ++ src/runtime/lockrank_on.go | 11 +++++++ src/runtime/malloc.go | 2 ++ src/runtime/mbarrier.go | 1 + src/runtime/mbitmap.go | 14 +++++++++ src/runtime/mem_aix.go | 2 ++ src/runtime/mem_bsd.go | 2 ++ src/runtime/mem_darwin.go | 2 ++ src/runtime/mem_js.go | 2 ++ src/runtime/mem_linux.go | 2 ++ src/runtime/mem_windows.go | 2 ++ src/runtime/mfinal.go | 1 + src/runtime/mgcmark.go | 3 ++ src/runtime/mgcstack.go | 1 + src/runtime/mgcsweep.go | 1 + src/runtime/mgcwork.go | 10 +++++++ src/runtime/mheap.go | 1 + src/runtime/msan.go | 1 + src/runtime/mstats.go | 3 ++ src/runtime/netpoll.go | 3 ++ src/runtime/netpoll_aix.go | 1 + src/runtime/norace_linux_test.go | 1 + src/runtime/norace_test.go | 1 + src/runtime/os2_aix.go | 1 + src/runtime/os3_solaris.go | 2 ++ src/runtime/os_aix.go | 2 ++ src/runtime/os_darwin.go | 4 +++ src/runtime/os_dragonfly.go | 3 ++ src/runtime/os_freebsd.go | 7 +++++ src/runtime/os_js.go | 1 + src/runtime/os_linux.go | 9 ++++++ src/runtime/os_netbsd.go | 4 +++ src/runtime/os_openbsd.go | 2 ++ src/runtime/os_openbsd_libc.go | 1 + src/runtime/os_openbsd_syscall.go | 1 + src/runtime/os_openbsd_syscall2.go | 1 + src/runtime/os_plan9.go | 2 ++ src/runtime/os_windows.go | 5 ++++ src/runtime/panic.go | 2 ++ src/runtime/pprof/pprof_test.go | 1 + src/runtime/proc.go | 30 +++++++++++++++++++ src/runtime/proc_test.go | 1 + src/runtime/race.go | 4 +++ src/runtime/runtime1.go | 8 +++++ src/runtime/runtime2.go | 2 ++ src/runtime/runtime_test.go | 1 + src/runtime/sema.go | 4 +++ src/runtime/signal_unix.go | 16 ++++++++++ src/runtime/sigqueue.go | 7 +++++ src/runtime/sigqueue_plan9.go | 5 ++++ src/runtime/string.go | 1 + src/runtime/stubs.go | 5 ++++ src/runtime/stubs2.go | 1 + src/runtime/stubs_linux.go | 1 + src/runtime/stubs_ppc64.go | 1 + src/runtime/symtab.go | 3 ++ src/runtime/symtab_test.go | 1 + src/runtime/sys_darwin.go | 2 ++ src/runtime/sys_libc.go | 1 + src/runtime/sys_openbsd2.go | 2 ++ src/runtime/syscall_aix.go | 2 ++ src/runtime/syscall_solaris.go | 2 ++ src/runtime/syscall_windows.go | 1 + src/runtime/syscall_windows_test.go | 1 + .../testdata/testprogcgo/dropm_stub.go | 1 + src/runtime/testdata/testprogcgo/eintr.go | 1 + src/runtime/time.go | 8 +++++ src/runtime/time_fake.go | 1 + src/runtime/vdso_linux.go | 1 + src/strings/builder.go | 1 + src/sync/pool_test.go | 1 + src/syscall/dir_plan9.go | 1 + src/syscall/exec_bsd.go | 1 + src/syscall/exec_freebsd.go | 1 + src/syscall/exec_libc.go | 1 + src/syscall/exec_libc2.go | 1 + src/syscall/exec_linux.go | 1 + src/syscall/exec_plan9.go | 4 +++ src/syscall/syscall_linux.go | 4 +++ src/time/time.go | 1 + src/time/tzdata/tzdata.go | 1 + 148 files changed, 363 insertions(+) diff --git a/src/cmd/compile/internal/ssa/value.go b/src/cmd/compile/internal/ssa/value.go index 7b411a4612413..8f125cef99357 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/test/float_test.go b/src/cmd/compile/internal/test/float_test.go index 884a983bdd7a5..c736f970f9941 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/testdata/addressed_test.go b/src/cmd/compile/internal/test/testdata/addressed_test.go index cdabf978f086b..4cc9ac4d5b27c 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 7d54a9181d1ef..253142a0fbc51 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 16d571ce2cbf4..ff3a1609c5a99 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 7d61a8063ee9a..b96ce84a6ca46 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 57571f5d170ec..052172819a7a6 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 67597cebb4630..581928c00515e 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/mkbuiltin.go b/src/cmd/compile/internal/typecheck/mkbuiltin.go index 6dbd1869b3e11..9b27557956daf 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/go/internal/lockedfile/lockedfile_test.go b/src/cmd/go/internal/lockedfile/lockedfile_test.go index c9907db46cecb..79352bc8c7360 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 3c1caa334eb4d..833cbf787958c 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/internal/objabi/symkind.go b/src/cmd/internal/objabi/symkind.go index 28f430fc547cb..dba23a54bd022 100644 --- a/src/cmd/internal/objabi/symkind.go +++ b/src/cmd/internal/objabi/symkind.go @@ -37,6 +37,7 @@ type SymKind uint8 // These are used to index into cmd/link/internal/sym/AbiSymKindToSymKind // // TODO(rsc): Give idiomatic Go names. +// //go:generate stringer -type=SymKind const ( // An otherwise invalid zero value for the type diff --git a/src/cmd/link/internal/ld/outbuf_darwin.go b/src/cmd/link/internal/ld/outbuf_darwin.go index b1ee3c56282d3..e372b3724aa95 100644 --- a/src/cmd/link/internal/ld/outbuf_darwin.go +++ b/src/cmd/link/internal/ld/outbuf_darwin.go @@ -13,6 +13,7 @@ import ( ) // Implemented in the syscall package. +// //go:linkname fcntl syscall.fcntl func fcntl(fd int, cmd int, arg int) (int, error) diff --git a/src/cmd/link/internal/sym/symkind.go b/src/cmd/link/internal/sym/symkind.go index 0a0741f84bf40..3ed04c49af995 100644 --- a/src/cmd/link/internal/sym/symkind.go +++ b/src/cmd/link/internal/sym/symkind.go @@ -38,6 +38,7 @@ type SymKind uint8 // Defined SymKind values. // // TODO(rsc): Give idiomatic Go names. +// //go:generate stringer -type=SymKind const ( Sxxx SymKind = iota diff --git a/src/crypto/aes/cbc_s390x.go b/src/crypto/aes/cbc_s390x.go index 28a6b1d546157..766247abffb8d 100644 --- a/src/crypto/aes/cbc_s390x.go +++ b/src/crypto/aes/cbc_s390x.go @@ -39,6 +39,7 @@ func (x *cbc) BlockSize() int { return BlockSize } // cryptBlocksChain invokes the cipher message with chaining (KMC) instruction // with the given function code. The length must be a multiple of BlockSize (16). +// //go:noescape func cryptBlocksChain(c code, iv, key, dst, src *byte, length int) diff --git a/src/crypto/aes/cipher_s390x.go b/src/crypto/aes/cipher_s390x.go index 65b6b2fc1b593..e357851143130 100644 --- a/src/crypto/aes/cipher_s390x.go +++ b/src/crypto/aes/cipher_s390x.go @@ -28,6 +28,7 @@ type aesCipherAsm struct { // cryptBlocks invokes the cipher message (KM) instruction with // the given function code. This is equivalent to AES in ECB // mode. The length must be a multiple of BlockSize (16). +// //go:noescape func cryptBlocks(c code, key, dst, src *byte, length int) diff --git a/src/crypto/aes/ctr_s390x.go b/src/crypto/aes/ctr_s390x.go index bfa8cbba7f3c2..f5c33d529928b 100644 --- a/src/crypto/aes/ctr_s390x.go +++ b/src/crypto/aes/ctr_s390x.go @@ -17,6 +17,7 @@ var _ ctrAble = (*aesCipherAsm)(nil) // dst. If a and b are not the same length then the number of bytes processed // will be equal to the length of shorter of the two. Returns the number // of bytes processed. +// //go:noescape func xorBytes(dst, a, b []byte) int diff --git a/src/crypto/aes/gcm_s390x.go b/src/crypto/aes/gcm_s390x.go index c58aa2cda8fd1..98d530aeda1af 100644 --- a/src/crypto/aes/gcm_s390x.go +++ b/src/crypto/aes/gcm_s390x.go @@ -100,6 +100,7 @@ func sliceForAppend(in []byte, n int) (head, tail []byte) { // ghash uses the GHASH algorithm to hash data with the given key. The initial // hash value is given by hash which will be updated with the new hash value. // The length of data must be a multiple of 16-bytes. +// //go:noescape func ghash(key *gcmHashKey, hash *[16]byte, data []byte) @@ -127,6 +128,7 @@ func (g *gcmAsm) paddedGHASH(hash *[16]byte, data []byte) { // The lengths of both dst and buf must be greater than or equal to the length // of src. buf may be partially or completely overwritten during the execution // of the function. +// //go:noescape func cryptBlocksGCM(fn code, key, dst, src, buf []byte, cnt *gcmCount) @@ -295,6 +297,7 @@ const ( // will be calculated and written to tag. cnt should contain the current // counter state and will be overwritten with the updated counter state. // TODO(mundaym): could pass in hash subkey +// //go:noescape func kmaGCM(fn code, key, dst, src, aad []byte, tag *[16]byte, cnt *gcmCount) diff --git a/src/crypto/ecdsa/ecdsa_s390x.go b/src/crypto/ecdsa/ecdsa_s390x.go index 1480d1bf6f629..bd9257977c777 100644 --- a/src/crypto/ecdsa/ecdsa_s390x.go +++ b/src/crypto/ecdsa/ecdsa_s390x.go @@ -18,6 +18,7 @@ import ( // The return value corresponds to the condition code set by the // instruction. Interrupted invocations are handled by the // function. +// //go:noescape func kdsa(fc uint64, params *[4096]byte) (errn uint64) diff --git a/src/crypto/ed25519/internal/edwards25519/field/fe_amd64.go b/src/crypto/ed25519/internal/edwards25519/field/fe_amd64.go index 363020bd6b8fc..70c541692c3a3 100644 --- a/src/crypto/ed25519/internal/edwards25519/field/fe_amd64.go +++ b/src/crypto/ed25519/internal/edwards25519/field/fe_amd64.go @@ -5,9 +5,11 @@ package field // feMul sets out = a * b. It works like feMulGeneric. +// //go:noescape func feMul(out *Element, a *Element, b *Element) // feSquare sets out = a * a. It works like feSquareGeneric. +// //go:noescape func feSquare(out *Element, a *Element) diff --git a/src/crypto/elliptic/p256_asm.go b/src/crypto/elliptic/p256_asm.go index 8624e031a385e..93adaf90565ae 100644 --- a/src/crypto/elliptic/p256_asm.go +++ b/src/crypto/elliptic/p256_asm.go @@ -53,26 +53,32 @@ func (curve p256Curve) Params() *CurveParams { // Functions implemented in p256_asm_*64.s // Montgomery multiplication modulo P256 +// //go:noescape func p256Mul(res, in1, in2 []uint64) // Montgomery square modulo P256, repeated n times (n >= 1) +// //go:noescape func p256Sqr(res, in []uint64, n int) // Montgomery multiplication by 1 +// //go:noescape func p256FromMont(res, in []uint64) // iff cond == 1 val <- -val +// //go:noescape func p256NegCond(val []uint64, cond int) // if cond == 0 res <- b; else res <- a +// //go:noescape func p256MovCond(res, a, b []uint64, cond int) // Endianness swap +// //go:noescape func p256BigToLittle(res []uint64, in []byte) @@ -80,6 +86,7 @@ func p256BigToLittle(res []uint64, in []byte) func p256LittleToBig(res []byte, in []uint64) // Constant time table access +// //go:noescape func p256Select(point, table []uint64, idx int) @@ -87,10 +94,12 @@ func p256Select(point, table []uint64, idx int) func p256SelectBase(point *[12]uint64, table string, idx int) // Montgomery multiplication modulo Ord(G) +// //go:noescape func p256OrdMul(res, in1, in2 []uint64) // Montgomery square modulo Ord(G), repeated n times +// //go:noescape func p256OrdSqr(res, in []uint64, n int) @@ -98,16 +107,19 @@ func p256OrdSqr(res, in []uint64, n int) // If sign == 1 -> in2 = -in2 // If sel == 0 -> res = in1 // if zero == 0 -> res = in2 +// //go:noescape func p256PointAddAffineAsm(res, in1, in2 []uint64, sign, sel, zero int) // Point add. Returns one if the two input points were equal and zero // otherwise. (Note that, due to the way that the equations work out, some // representations of ∞ are considered equal to everything by this function.) +// //go:noescape func p256PointAddAsm(res, in1, in2 []uint64) int // Point double +// //go:noescape func p256PointDoubleAsm(res, in []uint64) diff --git a/src/crypto/x509/internal/macos/corefoundation.go b/src/crypto/x509/internal/macos/corefoundation.go index eb91a5db6e0ae..2677ff706ae33 100644 --- a/src/crypto/x509/internal/macos/corefoundation.go +++ b/src/crypto/x509/internal/macos/corefoundation.go @@ -19,6 +19,7 @@ import ( ) // Core Foundation linker flags for the external linker. See Issue 42459. +// //go:cgo_ldflag "-framework" //go:cgo_ldflag "CoreFoundation" diff --git a/src/crypto/x509/internal/macos/security.go b/src/crypto/x509/internal/macos/security.go index 381d918a94f6a..d8147ba8ba26b 100644 --- a/src/crypto/x509/internal/macos/security.go +++ b/src/crypto/x509/internal/macos/security.go @@ -15,6 +15,7 @@ import ( ) // Security.framework linker flags for the external linker. See Issue 42459. +// //go:cgo_ldflag "-framework" //go:cgo_ldflag "Security" diff --git a/src/crypto/x509/root.go b/src/crypto/x509/root.go index eef9c047b2e31..91f4d29a1f9f6 100644 --- a/src/crypto/x509/root.go +++ b/src/crypto/x509/root.go @@ -8,6 +8,7 @@ package x509 // argument to the latest security_certificates version from // https://opensource.apple.com/source/security_certificates/ // and run "go generate". See https://golang.org/issue/38843. +// //go:generate go run root_ios_gen.go -version 55188.120.1.0.1 import "sync" diff --git a/src/encoding/gob/debug.go b/src/encoding/gob/debug.go index b6d5a3e95c452..c989ab1ad6b8a 100644 --- a/src/encoding/gob/debug.go +++ b/src/encoding/gob/debug.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // Delete the next line to include in the gob package. +// //go:build ignore package gob diff --git a/src/hash/crc32/crc32_amd64.go b/src/hash/crc32/crc32_amd64.go index 7017a89304fac..6be129f5ddd4c 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 686722761d53c..dcd32351a5b69 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 3a98bd8799ed8..4e50b56c6f959 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/internal/goarch/goarch.go b/src/internal/goarch/goarch.go index 921f5a208fc63..e8de67b01b443 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 ebb521fec6876..02dc9688cb210 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 75641106abdf9..c7639b4668736 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/poll/fcntl_libc.go b/src/internal/poll/fcntl_libc.go index f503d7a336ffa..13614dc3e8d5f 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 8eb770c35818a..3ae2dc8448215 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 2e9cd5c9d7ee4..4d3cc78405961 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 8137510c8b447..b5b8998df8cae 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 0a884307bb15b..7ae18f4b1a661 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/reflectlite/value.go b/src/internal/reflectlite/value.go index 966230f581701..b9bca3ab44db6 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 75c6e92a6e23e..84940714c3cc5 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/net/fcntl_libc_test.go b/src/net/fcntl_libc_test.go index 3478ce723188b..78892e3a9fac1 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/os/pipe_test.go b/src/os/pipe_test.go index 20716bce1e166..26565853e1ff9 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 fd2038a2332a1..62b99f8784302 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/plugin/plugin_dlopen.go b/src/plugin/plugin_dlopen.go index c59f11ef719d6..6ba0f780657a4 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 c9a4cd1c8e364..9d93472779012 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/makefunc.go b/src/reflect/makefunc.go index 3d9279ceaa64e..0a680765cda77 100644 --- a/src/reflect/makefunc.go +++ b/src/reflect/makefunc.go @@ -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/value.go b/src/reflect/value.go index f1454b8ae2251..c5c212ea364d4 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -2757,6 +2757,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 +3494,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 +3502,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/runtime/asan.go b/src/runtime/asan.go index 5f1e6370d2f29..8c41e418f7ce8 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/cgo/callbacks.go b/src/runtime/cgo/callbacks.go index cd8b79538745d..e7c8ef3e07c2b 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 f4b6fe25fa268..8f756fbdd9f43 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 872d02e3345bf..26b62fbdaf759 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 0cb25bdcdab32..4cb3e65f14ddd 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 97b962e40f69f..c723213809e6d 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 a2e12f0f0e272..9500c52205958 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 a0c9560fd0f65..977d049378e13 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 3acbadf803d04..74a2ec09bcd68 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 a16782ae94929..308667d7bc986 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) diff --git a/src/runtime/cpuprof.go b/src/runtime/cpuprof.go index 48cef46fe9c93..07673c9bd0b86 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/env_plan9.go b/src/runtime/env_plan9.go index f1ac4760a750d..65480c8217707 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 7d01ab4dd7dcf..94a19d80d8e86 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/export_test.go b/src/runtime/export_test.go index 01569815249c5..af27050bfd0a6 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/histogram.go b/src/runtime/histogram.go index cd7e29a8c8be8..eddfbab3bc564 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 27a77ec37a90e..bf2f4b922910e 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 e36eb83a113da..52a83620c8f69 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 e2539b6c7ec5e..bdb184727909c 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 e552e57495281..5dd15a0b02219 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 a058d601029c9..9855bf0780fbe 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 3f77f16b4efe2..835fc43ccf9f6 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 575df7a1d5708..1578984ce238a 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/lockrank_off.go b/src/runtime/lockrank_off.go index daa45b542dd33..bf046a104124e 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 3c8c367c19fbb..a170569d6e2a9 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 a00878a11c23c..c182197782159 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/mbarrier.go b/src/runtime/mbarrier.go index a0d145ec7618f..c3b45415a9533 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 937968807b0ea..665a9c6f63ece 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,11 @@ 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:nosplit func subtract1(p *byte) *byte { // Note: wrote out full expression instead of calling subtractb(p, 1) @@ -314,6 +319,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 +387,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 +425,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 +439,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 +486,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 +527,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 +545,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 +645,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 +1964,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 d6a181ad4dcab..21726b56aec91 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 e83145e86b92c..782465ae26284 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 d63b5559aac3c..25862cf16176e 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 c66b91eedd27e..e87c5f26ae23a 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 980f7bb53ddba..1630664cff8a9 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 c8f039f50beca..b1292fc72526c 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/mfinal.go b/src/runtime/mfinal.go index 10623e4d67062..979e0b4a2c7bc 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. diff --git a/src/runtime/mgcmark.go b/src/runtime/mgcmark.go index 3e1a0b560ac8b..cd0ec007f3bde 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 49dc54e16524c..472c61a491a40 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 a46f4ec2c6cf8..c863ea9cd8eaf 100644 --- a/src/runtime/mgcsweep.go +++ b/src/runtime/mgcsweep.go @@ -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 56d0b1cd6214f..5c47006cc286f 100644 --- a/src/runtime/mgcwork.go +++ b/src/runtime/mgcwork.go @@ -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 d2a63d093802e..a8a1e61ef226a 100644 --- a/src/runtime/mheap.go +++ b/src/runtime/mheap.go @@ -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/msan.go b/src/runtime/msan.go index 902a1e9e744b3..c4852165830a3 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 e5c3471ca3971..e8b42fbbbefc4 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/netpoll.go b/src/runtime/netpoll.go index 864148b71503c..6dcc60953f3a6 100644 --- a/src/runtime/netpoll.go +++ b/src/runtime/netpoll.go @@ -271,6 +271,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 +290,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 +440,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 90950af4447d9..22cc513881561 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 b188a2e88b7c8..3521b24655bc9 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 d49f2ec0dfd91..3b5eca5341ec5 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 4d77f0de6dd2f..9ad1caa816187 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 f465a3aa3f51d..8c85b71532dfd 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 292ff94795677..15e4929779a2c 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 9065b76375db8..8562d7d9069ee 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 a56706b4151ea..83478143b933b 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 e4d15474d82b2..23efd1a46e834 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 9ed916705bc51..7ec1210b73c68 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 efb54ff20e315..812a0b4ad3040 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -55,6 +55,7 @@ const ( // 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 +74,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 +159,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 +187,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 +369,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 +397,7 @@ func minit() { } // Called from dropm to undo the effect of an minit. +// //go:nosplit func unminit() { unminitSignals() @@ -497,6 +503,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 +514,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 +539,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 88a4a8b90e5da..3cbace38f9970 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 1a00b890db1e9..2383dc84280f0 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 ff21eccb4b575..4ad2a061bd88d 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 8128c20453b82..9d67a7ebbdaec 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 a48f5fa88a6a7..e4c9d2fe89308 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 975d460a7da92..1a0c0e9363e1a 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 c76add78025a2..2f6ec75cf8964 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 6600410cb6c5e..f2137c6853bb9 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -944,6 +944,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 +957,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_test.go b/src/runtime/pprof/pprof_test.go index ff4ecb4c68499..1742dc0cdcbe1 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/proc.go b/src/runtime/proc.go index f9f82f386754f..ae4440786e4fc 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() @@ -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) @@ -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 719d0d1aee5a4..c49d6ae8a8fc8 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 e019923bb5c67..4694288082bc9 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 65e1e0eebca1c..5429aa2e5b6e3 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 dc18bf927e432..b903cc8011102 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) { diff --git a/src/runtime/runtime_test.go b/src/runtime/runtime_test.go index 12f261bdd2c48..1dc04ac55da28 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 f94c1aa89106d..e83deee083f51 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 0e11c57683988..8bde739c64978 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 { @@ -1264,6 +1277,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 +1297,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 +1309,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 fdf99d94a2398..49502cbed3604 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 d5fe8f8b35df8..9ed6fb5886c05 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/string.go b/src/runtime/string.go index eec29075b9a4c..bef097c87ed40 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -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 cd7c91029b0f4..8c4ab3ed4e6cb 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 9aa965454d991..94a888dec6289 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 06c14e21601d6..2367dc2bd031f 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 07127629d19f6..6919b748f0c5f 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 ee4db47314beb..ad34b68c7d5b6 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 a83afc3385da1..79a114b02b96e 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) diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go index 58b3a9171c797..ea81fd4f46d67 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 { @@ -236,6 +237,7 @@ func close_trampoline() //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 7012b4167e969..0c6f13ca9f6fc 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 d174d87a49cfd..f936e0cfc3bb7 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 79b51240e906c..f294922e7dd29 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 79775711aeaea..e7bab3b23fb54 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 9c38facf081f7..a841a31a27e0f 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 034a1d84db39a..37f8f40cfba73 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/testprogcgo/dropm_stub.go b/src/runtime/testdata/testprogcgo/dropm_stub.go index f7f142c1fdd5e..6997cfd3fa8c5 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 b35b280a76ed0..6e9677f988bb5 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 a9ad62077644e..3ff3b668c00ff 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,12 +217,14 @@ 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. func resetTimer(t *timer, when int64) bool { @@ -231,6 +235,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 +742,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 +759,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 +826,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 b5e04635883d9..9e24f7093105b 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/vdso_linux.go b/src/runtime/vdso_linux.go index cff2000767f59..2ebdd44e94d48 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/strings/builder.go b/src/strings/builder.go index ba4df618bfaa0..3caddabd4ec95 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/sync/pool_test.go b/src/sync/pool_test.go index bb20043a54391..5e385974414cb 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 4ed052de7619e..1667cbc02f49e 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 530b48cb707df..4762ae751a343 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 90793fe83fa76..851b8fbd06a91 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 c8549c496444e..aee1b8c98ab35 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 91a39ba1b8f61..9eb61a5d35168 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 0f0dee8ea5a90..6d4b6939ada4b 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 c469fe1812632..6680e6f2ef91c 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/syscall_linux.go b/src/syscall/syscall_linux.go index f74a79c285191..a00d8c94a26b6 100644 --- a/src/syscall/syscall_linux.go +++ b/src/syscall/syscall_linux.go @@ -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/time/time.go b/src/time/time.go index 88301ec16bbf0..7dc1e49bc17e1 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -1068,6 +1068,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 diff --git a/src/time/tzdata/tzdata.go b/src/time/tzdata/tzdata.go index 25725bd84d2c6..324de5cd85c55 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)) From 9e16cc1541d42cb081d359339e3f45b4b9b2a372 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 14 Mar 2022 12:31:33 -0400 Subject: [PATCH 014/137] hash/maphash: add Bytes and String For very small inputs, h.Reset+h.Write+h.Sum64 is fundamentally slower than a single operation, by about a factor of two, because Write must copy the data into h's buffer, just in case there is another Write before the Sum64. A single function doing the whole sequence knows there is no extra write that will happen, so it doesn't need the buffer, so it avoids the copy. Fixes #42710. Change-Id: Icc79c68ccb10827f6640071d026df86b4940fcc1 Reviewed-on: https://go-review.googlesource.com/c/go/+/392494 Reviewed-by: Ian Lance Taylor Trust: Russ Cox Run-TryBot: Russ Cox TryBot-Result: Gopher Robot --- api/next/42710.txt | 2 ++ src/hash/maphash/maphash.go | 48 +++++++++++++++++++++++++ src/hash/maphash/maphash_test.go | 60 +++++++++++++++++++++----------- 3 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 api/next/42710.txt diff --git a/api/next/42710.txt b/api/next/42710.txt new file mode 100644 index 0000000000000..7879758d16c2e --- /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/src/hash/maphash/maphash.go b/src/hash/maphash/maphash.go index d022d746a74ff..973fb6870180b 100644 --- a/src/hash/maphash/maphash.go +++ b/src/hash/maphash/maphash.go @@ -33,6 +33,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.Sum() +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.Sum() +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. diff --git a/src/hash/maphash/maphash_test.go b/src/hash/maphash/maphash_test.go index 78cdfc0e73708..7526989073148 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() - - for i := 0; i < b.N; i++ { - h.Reset() - h.Write(buf) - h.Sum64() - } -} - -func BenchmarkHash8Bytes(b *testing.B) { - benchmarkSize(b, 8) -} + s := string(buf) + + 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() + } + }) -func BenchmarkHash320Bytes(b *testing.B) { - benchmarkSize(b, 320) -} + b.Run("Bytes", func(b *testing.B) { + b.SetBytes(int64(size)) + seed := h.Seed() + for i := 0; i < b.N; i++ { + Bytes(seed, buf) + } + }) -func BenchmarkHash1K(b *testing.B) { - benchmarkSize(b, 1024) + 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 BenchmarkHash8K(b *testing.B) { - benchmarkSize(b, 8192) +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) + }) + } } From 5a6a830c1ceafd551937876f11590fd60aea1799 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Tue, 5 Apr 2022 14:34:47 -0400 Subject: [PATCH 015/137] cmd/vendor: undo stray edits from CL 384262 cmd/internal/moddeps is currently failing on the longtest builders because vendored third-party dependencies were accidentally edited as part of CL 384262 (a global cleanup of the standard library). Updates #51082 Change-Id: I6f79c8f1177420a51128ce42d6c14fa5dcc4bd7b Reviewed-on: https://go-review.googlesource.com/c/go/+/398455 Trust: Bryan Mills Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go | 2 +- src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go index 8eba1fd0cf76b..75cff72b0390e 100644 --- a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go +++ b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/gnu.go @@ -10,7 +10,7 @@ import ( ) // GNUSyntax returns the GNU assembler syntax for the instruction, as defined by GNU binutils. -// This general form is often called “AT&T syntax” as a reference to AT&T System V Unix. +// This general form is often called ``AT&T syntax'' as a reference to AT&T System V Unix. func GNUSyntax(inst Inst, pc uint64, symname SymLookup) string { // Rewrite instruction to mimic GNU peculiarities. // Note that inst has been passed by value and contains diff --git a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go index e98f1a8418c58..4632b5064f9e7 100644 --- a/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go +++ b/src/cmd/vendor/golang.org/x/arch/x86/x86asm/inst.go @@ -144,7 +144,7 @@ type Arg interface { // the interface value instead of requiring an allocation. // A Reg is a single register. -// The zero Reg value has no name but indicates “no register.” +// The zero Reg value has no name but indicates ``no register.'' type Reg uint8 const ( From 69756b38f25bf72f1040dd7fd243febba89017e6 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Tue, 5 Apr 2022 14:25:34 -0400 Subject: [PATCH 016/137] cmd/dist: move more environment logic into cmd/dist from make and run scripts 'go tool dist env' outputs different (and fewer) environment variables than 'go env'. The 'go tool dist env' variables should be authoritative, whereas many printed by 'go env' are merely informational (and not intended to be overridden in the actual environment). Fixes #52009 Change-Id: Ic0590153875183135cebf7ca55ead7c2b4038569 Reviewed-on: https://go-review.googlesource.com/c/go/+/398061 Trust: Bryan Mills Run-TryBot: Bryan Mills Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/cmd/dist/build.go | 59 +++++++++++++++++++++---------------------- src/cmd/dist/test.go | 4 +-- src/make.bash | 8 +----- src/make.bat | 6 ++--- src/make.rc | 6 +---- src/run.bash | 7 ++--- src/run.bat | 11 +++----- src/run.rc | 6 +---- 8 files changed, 41 insertions(+), 66 deletions(-) diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index db2ac1f2a6e50..565efc91c6a43 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"))) } } @@ -1318,7 +1317,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 +1456,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 +1680,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/test.go b/src/cmd/dist/test.go index 9118c133e5e72..ee521f81bad92 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/make.bash b/src/make.bash index e517a1bda91dc..ab2ce19f4eee1 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 c2f87ace7593f..0ba2dd57c5a01 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 273d1511907a4..4597403a04136 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/run.bash b/src/run.bash index 2123c509f8b25..99b09fcbde977 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 1f16c493bbcb4..b4bab85a932b4 100644 --- a/src/run.bat +++ b/src/run.bat @@ -18,18 +18,13 @@ setlocal set GOBUILDFAIL=0 -set GOPATH=c:\nonexist-gopath -:: Issue 14340: ignore GOBIN during all.bat. -set GOBIN= -set GOFLAGS= -set GO111MODULE= - -:: 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 +set GOPATH=c:\nonexist-gopath + if x%1==x--no-rebuild goto norebuild ..\bin\go tool dist test --rebuild if errorlevel 1 goto fail diff --git a/src/run.rc b/src/run.rc index a7b4801207051..2a0bb7f7a1f82 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 $* From dbb52cc9f3e83a3040f46c2ae7650c15ab342179 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 31 Mar 2022 13:21:39 -0700 Subject: [PATCH 017/137] net/url: preserve a trailing slash in JoinPath Fixes #52074 Change-Id: I30897f32e70a6ca0c4e11aaf07088c27336efaba Reviewed-on: https://go-review.googlesource.com/c/go/+/397256 Trust: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Matt Layher Trust: Matt Layher --- src/net/url/url.go | 9 ++++++++- src/net/url/url_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/net/url/url.go b/src/net/url/url.go index bff6513b85fc0..f85bdb1580435 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -1189,11 +1189,18 @@ 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 } diff --git a/src/net/url/url_test.go b/src/net/url/url_test.go index 18aa5f8a1c55a..478cc34872700 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" From 2de2f6df64a8fff36fe9752f893a7cfff4590762 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Tue, 8 Mar 2022 11:18:44 -0800 Subject: [PATCH 018/137] crypto/x509: add new CRL parser, deprecate old one Adds a new, cryptobyte based, CRL parser, which returns a x509.RevocaitonList, rather than a pkix.CertificateList. This allows us to return much more detailed information, as well as leaving open the option of adding further information since RevocationList is not a direct ASN.1 representation like pkix.CertificateList. Additionally a new method is added to RevocationList, CheckSignatureFrom, which is analogous to the method with the same name on Certificate, which properly checks that the signature is from an issuing certiifcate. This change also deprecates a number of older CRL related functions and types, which have been replaced with the new functionality introduced in this change: * crypto/x509.ParseCRL * crypto/x509.ParseDERCRL * crypto/x509.CheckCRLSignature * crypto/x509/pkix.CertificateList * crypto/x509/pkix.TBSCertificateList Fixes #50674 Change-Id: I27dc219e39bef09a396e666b4fccaa32578fd913 Reviewed-on: https://go-review.googlesource.com/c/go/+/390834 Reviewed-by: Damien Neil Trust: Roland Shoemaker Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker TryBot-Result: Gopher Robot --- api/next/50674.txt | 9 ++ src/crypto/x509/parser.go | 219 ++++++++++++++++++++++++++++------- src/crypto/x509/pkix/pkix.go | 4 + src/crypto/x509/x509.go | 41 ++++++- src/crypto/x509/x509_test.go | 148 ++++++++++++++++++++--- 5 files changed, 366 insertions(+), 55 deletions(-) create mode 100644 api/next/50674.txt diff --git a/api/next/50674.txt b/api/next/50674.txt new file mode 100644 index 0000000000000..6b5bca3a9db08 --- /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/src/crypto/x509/parser.go b/src/crypto/x509/parser.go index 333991bf14d97..9dd7c2564e12b 100644 --- a/src/crypto/x509/parser.go +++ b/src/crypto/x509/parser.go @@ -164,53 +164,29 @@ func parseAI(der cryptobyte.String) (pkix.AlgorithmIdentifier, error) { return ai, nil } -func parseValidity(der cryptobyte.String) (time.Time, time.Time, error) { - extract := func() (time.Time, error) { - var t time.Time - switch { - case der.PeekASN1Tag(cryptobyte_asn1.UTCTime): - // TODO(rolandshoemaker): once #45411 is fixed, the following code - // should be replaced with a call to der.ReadASN1UTCTime. - var utc cryptobyte.String - if !der.ReadASN1(&utc, cryptobyte_asn1.UTCTime) { - return t, errors.New("x509: malformed UTCTime") - } - s := string(utc) - - formatStr := "0601021504Z0700" - var err error - t, err = time.Parse(formatStr, s) - if err != nil { - formatStr = "060102150405Z0700" - t, err = time.Parse(formatStr, s) - } - if err != nil { - return t, err - } - - if serialized := t.Format(formatStr); serialized != s { - return t, errors.New("x509: malformed UTCTime") - } - - if t.Year() >= 2050 { - // UTCTime only encodes times prior to 2050. See https://tools.ietf.org/html/rfc5280#section-4.1.2.5.1 - t = t.AddDate(-100, 0, 0) - } - case der.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime): - if !der.ReadASN1GeneralizedTime(&t) { - return t, errors.New("x509: malformed GeneralizedTime") - } - default: - return t, errors.New("x509: unsupported time format") +func parseTime(der *cryptobyte.String) (time.Time, error) { + var t time.Time + switch { + case der.PeekASN1Tag(cryptobyte_asn1.UTCTime): + if !der.ReadASN1UTCTime(&t) { + return t, errors.New("x509: malformed UTCTime") } - return t, nil + case der.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime): + if !der.ReadASN1GeneralizedTime(&t) { + return t, errors.New("x509: malformed GeneralizedTime") + } + default: + return t, errors.New("x509: unsupported time format") } + return t, nil +} - notBefore, err := extract() +func parseValidity(der cryptobyte.String) (time.Time, time.Time, error) { + notBefore, err := parseTime(&der) if err != nil { return time.Time{}, time.Time{}, err } - notAfter, err := extract() + notAfter, err := parseTime(&der) if err != nil { return time.Time{}, time.Time{}, err } @@ -1011,3 +987,164 @@ func ParseCertificates(der []byte) ([]*Certificate, error) { } return certs, nil } + +// The X.509 standards confusingly 1-indexed the version names, but 0-indexed +// the actual encoded version, so the version for X.509v2 is 1. +const x509v2Version = 1 + +// ParseRevocationList parses a X509 v2 Certificate Revocation List from the given +// ASN.1 DER data. +func ParseRevocationList(der []byte) (*RevocationList, error) { + rl := &RevocationList{} + + input := cryptobyte.String(der) + // we read the SEQUENCE including length and tag bytes so that + // we can populate RevocationList.Raw, before unwrapping the + // SEQUENCE so it can be operated on + if !input.ReadASN1Element(&input, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed certificate") + } + rl.Raw = input + if !input.ReadASN1(&input, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed certificate") + } + + var tbs cryptobyte.String + // do the same trick again as above to extract the raw + // bytes for Certificate.RawTBSCertificate + if !input.ReadASN1Element(&tbs, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed tbs certificate") + } + rl.RawTBSRevocationList = tbs + if !tbs.ReadASN1(&tbs, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed tbs certificate") + } + + var version int + if !tbs.PeekASN1Tag(cryptobyte_asn1.INTEGER) { + return nil, errors.New("x509: unsupported crl version") + } + if !tbs.ReadASN1Integer(&version) { + return nil, errors.New("x509: malformed crl") + } + if version != x509v2Version { + return nil, fmt.Errorf("x509: unsupported crl version: %d", version) + } + + var sigAISeq cryptobyte.String + if !tbs.ReadASN1(&sigAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed signature algorithm identifier") + } + // Before parsing the inner algorithm identifier, extract + // the outer algorithm identifier and make sure that they + // match. + var outerSigAISeq cryptobyte.String + if !input.ReadASN1(&outerSigAISeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed algorithm identifier") + } + if !bytes.Equal(outerSigAISeq, sigAISeq) { + return nil, errors.New("x509: inner and outer signature algorithm identifiers don't match") + } + sigAI, err := parseAI(sigAISeq) + if err != nil { + return nil, err + } + rl.SignatureAlgorithm = getSignatureAlgorithmFromAI(sigAI) + + var signature asn1.BitString + if !input.ReadASN1BitString(&signature) { + return nil, errors.New("x509: malformed signature") + } + rl.Signature = signature.RightAlign() + + var issuerSeq cryptobyte.String + if !tbs.ReadASN1Element(&issuerSeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed issuer") + } + rl.RawIssuer = issuerSeq + issuerRDNs, err := parseName(issuerSeq) + if err != nil { + return nil, err + } + rl.Issuer.FillFromRDNSequence(issuerRDNs) + + rl.ThisUpdate, err = parseTime(&tbs) + if err != nil { + return nil, err + } + if tbs.PeekASN1Tag(cryptobyte_asn1.GeneralizedTime) || tbs.PeekASN1Tag(cryptobyte_asn1.UTCTime) { + rl.NextUpdate, err = parseTime(&tbs) + if err != nil { + return nil, err + } + } + + if tbs.PeekASN1Tag(cryptobyte_asn1.SEQUENCE) { + var revokedSeq cryptobyte.String + if !tbs.ReadASN1(&revokedSeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed crl") + } + for !revokedSeq.Empty() { + var certSeq cryptobyte.String + if !revokedSeq.ReadASN1(&certSeq, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed crl") + } + rc := pkix.RevokedCertificate{} + rc.SerialNumber = new(big.Int) + if !certSeq.ReadASN1Integer(rc.SerialNumber) { + return nil, errors.New("x509: malformed serial number") + } + rc.RevocationTime, err = parseTime(&certSeq) + if err != nil { + return nil, err + } + var extensions cryptobyte.String + var present bool + if !tbs.ReadOptionalASN1(&extensions, &present, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed extensions") + } + if present { + if !extensions.ReadASN1(&extensions, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed extensions") + } + for !extensions.Empty() { + var extension cryptobyte.String + if !extensions.ReadASN1(&extension, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed extension") + } + ext, err := parseExtension(extension) + if err != nil { + return nil, err + } + rc.Extensions = append(rc.Extensions, ext) + } + } + + rl.RevokedCertificates = append(rl.RevokedCertificates, rc) + } + } + + var extensions cryptobyte.String + var present bool + if !tbs.ReadOptionalASN1(&extensions, &present, cryptobyte_asn1.Tag(0).Constructed().ContextSpecific()) { + return nil, errors.New("x509: malformed extensions") + } + if present { + if !extensions.ReadASN1(&extensions, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed extensions") + } + for !extensions.Empty() { + var extension cryptobyte.String + if !extensions.ReadASN1(&extension, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: malformed extension") + } + ext, err := parseExtension(extension) + if err != nil { + return nil, err + } + rl.Extensions = append(rl.Extensions, ext) + } + } + + return rl, nil +} diff --git a/src/crypto/x509/pkix/pkix.go b/src/crypto/x509/pkix/pkix.go index e9179ed0679be..da57b66831b87 100644 --- a/src/crypto/x509/pkix/pkix.go +++ b/src/crypto/x509/pkix/pkix.go @@ -296,6 +296,8 @@ func (certList *CertificateList) HasExpired(now time.Time) bool { // TBSCertificateList represents the ASN.1 structure of the same name. See RFC // 5280, section 5.1. +// +// Deprecated: x509.RevocationList should be used instead. type TBSCertificateList struct { Raw asn1.RawContent Version int `asn1:"optional,default:0"` @@ -309,6 +311,8 @@ type TBSCertificateList struct { // RevokedCertificate represents the ASN.1 structure of the same name. See RFC // 5280, section 5.1. +// +// Deprecated: x509.RevocationList should be used instead. type RevokedCertificate struct { SerialNumber *big.Int RevocationTime time.Time diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index cb43079a9c75e..41d85c458d047 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -880,6 +880,8 @@ func checkSignature(algo SignatureAlgorithm, signed, signature []byte, publicKey } // CheckCRLSignature checks that the signature in crl is from c. +// +// Deprecated: Use RevocationList.CheckSignatureFrom instead. func (c *Certificate) CheckCRLSignature(crl *pkix.CertificateList) error { algo := getSignatureAlgorithmFromAI(crl.SignatureAlgorithm) return c.CheckSignature(algo, crl.TBSCertList.Raw, crl.SignatureValue.RightAlign()) @@ -1607,6 +1609,8 @@ var pemType = "X509 CRL" // encoded CRLs will appear where they should be DER encoded, so this function // will transparently handle PEM encoding as long as there isn't any leading // garbage. +// +// Deprecated: Use ParseRevocationList instead. func ParseCRL(crlBytes []byte) (*pkix.CertificateList, error) { if bytes.HasPrefix(crlBytes, pemCRLPrefix) { block, _ := pem.Decode(crlBytes) @@ -1618,6 +1622,8 @@ func ParseCRL(crlBytes []byte) (*pkix.CertificateList, error) { } // ParseDERCRL parses a DER encoded CRL from the given bytes. +// +// Deprecated: Use ParseRevocationList instead. func ParseDERCRL(derBytes []byte) (*pkix.CertificateList, error) { certList := new(pkix.CertificateList) if rest, err := asn1.Unmarshal(derBytes, certList); err != nil { @@ -1631,7 +1637,7 @@ func ParseDERCRL(derBytes []byte) (*pkix.CertificateList, error) { // CreateCRL returns a DER encoded CRL, signed by this Certificate, that // contains the given list of revoked certificates. // -// Note: this method does not generate an RFC 5280 conformant X.509 v2 CRL. +// Deprecated: this method does not generate an RFC 5280 conformant X.509 v2 CRL. // To generate a standards compliant CRL, use CreateRevocationList instead. func (c *Certificate) CreateCRL(rand io.Reader, priv any, revokedCerts []pkix.RevokedCertificate, now, expiry time.Time) (crlBytes []byte, err error) { key, ok := priv.(crypto.Signer) @@ -2073,6 +2079,14 @@ func (c *CertificateRequest) CheckSignature() error { // RevocationList contains the fields used to create an X.509 v2 Certificate // Revocation list with CreateRevocationList. type RevocationList struct { + Raw []byte + RawTBSRevocationList []byte + RawIssuer []byte + + Issuer pkix.Name + AuthorityKeyId []byte + + Signature []byte // SignatureAlgorithm is used to determine the signature algorithm to be // used when signing the CRL. If 0 the default algorithm for the signing // key will be used. @@ -2087,6 +2101,7 @@ type RevocationList struct { // which should be a monotonically increasing sequence number for a given // CRL scope and CRL issuer. Number *big.Int + // ThisUpdate is used to populate the thisUpdate field in the CRL, which // indicates the issuance date of the CRL. ThisUpdate time.Time @@ -2094,6 +2109,11 @@ type RevocationList struct { // indicates the date by which the next CRL will be issued. NextUpdate // must be greater than ThisUpdate. NextUpdate time.Time + + // Extensions contains raw X.509 extensions. When creating a CRL, + // the Extensions field is ignored, see ExtraExtensions. + Extensions []pkix.Extension + // ExtraExtensions contains any additional extensions to add directly to // the CRL. ExtraExtensions []pkix.Extension @@ -2207,3 +2227,22 @@ func CreateRevocationList(rand io.Reader, template *RevocationList, issuer *Cert SignatureValue: asn1.BitString{Bytes: signature, BitLength: len(signature) * 8}, }) } + +// CheckSignatureFrom verifies that the signature on rl is a valid signature +// from issuer. +func (rl *RevocationList) CheckSignatureFrom(parent *Certificate) error { + if parent.Version == 3 && !parent.BasicConstraintsValid || + parent.BasicConstraintsValid && !parent.IsCA { + return ConstraintViolationError{} + } + + if parent.KeyUsage != 0 && parent.KeyUsage&KeyUsageCRLSign == 0 { + return ConstraintViolationError{} + } + + if parent.PublicKeyAlgorithm == UnknownPublicKeyAlgorithm { + return ErrUnsupportedAlgorithm + } + + return parent.CheckSignature(rl.SignatureAlgorithm, rl.RawTBSRevocationList, rl.Signature) +} diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index d2889fc1d7bd2..b26ace89e72e2 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -2631,24 +2631,24 @@ func TestCreateRevocationList(t *testing.T) { return } - parsedCRL, err := ParseDERCRL(crl) + parsedCRL, err := ParseRevocationList(crl) if err != nil { t.Fatalf("Failed to parse generated CRL: %s", err) } if tc.template.SignatureAlgorithm != UnknownSignatureAlgorithm && - parsedCRL.SignatureAlgorithm.Algorithm.Equal(signatureAlgorithmDetails[tc.template.SignatureAlgorithm].oid) { + parsedCRL.SignatureAlgorithm != tc.template.SignatureAlgorithm { t.Fatalf("SignatureAlgorithm mismatch: got %v; want %v.", parsedCRL.SignatureAlgorithm, tc.template.SignatureAlgorithm) } - if !reflect.DeepEqual(parsedCRL.TBSCertList.RevokedCertificates, tc.template.RevokedCertificates) { + if !reflect.DeepEqual(parsedCRL.RevokedCertificates, tc.template.RevokedCertificates) { t.Fatalf("RevokedCertificates mismatch: got %v; want %v.", - parsedCRL.TBSCertList.RevokedCertificates, tc.template.RevokedCertificates) + parsedCRL.RevokedCertificates, tc.template.RevokedCertificates) } - if len(parsedCRL.TBSCertList.Extensions) != 2+len(tc.template.ExtraExtensions) { - t.Fatalf("Generated CRL has wrong number of extensions, wanted: %d, got: %d", 2+len(tc.template.ExtraExtensions), len(parsedCRL.TBSCertList.Extensions)) + if len(parsedCRL.Extensions) != 2+len(tc.template.ExtraExtensions) { + t.Fatalf("Generated CRL has wrong number of extensions, wanted: %d, got: %d", 2+len(tc.template.ExtraExtensions), len(parsedCRL.Extensions)) } expectedAKI, err := asn1.Marshal(authKeyId{Id: tc.issuer.SubjectKeyId}) if err != nil { @@ -2658,9 +2658,9 @@ func TestCreateRevocationList(t *testing.T) { Id: oidExtensionAuthorityKeyId, Value: expectedAKI, } - if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[0], akiExt) { + if !reflect.DeepEqual(parsedCRL.Extensions[0], akiExt) { t.Fatalf("Unexpected first extension: got %v, want %v", - parsedCRL.TBSCertList.Extensions[0], akiExt) + parsedCRL.Extensions[0], akiExt) } expectedNum, err := asn1.Marshal(tc.template.Number) if err != nil { @@ -2670,18 +2670,18 @@ func TestCreateRevocationList(t *testing.T) { Id: oidExtensionCRLNumber, Value: expectedNum, } - if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[1], crlExt) { + if !reflect.DeepEqual(parsedCRL.Extensions[1], crlExt) { t.Fatalf("Unexpected second extension: got %v, want %v", - parsedCRL.TBSCertList.Extensions[1], crlExt) + parsedCRL.Extensions[1], crlExt) } - if len(parsedCRL.TBSCertList.Extensions[2:]) == 0 && len(tc.template.ExtraExtensions) == 0 { + if len(parsedCRL.Extensions[2:]) == 0 && len(tc.template.ExtraExtensions) == 0 { // If we don't have anything to check return early so we don't // hit a [] != nil false positive below. return } - if !reflect.DeepEqual(parsedCRL.TBSCertList.Extensions[2:], tc.template.ExtraExtensions) { + if !reflect.DeepEqual(parsedCRL.Extensions[2:], tc.template.ExtraExtensions) { t.Fatalf("Extensions mismatch: got %v; want %v.", - parsedCRL.TBSCertList.Extensions[2:], tc.template.ExtraExtensions) + parsedCRL.Extensions[2:], tc.template.ExtraExtensions) } }) } @@ -3444,3 +3444,125 @@ func TestDisableSHA1ForCertOnly(t *testing.T) { t.Errorf("unexpected error: %s", err) } } + +func TestParseRevocationList(t *testing.T) { + derBytes := fromBase64(derCRLBase64) + certList, err := ParseRevocationList(derBytes) + if err != nil { + t.Errorf("error parsing: %s", err) + return + } + numCerts := len(certList.RevokedCertificates) + expected := 88 + if numCerts != expected { + t.Errorf("bad number of revoked certificates. got: %d want: %d", numCerts, expected) + } +} + +func TestRevocationListCheckSignatureFrom(t *testing.T) { + goodKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate test key: %s", err) + } + badKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader) + if err != nil { + t.Fatalf("failed to generate test key: %s", err) + } + tests := []struct { + name string + issuer *Certificate + err string + }{ + { + name: "valid", + issuer: &Certificate{ + Version: 3, + BasicConstraintsValid: true, + IsCA: true, + PublicKeyAlgorithm: ECDSA, + PublicKey: goodKey.Public(), + }, + }, + { + name: "valid, key usage set", + issuer: &Certificate{ + Version: 3, + BasicConstraintsValid: true, + IsCA: true, + PublicKeyAlgorithm: ECDSA, + PublicKey: goodKey.Public(), + KeyUsage: KeyUsageCRLSign, + }, + }, + { + name: "invalid issuer, wrong key usage", + issuer: &Certificate{ + Version: 3, + BasicConstraintsValid: true, + IsCA: true, + PublicKeyAlgorithm: ECDSA, + PublicKey: goodKey.Public(), + KeyUsage: KeyUsageCertSign, + }, + err: "x509: invalid signature: parent certificate cannot sign this kind of certificate", + }, + { + name: "invalid issuer, no basic constraints/ca", + issuer: &Certificate{ + Version: 3, + PublicKeyAlgorithm: ECDSA, + PublicKey: goodKey.Public(), + }, + err: "x509: invalid signature: parent certificate cannot sign this kind of certificate", + }, + { + name: "invalid issuer, unsupported public key type", + issuer: &Certificate{ + Version: 3, + BasicConstraintsValid: true, + IsCA: true, + PublicKeyAlgorithm: UnknownPublicKeyAlgorithm, + PublicKey: goodKey.Public(), + }, + err: "x509: cannot verify signature: algorithm unimplemented", + }, + { + name: "wrong key", + issuer: &Certificate{ + Version: 3, + BasicConstraintsValid: true, + IsCA: true, + PublicKeyAlgorithm: ECDSA, + PublicKey: badKey.Public(), + }, + err: "x509: ECDSA verification failure", + }, + } + + crlIssuer := &Certificate{ + BasicConstraintsValid: true, + IsCA: true, + PublicKeyAlgorithm: ECDSA, + PublicKey: goodKey.Public(), + KeyUsage: KeyUsageCRLSign, + SubjectKeyId: []byte{1, 2, 3}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + crlDER, err := CreateRevocationList(rand.Reader, &RevocationList{Number: big.NewInt(1)}, crlIssuer, goodKey) + if err != nil { + t.Fatalf("failed to generate CRL: %s", err) + } + crl, err := ParseRevocationList(crlDER) + if err != nil { + t.Fatalf("failed to parse test CRL: %s", err) + } + err = crl.CheckSignatureFrom(tc.issuer) + if err != nil && err.Error() != tc.err { + t.Errorf("unexpected error: got %s, want %s", err, tc.err) + } else if err == nil && tc.err != "" { + t.Errorf("CheckSignatureFrom did not fail: want %s", tc.err) + } + }) + } +} From 6c17529af785d14fd317e19f878327539ea6cd47 Mon Sep 17 00:00:00 2001 From: Meng Zhuo Date: Tue, 29 Mar 2022 19:14:24 +0800 Subject: [PATCH 019/137] cmd/internal/obj: set morestack arg spilling and regabi prologue on riscv64 This CL spill arg registers before calling morestack, unspill after. Also, avoid X11,X12,X13 in function prologue, which may carry live argument value. Change-Id: I7a2841fbe306f62a7765e212f9f0be5c11ce7f8c Reviewed-on: https://go-review.googlesource.com/c/go/+/396655 Trust: mzh Run-TryBot: mzh TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui --- src/cmd/internal/obj/riscv/obj.go | 72 ++++++++++++++++++------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/src/cmd/internal/obj/riscv/obj.go b/src/cmd/internal/obj/riscv/obj.go index 956d69ee2e765..0f52f6677944b 100644 --- a/src/cmd/internal/obj/riscv/obj.go +++ b/src/cmd/internal/obj/riscv/obj.go @@ -415,17 +415,17 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if cursym.Func().Text.From.Sym.Wrapper() { // if(g->panic != nil && g->panic->argp == FP) g->panic->argp = bottom-of-frame // - // MOV g_panic(g), X11 - // BNE X11, ZERO, adjust + // MOV g_panic(g), X5 + // BNE X5, ZERO, adjust // end: // NOP // ...rest of function.. // adjust: - // MOV panic_argp(X11), X12 - // ADD $(autosize+FIXED_FRAME), SP, X13 - // BNE X12, X13, end - // ADD $FIXED_FRAME, SP, X12 - // MOV X12, panic_argp(X11) + // MOV panic_argp(X5), X6 + // ADD $(autosize+FIXED_FRAME), SP, X7 + // BNE X6, X7, end + // ADD $FIXED_FRAME, SP, X6 + // MOV X6, panic_argp(X5) // JMP end // // The NOP is needed to give the jumps somewhere to land. @@ -435,11 +435,11 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { ldpanic.As = AMOV ldpanic.From = obj.Addr{Type: obj.TYPE_MEM, Reg: REGG, Offset: 4 * int64(ctxt.Arch.PtrSize)} // G.panic ldpanic.Reg = obj.REG_NONE - ldpanic.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X11} + ldpanic.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X5} bneadj := obj.Appendp(ldpanic, newprog) bneadj.As = ABNE - bneadj.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X11} + bneadj.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X5} bneadj.Reg = REG_ZERO bneadj.To.Type = obj.TYPE_BRANCH @@ -453,9 +453,9 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { getargp := obj.Appendp(last, newprog) getargp.As = AMOV - getargp.From = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_X11, Offset: 0} // Panic.argp + getargp.From = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_X5, Offset: 0} // Panic.argp getargp.Reg = obj.REG_NONE - getargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12} + getargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X6} bneadj.To.SetTarget(getargp) @@ -463,12 +463,12 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { calcargp.As = AADDI calcargp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: stacksize + ctxt.FixedFrameSize()} calcargp.Reg = REG_SP - calcargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X13} + calcargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X7} testargp := obj.Appendp(calcargp, newprog) testargp.As = ABNE - testargp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12} - testargp.Reg = REG_X13 + testargp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X6} + testargp.Reg = REG_X7 testargp.To.Type = obj.TYPE_BRANCH testargp.To.SetTarget(endadj) @@ -476,13 +476,13 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { adjargp.As = AADDI adjargp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: int64(ctxt.Arch.PtrSize)} adjargp.Reg = REG_SP - adjargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12} + adjargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X6} setargp := obj.Appendp(adjargp, newprog) setargp.As = AMOV - setargp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X12} + setargp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X6} setargp.Reg = obj.REG_NONE - setargp.To = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_X11, Offset: 0} // Panic.argp + setargp.To = obj.Addr{Type: obj.TYPE_MEM, Reg: REG_X5, Offset: 0} // Panic.argp godone := obj.Appendp(setargp, newprog) godone.As = AJAL @@ -732,6 +732,11 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA // Save LR and REGCTXT const frameSize = 16 p = ctxt.StartUnsafePoint(p, newprog) + + // Spill Arguments. This has to happen before we open + // any more frame space. + p = cursym.Func().SpillRegisterArgs(p, newprog) + // MOV LR, -16(SP) p = obj.Appendp(p, newprog) p.As = AMOV @@ -778,13 +783,15 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_SP} p.Spadj = -frameSize + // Unspill arguments + p = cursym.Func().UnspillRegisterArgs(p, newprog) p = ctxt.EndUnsafePoint(p, newprog, -1) } // Jump back to here after morestack returns. startPred := p - // MOV g_stackguard(g), X10 + // MOV g_stackguard(g), X6 p = obj.Appendp(p, newprog) p.As = AMOV p.From.Type = obj.TYPE_MEM @@ -794,7 +801,7 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA p.From.Offset = 3 * int64(ctxt.Arch.PtrSize) // G.stackguard1 } p.To.Type = obj.TYPE_REG - p.To.Reg = REG_X10 + p.To.Reg = REG_X6 // Mark the stack bound check and morestack call async nonpreemptible. // If we get preempted here, when resumed the preemption request is @@ -811,7 +818,7 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA p = obj.Appendp(p, newprog) p.As = ABLTU p.From.Type = obj.TYPE_REG - p.From.Reg = REG_X10 + p.From.Reg = REG_X6 p.Reg = REG_SP p.To.Type = obj.TYPE_BRANCH to_done = p @@ -826,52 +833,56 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA // stack guard to incorrectly succeed. We explicitly // guard against underflow. // - // MOV $(framesize-StackSmall), X11 - // BLTU SP, X11, label-of-call-to-morestack + // MOV $(framesize-StackSmall), X7 + // BLTU SP, X7, label-of-call-to-morestack p = obj.Appendp(p, newprog) p.As = AMOV p.From.Type = obj.TYPE_CONST p.From.Offset = offset p.To.Type = obj.TYPE_REG - p.To.Reg = REG_X11 + p.To.Reg = REG_X7 p = obj.Appendp(p, newprog) p.As = ABLTU p.From.Type = obj.TYPE_REG p.From.Reg = REG_SP - p.Reg = REG_X11 + p.Reg = REG_X7 p.To.Type = obj.TYPE_BRANCH to_more = p } // Check against the stack guard. We've ensured this won't underflow. - // ADD $-(framesize-StackSmall), SP, X11 - // // if X11 > stackguard { goto done } - // BLTU stackguard, X11, done + // ADD $-(framesize-StackSmall), SP, X7 + // // if X7 > stackguard { goto done } + // BLTU stackguard, X7, done p = obj.Appendp(p, newprog) p.As = AADDI p.From.Type = obj.TYPE_CONST p.From.Offset = -offset p.Reg = REG_SP p.To.Type = obj.TYPE_REG - p.To.Reg = REG_X11 + p.To.Reg = REG_X7 p = obj.Appendp(p, newprog) p.As = ABLTU p.From.Type = obj.TYPE_REG - p.From.Reg = REG_X10 - p.Reg = REG_X11 + p.From.Reg = REG_X6 + p.Reg = REG_X7 p.To.Type = obj.TYPE_BRANCH to_done = p } + // Spill the register args that could be clobbered by the + // morestack code p = ctxt.EmitEntryStackMap(cursym, p, newprog) + p = cursym.Func().SpillRegisterArgs(p, newprog) // CALL runtime.morestack(SB) p = obj.Appendp(p, newprog) p.As = obj.ACALL p.To.Type = obj.TYPE_BRANCH + if cursym.CFunc() { p.To.Sym = ctxt.Lookup("runtime.morestackc") } else if !cursym.Func().Text.From.Sym.NeedCtxt() { @@ -884,6 +895,7 @@ func stacksplit(ctxt *obj.Link, p *obj.Prog, cursym *obj.LSym, newprog obj.ProgA } jalToSym(ctxt, p, REG_X5) + p = cursym.Func().UnspillRegisterArgs(p, newprog) p = ctxt.EndUnsafePoint(p, newprog, -1) // JMP start From 5bb2628c6f143be065776727cef03276c0e516f7 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Tue, 5 Apr 2022 20:28:16 +0000 Subject: [PATCH 020/137] bytes: limit allocation in SplitN So that bytes.SplitN("", "T", int(144115188075855872)) does not panic. Change-Id: I7c068852bd708416164fc2ed8b84cf6b2d593666 GitHub-Last-Rev: f8df09d65e2bc889fbd0c736bfb5e9a9078dfced GitHub-Pull-Request: golang/go#52147 Reviewed-on: https://go-review.googlesource.com/c/go/+/398076 Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Reviewed-by: mzh --- src/bytes/bytes.go | 3 +++ src/bytes/bytes_test.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index e3dab4d03560b..d9d502927ee93 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -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-- diff --git a/src/bytes/bytes_test.go b/src/bytes/bytes_test.go index 2e6ab315404e8..b702efb23933c 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) { From 81ae993e54547415ba674082801b05961e3f2aa3 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Wed, 6 Apr 2022 12:03:37 -0400 Subject: [PATCH 021/137] net/http: ignore ECONNRESET errors in TestTransportConcurrency on netbsd The source of these errors is undiagnosed, but they have only been observed on netbsd builders (on a variety of architectures). Tested manually by injecting this code into the test's handler: if mrand.Intn(4) == 0 { if conn, _, err := w.(Hijacker).Hijack(); err == nil { conn.(*net.TCPConn).SetLinger(0) conn.Close() return } } and temporarily disabling the 'runtime.GOOS' part of the condition. For #52168. Change-Id: I10965803e5a0d493ac4a000575de8b5f0266989c Reviewed-on: https://go-review.googlesource.com/c/go/+/398635 Trust: Bryan Mills Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/net/http/transport_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index 440d6b969b08a..84065c70851eb 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() From 9a6acc83c853c17700c44e336e2d3e2c0fe9a72b Mon Sep 17 00:00:00 2001 From: mprahl Date: Tue, 5 Apr 2022 15:06:33 -0400 Subject: [PATCH 022/137] text/template: support delimiters that can be confused with actions In fields that start with the same character as the right delimiter, the whole delimiter needs to be checked. The first character alone is not sufficient. Fixes #52165 Change-Id: I1e4086048417693757f34d0e9ff3bf86aba0d35c Reviewed-on: https://go-review.googlesource.com/c/go/+/398475 Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Rob Pike Trust: Ian Lance Taylor --- src/text/template/parse/lex.go | 24 ++++++++++++++++++------ src/text/template/parse/lex_test.go | 16 ++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/text/template/parse/lex.go b/src/text/template/parse/lex.go index 40d0411121b7c..078f714ccf5bf 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 df6aabffb20e1..fcb7e8eacddeb 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{ From 870256ec891421d9d68365f65e48d270e16958bb Mon Sep 17 00:00:00 2001 From: j178 Date: Thu, 7 Apr 2022 02:44:08 +0000 Subject: [PATCH 023/137] hash/maphash: use correct method name in comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I01a3a5232525683c987b52ab8ece3fc18b6f431b GitHub-Last-Rev: d2ec8fe536c7a1cdbd23017185447a86bee5a82a GitHub-Pull-Request: golang/go#52194 Reviewed-on: https://go-review.googlesource.com/c/go/+/398714 Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Daniel Martí Trust: Daniel Martí --- src/hash/maphash/maphash.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hash/maphash/maphash.go b/src/hash/maphash/maphash.go index 973fb6870180b..ffd488fa1708f 100644 --- a/src/hash/maphash/maphash.go +++ b/src/hash/maphash/maphash.go @@ -40,7 +40,7 @@ type Seed struct { // var h Hash // h.SetSeed(seed) // h.Write(b) -// return h.Sum() +// return h.Sum64() func Bytes(seed Seed, b []byte) uint64 { state := seed.s if state == 0 { @@ -66,7 +66,7 @@ func Bytes(seed Seed, b []byte) uint64 { // var h Hash // h.SetSeed(seed) // h.WriteString(s) -// return h.Sum() +// return h.Sum64() func String(seed Seed, s string) uint64 { state := seed.s if state == 0 { From d3362fc1242f20c40f6d3986ddf4398019c8ea26 Mon Sep 17 00:00:00 2001 From: Meng Zhuo Date: Sun, 31 Oct 2021 22:06:47 +0800 Subject: [PATCH 024/137] cmd/compile: enable reg args on riscv64 This CL updates config.go to enable register args. Change-Id: I00697fc3db23293be0f5bd2fe33fb0055eeab43e Reviewed-on: https://go-review.googlesource.com/c/go/+/360217 Trust: mzh Run-TryBot: mzh TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui --- src/cmd/compile/internal/ssa/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go index b9c98bdba90d7..f112881153b56 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -297,8 +297,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": From 3a0cda43a49793429bc38bdcb1d2112179b02fe1 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Thu, 31 Mar 2022 14:16:49 +1100 Subject: [PATCH 025/137] image/draw: have draw.Src preserve NRGBA colors This reverts a behavior change introduced in Go 1.18 (commit 9f69a443; CL 340049). In Go 1.17 and earlier, draw.Draw(etc, draw.Src) with image.NRGBA dst and src images would pass through a (heap allocated) color.Color interface value holding a color.NRGBA concrete value. Threading that color.NRGBA value all the way through preserves non-premultiplied alpha transparency information (distinguishing e.g. transparent blue from transparent red). CL 340049 optimized out that heap allocation (per pixel), calling new SetRGBA64At and RGBA64At methods instead. However, these methods (like the existing image/color Color.RGBA method) work in premultiplied alpha, so any distinction between transparent colors is lost. This commit re-introduces the preservation of distinct transparencies, when dst and src are both *image.NRGBA (or both *image.NRGBA64) and the op is draw.Src. Fixes #51893 Change-Id: Id9c64bfeeaecc458586f169f50b99d6c8aa52a7f Reviewed-on: https://go-review.googlesource.com/c/go/+/396795 Trust: Nigel Tao Reviewed-by: Dmitri Shuralyov --- doc/go1.19.html | 13 ++++++++ src/image/draw/draw.go | 59 +++++++++++++++++++++++++--------- src/image/draw/draw_test.go | 64 +++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 15 deletions(-) diff --git a/doc/go1.19.html b/doc/go1.19.html index 857d8ed8cee6d..5c48302bf7829 100644 --- a/doc/go1.19.html +++ b/doc/go1.19.html @@ -76,6 +76,19 @@

Minor changes to the library

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

diff --git a/src/image/draw/draw.go b/src/image/draw/draw.go index 7dd18dfdb59a1..920ebb905eb61 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 3be93962ad738..a34d1c3e6e8ab 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. From 063f4032f5ea8820d265ee1196ef9b8eba02c63f Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Tue, 29 Mar 2022 12:51:31 -0700 Subject: [PATCH 026/137] sort: add Find function For golang/go#50340 Change-Id: I3b4d278affc8e7ec706db8c9777f7a8c8ce7441d Reviewed-on: https://go-review.googlesource.com/c/go/+/396514 Reviewed-by: Ian Lance Taylor Trust: Cherry Mui Run-TryBot: Russ Cox Auto-Submit: Russ Cox TryBot-Result: Gopher Robot --- api/next/50340.txt | 1 + src/sort/search.go | 41 ++++++++++++++++ src/sort/search_test.go | 106 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 api/next/50340.txt diff --git a/api/next/50340.txt b/api/next/50340.txt new file mode 100644 index 0000000000000..211392cd25203 --- /dev/null +++ b/api/next/50340.txt @@ -0,0 +1 @@ +pkg sort, func Find(int, func(int) int) (int, bool) #50340 diff --git a/src/sort/search.go b/src/sort/search.go index 601557a94b310..434349416eebc 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 f06897ee21e08..49813eaecb0fe 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) + } + } + } +} From c0bbeb0982403db17bacb1533776fb638cb449ae Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Fri, 1 Apr 2022 17:02:28 -0700 Subject: [PATCH 027/137] cmd/compile: adjust types2 shift check to match go/types (cleanup) With this change, the shift checking code matches the corresponding go/types code, but for the differences in the internal error reporting, and call of check.overflow. The change leads to the recording of an untyped int value if the RHS of a non-constant shift is an untyped integer value. Adjust the type in the compiler's irgen accordingly. Add test/shift3.go to verify behavior. Change-Id: I20386fcb1d5c48becffdc2203081fb70c08b282d Reviewed-on: https://go-review.googlesource.com/c/go/+/398236 Trust: Robert Griesemer Run-TryBot: Robert Griesemer Trust: Matthew Dempsky Reviewed-by: Matthew Dempsky TryBot-Result: Gopher Robot Reviewed-by: Robert Findley --- src/cmd/compile/internal/noder/expr.go | 9 ++++ src/cmd/compile/internal/types2/api_test.go | 12 +++++ src/cmd/compile/internal/types2/expr.go | 46 +++++++++++++------ .../types2/testdata/fixedbugs/issue52031.go | 33 +++++++++++++ test/shift3.go | 41 +++++++++++++++++ 5 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 src/cmd/compile/internal/types2/testdata/fixedbugs/issue52031.go create mode 100644 test/shift3.go diff --git a/src/cmd/compile/internal/noder/expr.go b/src/cmd/compile/internal/noder/expr.go index 566abda963e9a..e37e4cd661d42 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/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index 528beaaceabcf..fde7291b03ecb 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/expr.go b/src/cmd/compile/internal/types2/expr.go index 1ecb4ff54bd77..e0c22f5b03836 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/testdata/fixedbugs/issue52031.go b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue52031.go new file mode 100644 index 0000000000000..448a550b250ca --- /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/test/shift3.go b/test/shift3.go new file mode 100644 index 0000000000000..bed2fd66ef788 --- /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< Date: Mon, 4 Apr 2022 11:40:44 -0700 Subject: [PATCH 028/137] test: extend issue52124.go to also test #52139 Change-Id: I7da79d52d50d96536a8175ba08e9da551d07fadd Reviewed-on: https://go-review.googlesource.com/c/go/+/398094 Trust: Matthew Dempsky Run-TryBot: Matthew Dempsky Reviewed-by: Robert Griesemer Reviewed-by: Cuong Manh Le TryBot-Result: Gopher Robot --- test/typeparam/issue52124.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/typeparam/issue52124.go b/test/typeparam/issue52124.go index 56318d5d4ce0f..a113fc74441f1 100644 --- a/test/typeparam/issue52124.go +++ b/test/typeparam/issue52124.go @@ -7,3 +7,9 @@ package p type I interface{ any | int } + +var ( + X I = 42 + Y I = "xxx" + Z I = true +) From 6f6942ef7afc34c6b7e1eea3031ed61acc458d2a Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 7 Apr 2022 13:02:35 -0700 Subject: [PATCH 029/137] doc/go1.19: use the right package error.Is arguments They were swapped. Fixes #52205 Change-Id: Iea2626aa2204f3bc96d08c571a1aa669436a32ad Reviewed-on: https://go-review.googlesource.com/c/go/+/398895 Trust: Ian Lance Taylor Reviewed-by: Cherry Mui --- doc/go1.19.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/go1.19.html b/doc/go1.19.html index 5c48302bf7829..c1523c57ecb81 100644 --- a/doc/go1.19.html +++ b/doc/go1.19.html @@ -106,9 +106,9 @@

Minor changes to the library

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 From 8d581f589eb9b9eac05c75f0dfe82a49c3afcd2f Mon Sep 17 00:00:00 2001 From: "Paul E. Murphy" Date: Thu, 24 Mar 2022 10:09:13 -0500 Subject: [PATCH 030/137] crypto/aes: simplify key expansion in ppc64le asm The ported cryptogam implementation uses a subtle and tricky mechanism using lxv/vperm/lvsl to load unaligned vectors. This is difficult to read, and may read and write unrelated bytes if reading from an unaligned address. Instead, POWER8 instructions can be used to load from unaligned memory with much less overhead. Alignment interrupts only occur when reading or writing cache-inhibited memory, which we assume isn't used in go today, otherwise alignment penalties are usually marginal. Instead lxvd2x+xxpermdi and xxpermdi+stxvd2x can be used to emulate unaligned LE bytewise loads, similar to lxv/stxv on POWER9 in little-endian mode. Likewise, a custom permute vector is used to emulate BE bytewise storage operations, lxvb16x/stxvb16x, on POWER9. This greatly simplifies the code, and it makes it much easier to store the keys in reverse (which is exactly how the decrypt keys are expected to be stored). Change-Id: I2334337e31a8fdf8d13ba96231142a039f237098 Reviewed-on: https://go-review.googlesource.com/c/go/+/395494 Reviewed-by: Lynn Boger Trust: Paul Murphy Run-TryBot: Paul Murphy TryBot-Result: Gopher Robot --- src/crypto/aes/asm_ppc64le.s | 132 ++++++++++++++--------------------- 1 file changed, 54 insertions(+), 78 deletions(-) diff --git a/src/crypto/aes/asm_ppc64le.s b/src/crypto/aes/asm_ppc64le.s index 5eae675322996..192a8096cd1bc 100644 --- a/src/crypto/aes/asm_ppc64le.s +++ b/src/crypto/aes/asm_ppc64le.s @@ -43,6 +43,10 @@ #define OUTHEAD V10 #define OUTTAIL V11 +// For P9 instruction emulation +#define ESPERM V21 // Endian swapping permute into BE +#define TMP2 V22 // Temporary for P8_STXVB16X/P8_STXV + // For {en,de}cryptBlockAsm #define BLK_INP R3 #define BLK_OUT R4 @@ -50,15 +54,38 @@ #define BLK_ROUNDS R6 #define BLK_IDX R7 -DATA ·rcon+0x00(SB)/8, $0x0100000001000000 // RCON -DATA ·rcon+0x08(SB)/8, $0x0100000001000000 // RCON -DATA ·rcon+0x10(SB)/8, $0x1b0000001b000000 -DATA ·rcon+0x18(SB)/8, $0x1b0000001b000000 -DATA ·rcon+0x20(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK -DATA ·rcon+0x28(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK -DATA ·rcon+0x30(SB)/8, $0x0000000000000000 -DATA ·rcon+0x38(SB)/8, $0x0000000000000000 -GLOBL ·rcon(SB), RODATA, $64 +DATA ·rcon+0x00(SB)/8, $0x0f0e0d0c0b0a0908 // Permute for vector doubleword endian swap +DATA ·rcon+0x08(SB)/8, $0x0706050403020100 +DATA ·rcon+0x10(SB)/8, $0x0100000001000000 // RCON +DATA ·rcon+0x18(SB)/8, $0x0100000001000000 // RCON +DATA ·rcon+0x20(SB)/8, $0x1b0000001b000000 +DATA ·rcon+0x28(SB)/8, $0x1b0000001b000000 +DATA ·rcon+0x30(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK +DATA ·rcon+0x38(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK +DATA ·rcon+0x40(SB)/8, $0x0000000000000000 +DATA ·rcon+0x48(SB)/8, $0x0000000000000000 +GLOBL ·rcon(SB), RODATA, $80 + +// Emulate unaligned BE vector load/stores on LE targets +#define P8_LXVB16X(RA,RB,VT) \ + LXVD2X (RA+RB), VT \ + VPERM VT, VT, ESPERM, VT + +#define P8_STXVB16X(VS,RA,RB) \ + VPERM VS, VS, ESPERM, TMP2 \ + STXVD2X TMP2, (RA+RB) + +#define P8_STXV(VS,RA,RB) \ + XXPERMDI VS, VS, $2, TMP2 \ + STXVD2X TMP2, (RA+RB) + +#define P8_LXV(RA,RB,VT) \ + LXVD2X (RA+RB), VT \ + XXPERMDI VT, VT, $2, VT + +#define LXSDX_BE(RA,RB,VT) \ + LXSDX (RA+RB), VT \ + VPERM VT, VT, ESPERM, VT // func setEncryptKeyAsm(key *byte, keylen int, enc *uint32) int TEXT ·setEncryptKeyAsm(SB), NOSPLIT|NOFRAME, $0 @@ -87,45 +114,32 @@ TEXT ·doEncryptKeyAsm(SB), NOSPLIT|NOFRAME, $0 BC 0x06, 2, enc_key_abort // bne- .Lenc_key_abort MOVD $·rcon(SB), PTR // PTR point to rcon addr + LVX (PTR), ESPERM + ADD $0x10, PTR // Get key from memory and write aligned into VR - NEG INP, R9 // neg 9,3 R9 is ~INP + 1 - LVX (INP)(R0), IN0 // lvx 1,0,3 Load key inside IN0 - ADD $15, INP, INP // addi 3,3,15 Add 15B to INP addr - LVSR (R9)(R0), KEY // lvsr 3,0,9 + P8_LXVB16X(INP, R0, IN0) + ADD $0x10, INP, INP MOVD $0x20, R8 // li 8,0x20 R8 = 32 + CMPW BITS, $192 // cmpwi 4,192 Key size == 192? - LVX (INP)(R0), IN1 // lvx 2,0,3 - VSPLTISB $0x0f, MASK// vspltisb 5,0x0f 0x0f0f0f0f... mask LVX (PTR)(R0), RCON // lvx 4,0,6 Load first 16 bytes into RCON - VXOR KEY, MASK, KEY // vxor 3,3,5 Adjust for byte swap LVX (PTR)(R8), MASK // lvx 5,8,6 ADD $0x10, PTR, PTR // addi 6,6,0x10 PTR to next 16 bytes of RCON - VPERM IN0, IN1, KEY, IN0 // vperm 1,1,2,3 Align MOVD $8, CNT // li 7,8 CNT = 8 VXOR ZERO, ZERO, ZERO // vxor 0,0,0 Zero to be zero :) MOVD CNT, CTR // mtctr 7 Set the counter to 8 (rounds) - LVSL (OUT)(R0), OUTPERM // lvsl 8,0,5 - VSPLTISB $-1, OUTMASK // vspltisb 9,-1 - LVX (OUT)(R0), OUTHEAD // lvx 10,0,5 - VPERM OUTMASK, ZERO, OUTPERM, OUTMASK // vperm 9,9,0,8 - BLT loop128 // blt .Loop128 - ADD $8, INP, INP // addi 3,3,8 BEQ l192 // beq .L192 - ADD $8, INP, INP // addi 3,3,8 JMP l256 // b .L256 loop128: // Key schedule (Round 1 to 8) VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-splat VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 Rotate - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 + P8_STXV(IN0, R0, OUT) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - STVX STAGE, (OUT+R0) // stvx 7,0,5 Write to output ADD $16, OUT, OUT // addi 5,5,16 Point to the next round VXOR IN0, TMP, IN0 // vxor 1,1,6 @@ -142,11 +156,8 @@ loop128: // Key schedule (Round 9) VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-spat VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 Rotate - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 + P8_STXV(IN0, R0, OUT) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - STVX STAGE, (OUT+R0) // stvx 7,0,5 Round 9 ADD $16, OUT, OUT // addi 5,5,16 // Key schedule (Round 10) @@ -160,11 +171,8 @@ loop128: VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-splat VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 Rotate - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 + P8_STXV(IN0, R0, OUT) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - STVX STAGE, (OUT+R0) // stvx 7,0,5 Round 10 ADD $16, OUT, OUT // addi 5,5,16 // Key schedule (Round 11) @@ -174,26 +182,18 @@ loop128: VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 VXOR IN0, TMP, IN0 // vxor 1,1,6 VXOR IN0, KEY, IN0 // vxor 1,1,3 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 - STVX STAGE, (OUT+R0) // stvx 7,0,5 Round 11 + P8_STXV(IN0, R0, OUT) - ADD $15, OUT, INP // addi 3,5,15 ADD $0x50, OUT, OUT // addi 5,5,0x50 MOVD $10, ROUNDS // li 8,10 JMP done // b .Ldone l192: - LVX (INP)(R0), TMP // lvx 6,0,3 + LXSDX_BE(INP, R0, IN1) // Load next 8 bytes into upper half of VSR in BE order. MOVD $4, CNT // li 7,4 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 - STVX STAGE, (OUT+R0) // stvx 7,0,5 + P8_STXV(IN0, R0, OUT) ADD $16, OUT, OUT // addi 5,5,16 - VPERM IN1, TMP, KEY, IN1 // vperm 2,2,6,3 VSPLTISB $8, KEY // vspltisb 3,8 MOVD CNT, CTR // mtctr 7 VSUBUBM MASK, KEY, MASK // vsububm 5,5,3 @@ -221,23 +221,17 @@ loop192: VPERM IN1, IN1, MASK, KEY // vperm 3,2,2,5 VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - VPERM STAGE, STAGE, OUTPERM, OUTTAIL // vperm 11,7,7,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 + P8_STXV(STAGE, R0, OUT) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - STVX STAGE, (OUT+R0) // stvx 7,0,5 ADD $16, OUT, OUT // addi 5,5,16 VSLDOI $8, IN0, IN1, STAGE // vsldoi 7,1,2,8 VXOR IN0, TMP, IN0 // vxor 1,1,6 VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 - VPERM STAGE, STAGE, OUTPERM, OUTTAIL // vperm 11,7,7,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 + P8_STXV(STAGE, R0, OUT) VXOR IN0, TMP, IN0 // vxor 1,1,6 VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 VXOR IN0, TMP, IN0 // vxor 1,1,6 - STVX STAGE, (OUT+R0) // stvx 7,0,5 ADD $16, OUT, OUT // addi 5,5,16 VSPLTW $3, IN0, TMP // vspltw 6,1,3 @@ -247,11 +241,7 @@ loop192: VXOR IN1, TMP, IN1 // vxor 2,2,6 VXOR IN0, KEY, IN0 // vxor 1,1,3 VXOR IN1, KEY, IN1 // vxor 2,2,3 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 - STVX STAGE, (OUT+R0) // stvx 7,0,5 - ADD $15, OUT, INP // addi 3,5,15 + P8_STXV(IN0, R0, OUT) ADD $16, OUT, OUT // addi 5,5,16 BC 0x10, 0, loop192 // bdnz .Loop192 @@ -260,25 +250,18 @@ loop192: BR done // b .Ldone l256: - LVX (INP)(R0), TMP // lvx 6,0,3 + P8_LXVB16X(INP, R0, IN1) MOVD $7, CNT // li 7,7 MOVD $14, ROUNDS // li 8,14 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 - STVX STAGE, (OUT+R0) // stvx 7,0,5 + P8_STXV(IN0, R0, OUT) ADD $16, OUT, OUT // addi 5,5,16 - VPERM IN1, TMP, KEY, IN1 // vperm 2,2,6,3 MOVD CNT, CTR // mtctr 7 loop256: VPERM IN1, IN1, MASK, KEY // vperm 3,2,2,5 VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - VPERM IN1, IN1, OUTPERM, OUTTAIL // vperm 11,2,2,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 + P8_STXV(IN1, R0, OUT) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - STVX STAGE, (OUT+R0) // stvx 7,0,5 ADD $16, OUT, OUT // addi 5,5,16 VXOR IN0, TMP, IN0 // vxor 1,1,6 @@ -288,11 +271,7 @@ loop256: VXOR IN0, TMP, IN0 // vxor 1,1,6 VADDUWM RCON, RCON, RCON // vadduwm 4,4,4 VXOR IN0, KEY, IN0 // vxor 1,1,3 - VPERM IN0, IN0, OUTPERM, OUTTAIL // vperm 11,1,1,8 - VSEL OUTHEAD, OUTTAIL, OUTMASK, STAGE // vsel 7,10,11,9 - VOR OUTTAIL, OUTTAIL, OUTHEAD // vor 10,11,11 - STVX STAGE, (OUT+R0) // stvx 7,0,5 - ADD $15, OUT, INP // addi 3,5,15 + P8_STXV(IN0, R0, OUT) ADD $16, OUT, OUT // addi 5,5,16 BC 0x12, 0, done // bdz .Ldone @@ -310,9 +289,6 @@ loop256: JMP loop256 // b .Loop256 done: - LVX (INP)(R0), IN1 // lvx 2,0,3 - VSEL OUTHEAD, IN1, OUTMASK, IN1 // vsel 2,10,2,9 - STVX IN1, (INP+R0) // stvx 2,0,3 MOVD $0, PTR // li 6,0 set PTR to 0 (exit code 0) MOVW ROUNDS, 0(OUT) // stw 8,0(5) From c451a02a6d3e95d279260bd8c1edae676d62997d Mon Sep 17 00:00:00 2001 From: hopehook Date: Thu, 7 Apr 2022 23:53:12 +0800 Subject: [PATCH 031/137] strings, bytes: improve the description of simple case-folding in EqualFold This CL removes the problem description pointed out by @bjkail. Second, synchronously modify the comments of the bytes package. Updates #52022 Fixes #52204 Change-Id: I0aa52c774f40bb91f32bebdd2a62a11067a77be0 Reviewed-on: https://go-review.googlesource.com/c/go/+/398736 Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor TryBot-Result: Gopher Robot Trust: Cherry Mui --- src/bytes/bytes.go | 2 +- src/strings/strings.go | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index d9d502927ee93..979cf1ccf075b 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -1142,7 +1142,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/strings/strings.go b/src/strings/strings.go index 74e505338e1e5..8294f7ec35547 100644 --- a/src/strings/strings.go +++ b/src/strings/strings.go @@ -1043,8 +1043,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. From 5a90270d7f5b384de31399133c7336d007fbd93d Mon Sep 17 00:00:00 2001 From: hopehook Date: Mon, 4 Apr 2022 23:01:16 +0800 Subject: [PATCH 032/137] cmd/compile: fix deadlock on syntax error Fixes #52127 Change-Id: I6523c83350cb9263d23e3e8b472fe63a5cc99c2e Reviewed-on: https://go-review.googlesource.com/c/go/+/398014 Reviewed-by: Matthew Dempsky Run-TryBot: Matthew Dempsky Auto-Submit: Matthew Dempsky TryBot-Result: Gopher Robot Trust: Cherry Mui --- src/cmd/compile/internal/noder/noder.go | 37 ++++++++++++++----------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index 77ca642183b30..bbd73aa8bee06 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -33,29 +33,34 @@ 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() { + go func() { + for i, filename := range filenames { + filename := filename + p := noders[i] 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() + 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 - }() - } + p.file, _ = syntax.Parse(fbase, f, p.error, p.pragma, mode) // errors are tracked via p.error + }() + } + }() var lines uint for _, p := range noders { From 3e7ffb862f550c38ce0611b970a4dce10a01226e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Thu, 7 Apr 2022 15:13:47 +0900 Subject: [PATCH 033/137] all: consistently use US spelling of present participles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It has been agreed that we should prefer the US spelling of words like "canceling" over "cancelling"; for example, see https://go.dev/cl/14526. Fix a few occurrences of the "canceling" inconsistency, as well as: * signaling * tunneling * marshaling Change-Id: I99f3ba0a700a9f0292bc6c1b110af31dd05f1ff0 Reviewed-on: https://go-review.googlesource.com/c/go/+/398734 Trust: Daniel Martí Run-TryBot: Daniel Martí TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/cmd/compile/internal/dwarfgen/dwarf.go | 2 +- src/cmd/compile/internal/ir/const.go | 2 +- src/cmd/go/internal/work/security.go | 2 +- src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go | 4 ++-- src/crypto/tls/handshake_client_test.go | 2 +- src/crypto/tls/handshake_server_test.go | 2 +- src/net/http/fcgi/fcgi_test.go | 8 ++++---- src/net/http/server.go | 2 +- src/reflect/visiblefields_test.go | 2 +- src/runtime/mgcsweep.go | 2 +- src/testing/testing.go | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go index e249a52e57a98..ba73976504e16 100644 --- a/src/cmd/compile/internal/dwarfgen/dwarf.go +++ b/src/cmd/compile/internal/dwarfgen/dwarf.go @@ -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/ir/const.go b/src/cmd/compile/internal/ir/const.go index eaa4d5b6b15ca..f0b66957f1201 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/go/internal/work/security.go b/src/cmd/go/internal/work/security.go index d1e2c673fadd8..0bf8763543b14 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/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go b/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go index d42ab0f29441f..e30e41fe3398b 100644 --- a/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go +++ b/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go @@ -4037,9 +4037,9 @@ var instFormats = [...]instFormat{ [6]*argField{ap_VecReg_6_10, ap_VecReg_16_20}}, {XSADDSP, 0xfc0007f800000000, 0xf000000000000000, 0x0, // VSX Scalar Add Single-Precision XX3-form (xsaddsp XT,XA,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_29_29_11_15, ap_VecSReg_30_30_16_20}}, - {XSCVDPSPN, 0xfc0007fc00000000, 0xf000042c00000000, 0x1f000000000000, // VSX Scalar Convert Scalar Single-Precision to Vector Single-Precision format Non-signalling XX2-form (xscvdpspn XT,XB) + {XSCVDPSPN, 0xfc0007fc00000000, 0xf000042c00000000, 0x1f000000000000, // VSX Scalar Convert Scalar Single-Precision to Vector Single-Precision format Non-signaling XX2-form (xscvdpspn XT,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_30_30_16_20}}, - {XSCVSPDPN, 0xfc0007fc00000000, 0xf000052c00000000, 0x1f000000000000, // VSX Scalar Convert Single-Precision to Double-Precision format Non-signalling XX2-form (xscvspdpn XT,XB) + {XSCVSPDPN, 0xfc0007fc00000000, 0xf000052c00000000, 0x1f000000000000, // VSX Scalar Convert Single-Precision to Double-Precision format Non-signaling XX2-form (xscvspdpn XT,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_30_30_16_20}}, {XSCVSXDSP, 0xfc0007fc00000000, 0xf00004e000000000, 0x1f000000000000, // VSX Scalar Convert with round Signed Doubleword to Single-Precision format XX2-form (xscvsxdsp XT,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_30_30_16_20}}, diff --git a/src/crypto/tls/handshake_client_test.go b/src/crypto/tls/handshake_client_test.go index 0950bb0ac4533..380de9f6fb50e 100644 --- a/src/crypto/tls/handshake_client_test.go +++ b/src/crypto/tls/handshake_client_test.go @@ -2564,7 +2564,7 @@ func testResumptionKeepsOCSPAndSCT(t *testing.T, ver uint16) { } } -// TestClientHandshakeContextCancellation tests that cancelling +// TestClientHandshakeContextCancellation tests that canceling // the context given to the client side conn.HandshakeContext // interrupts the in-progress handshake. func TestClientHandshakeContextCancellation(t *testing.T) { diff --git a/src/crypto/tls/handshake_server_test.go b/src/crypto/tls/handshake_server_test.go index 6d2c4056261fe..16a22542ebf50 100644 --- a/src/crypto/tls/handshake_server_test.go +++ b/src/crypto/tls/handshake_server_test.go @@ -1944,7 +1944,7 @@ func TestAESCipherReorderingTLS13(t *testing.T) { } } -// TestServerHandshakeContextCancellation tests that cancelling +// TestServerHandshakeContextCancellation tests that canceling // the context given to the server side conn.HandshakeContext // interrupts the in-progress handshake. func TestServerHandshakeContextCancellation(t *testing.T) { diff --git a/src/net/http/fcgi/fcgi_test.go b/src/net/http/fcgi/fcgi_test.go index 5888783620874..7a344ff31dc46 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/server.go b/src/net/http/server.go index bd4ef1baccd00..77e0108426e0a 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -1721,7 +1721,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. // diff --git a/src/reflect/visiblefields_test.go b/src/reflect/visiblefields_test.go index fdedc21f738b5..66d545dd1f7c2 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/runtime/mgcsweep.go b/src/runtime/mgcsweep.go index c863ea9cd8eaf..d0b81fd3dfb95 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 diff --git a/src/testing/testing.go b/src/testing/testing.go index 05d8f22affdb5..badc159159ec6 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -219,7 +219,7 @@ // 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) { +// func FuzzJSONMarshaling(f *testing.F) { // f.Fuzz(func(t *testing.T, b []byte) { // var v interface{} // if err := json.Unmarshal(b, &v); err != nil { From 3e387528e54971d6009fe8833dcab6fc08737e04 Mon Sep 17 00:00:00 2001 From: qmuntal Date: Thu, 31 Mar 2022 14:15:22 +0200 Subject: [PATCH 034/137] cmd/go: cgo export header to be compatible with MSVC complex types After CL 379474 has landed, the only remaining cgo export header incompatibility with MSVC is the use of the _Complex macro, which is not supported in MSVC even when it is part of the ISO C99 standard (1). Since MSVC 2015 (2), complex math are supported via _Fcomplex and _Dcomplex, which are equivalent to float _Complex and double _Complex. As MSVC and C complex types have the same memory layout, we should be able to typedef GoComplex64 and GoComplex128 to the appropriate type in MSVC. It is important to note that this CL is not adding MSVC support to cgo. C compilers should still be GCC-compatible. This CL is about allowing to include, without further modifications, a DLL export header generated by cgo, normally using Mingw-W64 compiler, into a MSVC project. This was already possible if the export header changes introduced in this CL were done outside cgo, either manually or in a post-build script. Fixes #36233 1: https://docs.microsoft.com/en-us/cpp/c-runtime-library/complex-math-support 2: https://docs.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?c-standard-library-features-1 Change-Id: Iad8f26984b115c728e3b73f3a8334ade7a11cfa1 Reviewed-on: https://go-review.googlesource.com/c/go/+/397134 Reviewed-by: Ian Lance Taylor Trust: Cherry Mui Run-TryBot: Cherry Mui Auto-Submit: Cherry Mui TryBot-Result: Gopher Robot --- misc/cgo/testcshared/cshared_test.go | 49 +++++++++++++++++++ .../testdata/issue36233/issue36233.go | 29 +++++++++++ src/cmd/cgo/out.go | 42 +++++++++++----- 3 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 misc/cgo/testcshared/testdata/issue36233/issue36233.go diff --git a/misc/cgo/testcshared/cshared_test.go b/misc/cgo/testcshared/cshared_test.go index c9e9e5fe63e4a..e4898778be80e 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 0000000000000..d0d1e5d50ae37 --- /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/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 8ead173e64d86..adbb761e38871 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 From 3a19102de3435a3823126e74cf42c67037ff1890 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Fri, 8 Apr 2022 14:13:46 -0400 Subject: [PATCH 035/137] cmd/vendor: revert vendored code mistakenly modified in CL 398734 CL 398734 mistakenly modified cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go, which causes the cmd/internal/moddeps to (correctly) fail due to modified vendored code. This reverts the edit by re-running 'go mod vendor'. Fixes #52231. Change-Id: I1def3efe5d408464561e775feb0f9d34ff5fd0b3 Reviewed-on: https://go-review.googlesource.com/c/go/+/399154 Trust: Bryan Mills Run-TryBot: Bryan Mills Auto-Submit: Bryan Mills Reviewed-by: Heschi Kreinick TryBot-Result: Gopher Robot --- src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go b/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go index e30e41fe3398b..d42ab0f29441f 100644 --- a/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go +++ b/src/cmd/vendor/golang.org/x/arch/ppc64/ppc64asm/tables.go @@ -4037,9 +4037,9 @@ var instFormats = [...]instFormat{ [6]*argField{ap_VecReg_6_10, ap_VecReg_16_20}}, {XSADDSP, 0xfc0007f800000000, 0xf000000000000000, 0x0, // VSX Scalar Add Single-Precision XX3-form (xsaddsp XT,XA,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_29_29_11_15, ap_VecSReg_30_30_16_20}}, - {XSCVDPSPN, 0xfc0007fc00000000, 0xf000042c00000000, 0x1f000000000000, // VSX Scalar Convert Scalar Single-Precision to Vector Single-Precision format Non-signaling XX2-form (xscvdpspn XT,XB) + {XSCVDPSPN, 0xfc0007fc00000000, 0xf000042c00000000, 0x1f000000000000, // VSX Scalar Convert Scalar Single-Precision to Vector Single-Precision format Non-signalling XX2-form (xscvdpspn XT,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_30_30_16_20}}, - {XSCVSPDPN, 0xfc0007fc00000000, 0xf000052c00000000, 0x1f000000000000, // VSX Scalar Convert Single-Precision to Double-Precision format Non-signaling XX2-form (xscvspdpn XT,XB) + {XSCVSPDPN, 0xfc0007fc00000000, 0xf000052c00000000, 0x1f000000000000, // VSX Scalar Convert Single-Precision to Double-Precision format Non-signalling XX2-form (xscvspdpn XT,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_30_30_16_20}}, {XSCVSXDSP, 0xfc0007fc00000000, 0xf00004e000000000, 0x1f000000000000, // VSX Scalar Convert with round Signed Doubleword to Single-Precision format XX2-form (xscvsxdsp XT,XB) [6]*argField{ap_VecSReg_31_31_6_10, ap_VecSReg_30_30_16_20}}, From db576c9f3a33d043b4dc6cd8177f4e0b25ded8ec Mon Sep 17 00:00:00 2001 From: Johan Jansson Date: Fri, 1 Apr 2022 14:00:09 +0300 Subject: [PATCH 036/137] net/textproto: initialize commonHeader in canonicalMIMEHeaderKey Call initCommonHeader in canonicalMIMEHeaderKey to ensure that commonHeader is initialized before use. Remove all other calls to initCommonHeader, since commonHeader is only used in canonicalMIMEHeaderKey. This prevents a race condition: read of commonHeader before commonHeader has been initialized. Add regression test that triggers the race condition which can be detected by the race detector. Fixes #46363 Change-Id: I00c8c52c6f4c78c0305978c876142c1b388174af Reviewed-on: https://go-review.googlesource.com/c/go/+/397575 Trust: Brad Fitzpatrick Reviewed-by: Bryan Mills Trust: Bryan Mills Run-TryBot: Bryan Mills Auto-Submit: Bryan Mills TryBot-Result: Gopher Robot --- src/net/textproto/reader.go | 4 +--- src/net/textproto/reader_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go index ac47f007007a8..65974f9cc21ba 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} } @@ -579,8 +578,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++ { @@ -642,6 +639,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 3124d438fa57d..d11d40f1cf10b 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 From 0f0c89243044a5a5de142e51da3a98f082fd3771 Mon Sep 17 00:00:00 2001 From: "Paul E. Murphy" Date: Thu, 24 Mar 2022 11:24:01 -0500 Subject: [PATCH 037/137] crypto/aes: merge ppc64le crypt key expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is not necessary to expand the key twice for each direction, the decrypt key can be stored in reverse simultaneously. Likewise, there is no need to store the key length alongside the expanded keys, this is now inferred by the key length slice. Noteably, the key expansion benchmark assumes the key array size is the exact size of the expanded key. Now, the ppc64le aes asm interface is identical to the generic asm interface. Callsites and usage is updated to reflect this. Performance uplift on POWER9 is substantial: name old time/op new time/op delta Expand 167ns ± 0% 49ns ± 0% -70.55% Change-Id: I3fdaf9c27e8860e8150d4683eb4046d97a53293a Reviewed-on: https://go-review.googlesource.com/c/go/+/398894 Run-TryBot: Paul Murphy TryBot-Result: Gopher Robot Reviewed-by: Lynn Boger Trust: Paul Murphy --- src/crypto/aes/asm_ppc64le.s | 207 ++++++++++++------------------- src/crypto/aes/cbc_ppc64le.go | 6 +- src/crypto/aes/cipher_ppc64le.go | 43 ++++--- 3 files changed, 105 insertions(+), 151 deletions(-) diff --git a/src/crypto/aes/asm_ppc64le.s b/src/crypto/aes/asm_ppc64le.s index 192a8096cd1bc..647e8469b7017 100644 --- a/src/crypto/aes/asm_ppc64le.s +++ b/src/crypto/aes/asm_ppc64le.s @@ -22,13 +22,14 @@ #include "textflag.h" -// For set{En,De}cryptKeyAsm +// For expandKeyAsm #define INP R3 #define BITS R4 -#define OUT R5 +#define OUTENC R5 // Pointer to next expanded encrypt key #define PTR R6 #define CNT R7 #define ROUNDS R8 +#define OUTDEC R9 // Pointer to next expanded decrypt key #define TEMP R19 #define ZERO V0 #define IN0 V1 @@ -87,31 +88,13 @@ GLOBL ·rcon(SB), RODATA, $80 LXSDX (RA+RB), VT \ VPERM VT, VT, ESPERM, VT -// func setEncryptKeyAsm(key *byte, keylen int, enc *uint32) int -TEXT ·setEncryptKeyAsm(SB), NOSPLIT|NOFRAME, $0 +// func setEncryptKeyAsm(nr int, key *byte, enc *uint32, dec *uint32) +TEXT ·expandKeyAsm(SB), NOSPLIT|NOFRAME, $0 // Load the arguments inside the registers - MOVD key+0(FP), INP - MOVD keylen+8(FP), BITS - MOVD enc+16(FP), OUT - JMP ·doEncryptKeyAsm(SB) - -// This text is used both setEncryptKeyAsm and setDecryptKeyAsm -TEXT ·doEncryptKeyAsm(SB), NOSPLIT|NOFRAME, $0 - // Do not change R10 since it's storing the LR value in setDecryptKeyAsm - - // Check arguments - MOVD $-1, PTR // li 6,-1 exit code to -1 (255) - CMPU INP, $0 // cmpldi r3,0 input key pointer set? - BC 0x0E, 2, enc_key_abort // beq- .Lenc_key_abort - CMPU OUT, $0 // cmpldi r5,0 output key pointer set? - BC 0x0E, 2, enc_key_abort // beq- .Lenc_key_abort - MOVD $-2, PTR // li 6,-2 exit code to -2 (254) - CMPW BITS, $128 // cmpwi 4,128 greater or equal to 128 - BC 0x0E, 0, enc_key_abort // blt- .Lenc_key_abort - CMPW BITS, $256 // cmpwi 4,256 lesser or equal to 256 - BC 0x0E, 1, enc_key_abort // bgt- .Lenc_key_abort - ANDCC $0x3f, BITS, TEMP // andi. 0,4,0x3f multiple of 64 - BC 0x06, 2, enc_key_abort // bne- .Lenc_key_abort + MOVD nr+0(FP), ROUNDS + MOVD key+8(FP), INP + MOVD enc+16(FP), OUTENC + MOVD dec+24(FP), OUTDEC MOVD $·rcon(SB), PTR // PTR point to rcon addr LVX (PTR), ESPERM @@ -120,27 +103,34 @@ TEXT ·doEncryptKeyAsm(SB), NOSPLIT|NOFRAME, $0 // Get key from memory and write aligned into VR P8_LXVB16X(INP, R0, IN0) ADD $0x10, INP, INP - MOVD $0x20, R8 // li 8,0x20 R8 = 32 + MOVD $0x20, TEMP - CMPW BITS, $192 // cmpwi 4,192 Key size == 192? + CMPW ROUNDS, $12 LVX (PTR)(R0), RCON // lvx 4,0,6 Load first 16 bytes into RCON - LVX (PTR)(R8), MASK // lvx 5,8,6 + LVX (PTR)(TEMP), MASK ADD $0x10, PTR, PTR // addi 6,6,0x10 PTR to next 16 bytes of RCON MOVD $8, CNT // li 7,8 CNT = 8 VXOR ZERO, ZERO, ZERO // vxor 0,0,0 Zero to be zero :) MOVD CNT, CTR // mtctr 7 Set the counter to 8 (rounds) - BLT loop128 // blt .Loop128 - BEQ l192 // beq .L192 - JMP l256 // b .L256 + // The expanded decrypt key is the expanded encrypt key stored in reverse order. + // Move OUTDEC to the last key location, and store in descending order. + ADD $160, OUTDEC, OUTDEC + BLT loop128 + ADD $32, OUTDEC, OUTDEC + BEQ l192 + ADD $32, OUTDEC, OUTDEC + JMP l256 loop128: // Key schedule (Round 1 to 8) VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-splat VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - P8_STXV(IN0, R0, OUT) + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - ADD $16, OUT, OUT // addi 5,5,16 Point to the next round + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC VXOR IN0, TMP, IN0 // vxor 1,1,6 VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 @@ -156,9 +146,11 @@ loop128: // Key schedule (Round 9) VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-spat VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - P8_STXV(IN0, R0, OUT) + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - ADD $16, OUT, OUT // addi 5,5,16 + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC // Key schedule (Round 10) VXOR IN0, TMP, IN0 // vxor 1,1,6 @@ -171,9 +163,11 @@ loop128: VPERM IN0, IN0, MASK, KEY // vperm 3,1,1,5 Rotate-n-splat VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - P8_STXV(IN0, R0, OUT) + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - ADD $16, OUT, OUT // addi 5,5,16 + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC // Key schedule (Round 11) VXOR IN0, TMP, IN0 // vxor 1,1,6 @@ -182,18 +176,18 @@ loop128: VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 VXOR IN0, TMP, IN0 // vxor 1,1,6 VXOR IN0, KEY, IN0 // vxor 1,1,3 - P8_STXV(IN0, R0, OUT) + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) - ADD $0x50, OUT, OUT // addi 5,5,0x50 - - MOVD $10, ROUNDS // li 8,10 - JMP done // b .Ldone + RET l192: LXSDX_BE(INP, R0, IN1) // Load next 8 bytes into upper half of VSR in BE order. MOVD $4, CNT // li 7,4 - P8_STXV(IN0, R0, OUT) - ADD $16, OUT, OUT // addi 5,5,16 + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC VSPLTISB $8, KEY // vspltisb 3,8 MOVD CNT, CTR // mtctr 7 VSUBUBM MASK, KEY, MASK // vsububm 5,5,3 @@ -221,18 +215,22 @@ loop192: VPERM IN1, IN1, MASK, KEY // vperm 3,2,2,5 VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - P8_STXV(STAGE, R0, OUT) + P8_STXV(STAGE, R0, OUTENC) + P8_STXV(STAGE, R0, OUTDEC) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - ADD $16, OUT, OUT // addi 5,5,16 + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC VSLDOI $8, IN0, IN1, STAGE // vsldoi 7,1,2,8 VXOR IN0, TMP, IN0 // vxor 1,1,6 VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 - P8_STXV(STAGE, R0, OUT) + P8_STXV(STAGE, R0, OUTENC) + P8_STXV(STAGE, R0, OUTDEC) VXOR IN0, TMP, IN0 // vxor 1,1,6 VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 VXOR IN0, TMP, IN0 // vxor 1,1,6 - ADD $16, OUT, OUT // addi 5,5,16 + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC VSPLTW $3, IN0, TMP // vspltw 6,1,3 VXOR TMP, IN1, TMP // vxor 6,6,2 @@ -241,28 +239,31 @@ loop192: VXOR IN1, TMP, IN1 // vxor 2,2,6 VXOR IN0, KEY, IN0 // vxor 1,1,3 VXOR IN1, KEY, IN1 // vxor 2,2,3 - P8_STXV(IN0, R0, OUT) - ADD $16, OUT, OUT // addi 5,5,16 + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC BC 0x10, 0, loop192 // bdnz .Loop192 - MOVD $12, ROUNDS // li 8,12 - ADD $0x20, OUT, OUT // addi 5,5,0x20 - BR done // b .Ldone + RET l256: P8_LXVB16X(INP, R0, IN1) MOVD $7, CNT // li 7,7 - MOVD $14, ROUNDS // li 8,14 - P8_STXV(IN0, R0, OUT) - ADD $16, OUT, OUT // addi 5,5,16 + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC MOVD CNT, CTR // mtctr 7 loop256: VPERM IN1, IN1, MASK, KEY // vperm 3,2,2,5 VSLDOI $12, ZERO, IN0, TMP // vsldoi 6,0,1,12 - P8_STXV(IN1, R0, OUT) + P8_STXV(IN1, R0, OUTENC) + P8_STXV(IN1, R0, OUTDEC) VCIPHERLAST KEY, RCON, KEY // vcipherlast 3,3,4 - ADD $16, OUT, OUT // addi 5,5,16 + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC VXOR IN0, TMP, IN0 // vxor 1,1,6 VSLDOI $12, ZERO, TMP, TMP // vsldoi 6,0,6,12 @@ -271,8 +272,10 @@ loop256: VXOR IN0, TMP, IN0 // vxor 1,1,6 VADDUWM RCON, RCON, RCON // vadduwm 4,4,4 VXOR IN0, KEY, IN0 // vxor 1,1,3 - P8_STXV(IN0, R0, OUT) - ADD $16, OUT, OUT // addi 5,5,16 + P8_STXV(IN0, R0, OUTENC) + P8_STXV(IN0, R0, OUTDEC) + ADD $16, OUTENC, OUTENC + ADD $-16, OUTDEC, OUTDEC BC 0x12, 0, done // bdz .Ldone VSPLTW $3, IN0, KEY // vspltw 3,1,3 @@ -289,71 +292,16 @@ loop256: JMP loop256 // b .Loop256 done: - MOVD $0, PTR // li 6,0 set PTR to 0 (exit code 0) - MOVW ROUNDS, 0(OUT) // stw 8,0(5) - -enc_key_abort: - MOVD PTR, INP // mr 3,6 set exit code with PTR value - MOVD INP, ret+24(FP) // Put return value into the FP - RET // blr + RET -// func setDecryptKeyAsm(key *byte, keylen int, dec *uint32) int -TEXT ·setDecryptKeyAsm(SB), NOSPLIT|NOFRAME, $0 - // Load the arguments inside the registers - MOVD key+0(FP), INP - MOVD keylen+8(FP), BITS - MOVD dec+16(FP), OUT - - MOVD LR, R10 // mflr 10 - CALL ·doEncryptKeyAsm(SB) - MOVD R10, LR // mtlr 10 - - CMPW INP, $0 // cmpwi 3,0 exit 0 = ok - BC 0x06, 2, dec_key_abort // bne- .Ldec_key_abort - - // doEncryptKeyAsm set ROUNDS (R8) with the proper value for each mode - SLW $4, ROUNDS, CNT // slwi 7,8,4 - SUB $240, OUT, INP // subi 3,5,240 - SRW $1, ROUNDS, ROUNDS // srwi 8,8,1 - ADD R7, INP, OUT // add 5,3,7 - MOVD ROUNDS, CTR // mtctr 8 - - // dec_key will invert the key sequence in order to be used for decrypt -dec_key: - MOVWZ 0(INP), TEMP // lwz 0, 0(3) - MOVWZ 4(INP), R6 // lwz 6, 4(3) - MOVWZ 8(INP), R7 // lwz 7, 8(3) - MOVWZ 12(INP), R8 // lwz 8, 12(3) - ADD $16, INP, INP // addi 3,3,16 - MOVWZ 0(OUT), R9 // lwz 9, 0(5) - MOVWZ 4(OUT), R10 // lwz 10,4(5) - MOVWZ 8(OUT), R11 // lwz 11,8(5) - MOVWZ 12(OUT), R12 // lwz 12,12(5) - MOVW TEMP, 0(OUT) // stw 0, 0(5) - MOVW R6, 4(OUT) // stw 6, 4(5) - MOVW R7, 8(OUT) // stw 7, 8(5) - MOVW R8, 12(OUT) // stw 8, 12(5) - SUB $16, OUT, OUT // subi 5,5,16 - MOVW R9, -16(INP) // stw 9, -16(3) - MOVW R10, -12(INP) // stw 10,-12(3) - MOVW R11, -8(INP) // stw 11,-8(3) - MOVW R12, -4(INP) // stw 12,-4(3) - BC 0x10, 0, dec_key // bdnz .Ldeckey - - XOR R3, R3, R3 // xor 3,3,3 Clean R3 - -dec_key_abort: - MOVD R3, ret+24(FP) // Put return value into the FP - RET // blr - -// func encryptBlockAsm(dst, src *byte, enc *uint32) +// func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) TEXT ·encryptBlockAsm(SB), NOSPLIT|NOFRAME, $0 // Load the arguments inside the registers - MOVD dst+0(FP), BLK_OUT - MOVD src+8(FP), BLK_INP - MOVD enc+16(FP), BLK_KEY + MOVD nr+0(FP), BLK_ROUNDS + MOVD xk+8(FP), BLK_KEY + MOVD dst+16(FP), BLK_OUT + MOVD src+24(FP), BLK_INP - MOVWZ 240(BLK_KEY), BLK_ROUNDS // lwz 6,240(5) MOVD $15, BLK_IDX // li 7,15 LVX (BLK_INP)(R0), ZERO // lvx 0,0,3 @@ -410,14 +358,14 @@ loop_enc: RET // blr -// func decryptBlockAsm(dst, src *byte, dec *uint32) +// func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) TEXT ·decryptBlockAsm(SB), NOSPLIT|NOFRAME, $0 // Load the arguments inside the registers - MOVD dst+0(FP), BLK_OUT - MOVD src+8(FP), BLK_INP - MOVD dec+16(FP), BLK_KEY + MOVD nr+0(FP), BLK_ROUNDS + MOVD xk+8(FP), BLK_KEY + MOVD dst+16(FP), BLK_OUT + MOVD src+24(FP), BLK_INP - MOVWZ 240(BLK_KEY), BLK_ROUNDS // lwz 6,240(5) MOVD $15, BLK_IDX // li 7,15 LVX (BLK_INP)(R0), ZERO // lvx 0,0,3 @@ -476,7 +424,7 @@ loop_dec: // Remove defines from above so they can be defined here #undef INP -#undef OUT +#undef OUTENC #undef ROUNDS #undef KEY #undef TMP @@ -545,6 +493,7 @@ loop_dec: // for decryption which was omitted to avoid the // complexity. +// func cryptBlocksChain(src, dst *byte, length int, key *uint32, iv *byte, enc int, nr int) TEXT ·cryptBlocksChain(SB), NOSPLIT|NOFRAME, $0 MOVD src+0(FP), INP MOVD dst+8(FP), OUT @@ -552,6 +501,7 @@ TEXT ·cryptBlocksChain(SB), NOSPLIT|NOFRAME, $0 MOVD key+24(FP), KEY MOVD iv+32(FP), IVP MOVD enc+40(FP), ENC + MOVD nr+48(FP), ROUNDS CMPU LEN, $16 // cmpldi r5,16 BC 14, 0, LR // bltlr- @@ -567,7 +517,6 @@ TEXT ·cryptBlocksChain(SB), NOSPLIT|NOFRAME, $0 VPERM IVEC, INPTAIL, INPPERM, IVEC // vperm v4,v4,v5,v6 NEG INP, R11 // neg r11,r3 LVSR (KEY)(R0), KEYPERM // lvsr v10,r0,r6 - MOVWZ 240(KEY), ROUNDS // lwz r9,240(r6) LVSR (R11)(R0), V6 // lvsr v6,r0,r11 LVX (INP)(R0), INPTAIL // lvx v5,r0,r3 ADD $15, INP // addi r3,r3,15 diff --git a/src/crypto/aes/cbc_ppc64le.go b/src/crypto/aes/cbc_ppc64le.go index fa8a430ed43b4..cb9ff4c84397d 100644 --- a/src/crypto/aes/cbc_ppc64le.go +++ b/src/crypto/aes/cbc_ppc64le.go @@ -42,7 +42,7 @@ func (x *cbc) BlockSize() int { return BlockSize } // cryptBlocksChain invokes the cipher message identifying encrypt or decrypt. //go:noescape -func cryptBlocksChain(src, dst *byte, length int, key *uint32, iv *byte, enc int) +func cryptBlocksChain(src, dst *byte, length int, key *uint32, iv *byte, enc int, nr int) func (x *cbc) CryptBlocks(dst, src []byte) { if len(src)%BlockSize != 0 { @@ -56,9 +56,9 @@ func (x *cbc) CryptBlocks(dst, src []byte) { } if len(src) > 0 { if x.enc == cbcEncrypt { - cryptBlocksChain(&src[0], &dst[0], len(src), &x.b.enc[0], &x.iv[0], x.enc) + cryptBlocksChain(&src[0], &dst[0], len(src), &x.b.enc[0], &x.iv[0], x.enc, len(x.b.enc)/4-1) } else { - cryptBlocksChain(&src[0], &dst[0], len(src), &x.b.dec[0], &x.iv[0], x.enc) + cryptBlocksChain(&src[0], &dst[0], len(src), &x.b.dec[0], &x.iv[0], x.enc, len(x.b.dec)/4-1) } } } diff --git a/src/crypto/aes/cipher_ppc64le.go b/src/crypto/aes/cipher_ppc64le.go index b788ea7d475fa..18615148fd5b6 100644 --- a/src/crypto/aes/cipher_ppc64le.go +++ b/src/crypto/aes/cipher_ppc64le.go @@ -12,37 +12,36 @@ import ( // defined in asm_ppc64le.s //go:noescape -func setEncryptKeyAsm(key *byte, keylen int, enc *uint32) int +func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32) //go:noescape -func setDecryptKeyAsm(key *byte, keylen int, dec *uint32) int +func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) //go:noescape -func doEncryptKeyAsm(key *byte, keylen int, dec *uint32) int - -//go:noescape -func encryptBlockAsm(dst, src *byte, enc *uint32) - -//go:noescape -func decryptBlockAsm(dst, src *byte, dec *uint32) +func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) type aesCipherAsm struct { aesCipher } func newCipher(key []byte) (cipher.Block, error) { - n := 64 // size is fixed for all and round value is stored inside it too + n := len(key) + 28 c := aesCipherAsm{aesCipher{make([]uint32, n), make([]uint32, n)}} k := len(key) - ret := 0 - ret += setEncryptKeyAsm(&key[0], k*8, &c.enc[0]) - ret += setDecryptKeyAsm(&key[0], k*8, &c.dec[0]) - - if ret > 0 { + var rounds int + switch len(key) { + case 128 / 8: + rounds = 10 + case 192 / 8: + rounds = 12 + case 256 / 8: + rounds = 14 + default: return nil, KeySizeError(k) } + expandKeyAsm(rounds, &key[0], &c.enc[0], &c.dec[0]) return &c, nil } @@ -58,7 +57,7 @@ func (c *aesCipherAsm) Encrypt(dst, src []byte) { if subtle.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { panic("crypto/aes: invalid buffer overlap") } - encryptBlockAsm(&dst[0], &src[0], &c.enc[0]) + encryptBlockAsm(len(c.enc)/4-1, &c.enc[0], &dst[0], &src[0]) } func (c *aesCipherAsm) Decrypt(dst, src []byte) { @@ -71,12 +70,18 @@ func (c *aesCipherAsm) Decrypt(dst, src []byte) { if subtle.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { panic("crypto/aes: invalid buffer overlap") } - decryptBlockAsm(&dst[0], &src[0], &c.dec[0]) + decryptBlockAsm(len(c.dec)/4-1, &c.dec[0], &dst[0], &src[0]) } // expandKey is used by BenchmarkExpand to ensure that the asm implementation // of key expansion is used for the benchmark when it is available. func expandKey(key []byte, enc, dec []uint32) { - setEncryptKeyAsm(&key[0], len(key)*8, &enc[0]) - setDecryptKeyAsm(&key[0], len(key)*8, &dec[0]) + rounds := 10 // rounds needed for AES128 + switch len(key) { + case 192 / 8: + rounds = 12 + case 256 / 8: + rounds = 14 + } + expandKeyAsm(rounds, &key[0], &enc[0], &dec[0]) } From db7183ccf9e7c245872864833e78a469e8747031 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 8 Apr 2022 16:23:35 -0700 Subject: [PATCH 038/137] go/build: remove unused fileInfo.embedErr field Change-Id: If86a0402dae32c57d07545ee6d818010e0e4b5ee Reviewed-on: https://go-review.googlesource.com/c/go/+/399255 Trust: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Dmitri Shuralyov --- src/go/build/build.go | 1 - src/go/build/read.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/go/build/build.go b/src/go/build/build.go index 2666b8acb78c8..3b09cce84b692 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -1380,7 +1380,6 @@ type fileInfo struct { parseErr error imports []fileImport embeds []fileEmbed - embedErr error } type fileImport struct { diff --git a/src/go/build/read.go b/src/go/build/read.go index de5c33a4f849c..52adfeab9ae14 100644 --- a/src/go/build/read.go +++ b/src/go/build/read.go @@ -390,7 +390,7 @@ func readComments(f io.Reader) ([]byte, error) { // readGoInfo expects a Go file as input and reads the file up to and including the import section. // It records what it learned in *info. // If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr, -// info.imports, info.embeds, and info.embedErr. +// info.imports and info.embeds. // // It only returns an error if there are problems reading the file, // not for syntax errors in the file itself. From 8cd6aacf81ad0a759ac895bce0b569b47aa4201c Mon Sep 17 00:00:00 2001 From: cia-rana Date: Sat, 9 Apr 2022 15:37:11 +0900 Subject: [PATCH 039/137] runtime: fix URL in a comment For various reasons Intel has suspended viewing web pages in the .ru domain, so change the domain of the documents cited in the code to the .com domain. In addition, the chapter numbers in the document were updated and fix it. Change-Id: I718be1548ec46f05ebc4f73873d4635c1d5fc76d Reviewed-on: https://go-review.googlesource.com/c/go/+/399060 Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Emmanuel Odeke Auto-Submit: Emmanuel Odeke --- src/runtime/memmove_amd64.s | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/memmove_amd64.s b/src/runtime/memmove_amd64.s index eeb5033fd9556..018bb0b19d570 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 From a10f158d6fdac7c6130b816a00ff14cc22619d2e Mon Sep 17 00:00:00 2001 From: Leonard Wang Date: Sun, 10 Apr 2022 21:57:39 +0800 Subject: [PATCH 040/137] runtime: update description of GODEBUG=gctrace=1 For #44167. Change-Id: I2dcd13cbe74e88de00e9fc51f9bd86e604a167df Reviewed-on: https://go-review.googlesource.com/c/go/+/399300 Reviewed-by: Michael Knyszek Reviewed-by: Emmanuel Odeke Run-TryBot: Emmanuel Odeke Auto-Submit: Emmanuel Odeke TryBot-Result: Gopher Robot --- src/runtime/extern.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/runtime/extern.go b/src/runtime/extern.go index f1f6ea51231fe..39bdd09849e59 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -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 From b6fb3af6af9835962ce1de1e1afcaa46726a654e Mon Sep 17 00:00:00 2001 From: Meng Zhuo Date: Wed, 24 Nov 2021 16:33:14 +0800 Subject: [PATCH 041/137] archive/zip: fail fast if UncompressedSize64 < nread The zip reader checks that the uncompressed file size is valid after all compressed files read until EOF. However in between reading each file, there could have already been an overflow where nread > UncompressedSize64 hence this change will now return ErrFormat in such situations. Fixes #49791 Change-Id: If3584a57d173de6a97bf35c07d2a99ff6972f820 Reviewed-on: https://go-review.googlesource.com/c/go/+/366854 Trust: mzh Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Emmanuel Odeke Trust: Emmanuel Odeke --- src/archive/zip/reader.go | 3 +++ src/archive/zip/reader_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/archive/zip/reader.go b/src/archive/zip/reader.go index 92fd6f6a92525..b4f6a8d714901 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 9bc23642c0e66..fd0a171304ce1 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) + } + }) + } +} From a6f6932b3ee87d9607ce246228e23f9a08dacc31 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Sun, 10 Apr 2022 09:12:43 -0700 Subject: [PATCH 042/137] cmd/asm: fix MOVK when constant has high bit set Fixes #52261 Change-Id: I1dc4c19c95a91f9e1e99d1e74afeb69f5bf8a979 Reviewed-on: https://go-review.googlesource.com/c/go/+/399455 Trust: Keith Randall Run-TryBot: Keith Randall TryBot-Result: Gopher Robot Auto-Submit: Keith Randall Reviewed-by: Eric Fang --- src/cmd/internal/obj/arm64/asm7.go | 2 +- src/cmd/internal/obj/arm64/asm_arm64_test.go | 11 +++++++++++ src/cmd/internal/obj/arm64/asm_arm64_test.s | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/cmd/internal/obj/arm64/asm7.go b/src/cmd/internal/obj/arm64/asm7.go index 72c4cd48ed092..57d4e7a8d32dd 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 c6a00f5b94103..f468b6b0fe97b 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 9d337a4fd1a86..f85433c6e3ca3 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 From 615d3c304077f1f3ca249151fb87d7d7a802cab2 Mon Sep 17 00:00:00 2001 From: Wayne Zuo Date: Mon, 4 Apr 2022 16:32:29 +0800 Subject: [PATCH 043/137] test: adjust load and store test In the load tests, we only want to test the assembly produced by the load operations. If we use the global variable sink, it will produce one load operation and one store operation(assign to sink). For example: func load_be64(b []byte) uint64 { sink64 = binary.BigEndian.Uint64(b) } If we compile this function with GOAMD64=v3, it may produce MOVBEQload and MOVQstore or MOVQload and MOVBEQstore, but we only want MOVBEQload. Discovered when developing CL 395474. Same for the store tests. Change-Id: I65c3c742f1eff657c3a0d2dd103f51140ae8079e Reviewed-on: https://go-review.googlesource.com/c/go/+/397875 Reviewed-by: Keith Randall Trust: Cherry Mui --- test/codegen/memcombine.go | 92 ++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go index ad42538dcdba8..0d4e96f862ccc 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` // 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` // 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` // 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` // 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` // 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) { From 32de2b0d1cf4d60119d7c717acec2163342392b9 Mon Sep 17 00:00:00 2001 From: Wayne Zuo Date: Thu, 24 Mar 2022 22:53:41 +0800 Subject: [PATCH 044/137] cmd/compile: add MOVBE index load/store Fixes #51724 Change-Id: I94e650a7482dc4c479d597f0162a6a89d779708d Reviewed-on: https://go-review.googlesource.com/c/go/+/395474 Reviewed-by: Keith Randall Trust: Cherry Mui --- src/cmd/compile/internal/amd64/ssa.go | 6 +- .../compile/internal/ssa/addressingmodes.go | 20 ++ src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 14 ++ src/cmd/compile/internal/ssa/opGen.go | 207 ++++++++++++++++++ test/codegen/memcombine.go | 10 +- 5 files changed, 250 insertions(+), 7 deletions(-) diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index d34fdc611b6a0..9628ce5644022 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -786,7 +786,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 +809,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() diff --git a/src/cmd/compile/internal/ssa/addressingmodes.go b/src/cmd/compile/internal/ssa/addressingmodes.go index 28fa86cd643c7..d600e31666eb7 100644 --- a/src/cmd/compile/internal/ssa/addressingmodes.go +++ b/src/cmd/compile/internal/ssa/addressingmodes.go @@ -356,6 +356,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/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index d760d7d79e340..ab84504d1ab99 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -937,6 +937,20 @@ 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: "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 diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 005a033a409d5..1c941e84e17f6 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1050,6 +1050,18 @@ const ( OpAMD64MOVBELstore OpAMD64MOVBEQload OpAMD64MOVBEQstore + OpAMD64MOVBELloadidx1 + OpAMD64MOVBELloadidx4 + OpAMD64MOVBELloadidx8 + OpAMD64MOVBEQloadidx1 + OpAMD64MOVBEQloadidx8 + OpAMD64MOVBEWstoreidx1 + OpAMD64MOVBEWstoreidx2 + OpAMD64MOVBELstoreidx1 + OpAMD64MOVBELstoreidx4 + OpAMD64MOVBELstoreidx8 + OpAMD64MOVBEQstoreidx1 + OpAMD64MOVBEQstoreidx8 OpAMD64SHLXLload OpAMD64SHLXQload OpAMD64SHRXLload @@ -13910,6 +13922,201 @@ 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: "SHLXLload", auxType: auxSymOff, diff --git a/test/codegen/memcombine.go b/test/codegen/memcombine.go index 0d4e96f862ccc..0292d7f0f3eb4 100644 --- a/test/codegen/memcombine.go +++ b/test/codegen/memcombine.go @@ -76,7 +76,7 @@ func load_be64(b []byte) uint64 { 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` @@ -94,7 +94,7 @@ func load_be32(b []byte) uint32 { 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` @@ -421,7 +421,7 @@ func store_be64(b []byte, x uint64) { 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` @@ -451,7 +451,7 @@ func store_be32_load(b, x *[8]byte) { 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` @@ -469,7 +469,7 @@ func store_be16(b []byte, x uint16) { 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` From 494b79f39ab8e7b6f096b6c6088bd84bb8592dc4 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 07:55:25 -0400 Subject: [PATCH 045/137] go/doc/comment: add data structures [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement just the data structures of the new API for parsing and printing doc comments, as well as a syntax tree form for inspecting and manipulating them. The API itself was discussed and accepted as part of the proposal process in #51082. For #51082. Change-Id: Iae7fbc85705964585273b970c5c62e394feb1288 Reviewed-on: https://go-review.googlesource.com/c/go/+/397276 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- api/next/51082.txt | 35 ++++++++ src/go/build/deps_test.go | 4 +- src/go/doc/comment/parse.go | 169 ++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 api/next/51082.txt create mode 100644 src/go/doc/comment/parse.go diff --git a/api/next/51082.txt b/api/next/51082.txt new file mode 100644 index 0000000000000..2127d2ee24aae --- /dev/null +++ b/api/next/51082.txt @@ -0,0 +1,35 @@ +pkg go/doc/comment, method (*List) BlankBefore() bool #51082 +pkg go/doc/comment, method (*List) BlankBetween() bool #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 Plain string #51082 +pkg go/doc/comment, type Text interface, unexported methods #51082 diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 052e7ad9c0d82..7117e08c3bfcb 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -288,9 +288,9 @@ var depsRules = ` < go/parser; FMT - < go/build/constraint; + < go/build/constraint, go/doc/comment; - go/build/constraint, go/parser, text/tabwriter + go/build/constraint, go/doc/comment, go/parser, text/tabwriter < go/printer < go/format; diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go new file mode 100644 index 0000000000000..672b115bf8f8c --- /dev/null +++ b/src/go/doc/comment/parse.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 + +// 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() {} From 7575811c2b7c4a4a06a1e4b93c2473dffbb8bdcf Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 08:00:55 -0400 Subject: [PATCH 046/137] go/doc/comment: add low-level parsing helpers [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement helpers to recognize old-style headings, plain text (not marked up) URLs, and Go identifiers. For #51082. Change-Id: Ibabce72ef3ffd79a9d33366091f8c76ef27d0182 Reviewed-on: https://go-review.googlesource.com/c/go/+/397277 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/go/doc/comment/old_test.go | 80 +++++++++ src/go/doc/comment/parse.go | 294 +++++++++++++++++++++++++++++++++ 2 files changed, 374 insertions(+) create mode 100644 src/go/doc/comment/old_test.go diff --git a/src/go/doc/comment/old_test.go b/src/go/doc/comment/old_test.go new file mode 100644 index 0000000000000..944f94d16d9ae --- /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 index 672b115bf8f8c..12b6679413562 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -4,6 +4,12 @@ package comment +import ( + "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. @@ -167,3 +173,291 @@ type DocLink struct { } func (*DocLink) text() {} + +// 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 +} + +// 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) + 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 { + const mask = 0 | + (1<<26-1)<<'A' | + (1<<26-1)<<'a' | + (1<<10-1)<<'0' | + 1<<'_' + + return ((uint64(1)<>64)) != 0 +} From 98b17892a080a42378c9c3551dc5bb273e7ec2d3 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 08:15:40 -0400 Subject: [PATCH 047/137] go/doc/comment: add paragraph parsing and test framework [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement parsing of plain text doc paragraphs, as well as a txtar-based test framework. Subsequent CLs will implement the rest of the possible markup. For #51082. Change-Id: I449aac69b44089f241fde8050ac134e17cb25116 Reviewed-on: https://go-review.googlesource.com/c/go/+/397278 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- api/next/51082.txt | 5 + src/go/doc/comment/parse.go | 167 +++++++++++++++++++++++++ src/go/doc/comment/testdata/hello.txt | 16 +++ src/go/doc/comment/testdata_test.go | 168 ++++++++++++++++++++++++++ 4 files changed, 356 insertions(+) create mode 100644 src/go/doc/comment/testdata/hello.txt create mode 100644 src/go/doc/comment/testdata_test.go diff --git a/api/next/51082.txt b/api/next/51082.txt index 2127d2ee24aae..2cafd4b5337be 100644 --- a/api/next/51082.txt +++ b/api/next/51082.txt @@ -1,5 +1,6 @@ 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, type Block interface, unexported methods #51082 pkg go/doc/comment, type Code struct #51082 pkg go/doc/comment, type Code struct, Text string #51082 @@ -31,5 +32,9 @@ 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 Text interface, unexported methods #51082 diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index 12b6679413562..b12a0d84b95d0 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -174,6 +174,152 @@ type DocLink struct { 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 +} + +// 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. + for len(lines) > 0 { + line := lines[0] + if line != "" { + var b Block + b, lines = d.paragraph(lines) + d.Content = append(d.Content, b) + } else { + lines = lines[1:] + } + } + + // Second pass: interpret all the Plain text now that we know the links. + // TODO: Actually interpret the plain text. + + 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 @@ -234,6 +380,27 @@ func isOldHeading(line string, all []string, off int) bool { return true } +// parargraph 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) { + // TODO: Paragraph should be interrupted by any indented line, + // which is either a list or a code block, + // and of course by a blank line. + // It should not be interrupted by a # line - headings must stand alone. + i := 0 + for i < len(lines) && lines[i] != "" { + i++ + } + lines, rest = lines[:i], lines[i:] + if len(lines) == 0 { + return nil, rest + } + + return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}}, rest +} + // 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. diff --git a/src/go/doc/comment/testdata/hello.txt b/src/go/doc/comment/testdata/hello.txt new file mode 100644 index 0000000000000..4f669fc363b59 --- /dev/null +++ b/src/go/doc/comment/testdata/hello.txt @@ -0,0 +1,16 @@ +-- input -- +Hello, +world + +This is +a test. +-- dump -- +Doc + Paragraph + Plain + "Hello,\n" + "world" + Paragraph + Plain + "This is\n" + "a test." diff --git a/src/go/doc/comment/testdata_test.go b/src/go/doc/comment/testdata_test.go new file mode 100644 index 0000000000000..a94e76ca029ff --- /dev/null +++ b/src/go/doc/comment/testdata_test.go @@ -0,0 +1,168 @@ +// 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" + "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 + + 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) { + a, err := txtar.ParseFile(file) + if err != nil { + t.Fatal(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) + } + 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') + } +} From 6130b88130ac6954f557e4737d88419d063b32c3 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 08:20:31 -0400 Subject: [PATCH 048/137] go/doc/comment: add Printer and basic comment printing [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement printing of plain text doc paragraphs. For #51082. Change-Id: Ieff0af64a900f566bfc833c3b5706488f1444798 Reviewed-on: https://go-review.googlesource.com/c/go/+/397279 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- api/next/51082.txt | 12 +++ src/go/doc/comment/html.go | 81 ++++++++++++++++ src/go/doc/comment/markdown.go | 115 +++++++++++++++++++++++ src/go/doc/comment/print.go | 122 +++++++++++++++++++++++++ src/go/doc/comment/testdata/blank.txt | 12 +++ src/go/doc/comment/testdata/escape.txt | 55 +++++++++++ src/go/doc/comment/testdata/hello.txt | 29 +++++- src/go/doc/comment/testdata_test.go | 9 ++ src/go/doc/comment/text.go | 69 ++++++++++++++ 9 files changed, 500 insertions(+), 4 deletions(-) create mode 100644 src/go/doc/comment/html.go create mode 100644 src/go/doc/comment/markdown.go create mode 100644 src/go/doc/comment/print.go create mode 100644 src/go/doc/comment/testdata/blank.txt create mode 100644 src/go/doc/comment/testdata/escape.txt create mode 100644 src/go/doc/comment/text.go diff --git a/api/next/51082.txt b/api/next/51082.txt index 2cafd4b5337be..e078547b55864 100644 --- a/api/next/51082.txt +++ b/api/next/51082.txt @@ -1,6 +1,10 @@ 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 @@ -37,4 +41,12 @@ pkg go/doc/comment, type Parser struct, LookupPackage func(string) (string, bool 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/src/go/doc/comment/html.go b/src/go/doc/comment/html.go new file mode 100644 index 0000000000000..d41e36cefe58f --- /dev/null +++ b/src/go/doc/comment/html.go @@ -0,0 +1,81 @@ +// 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" +) + +// An htmlPrinter holds the state needed for printing a Doc as HTML. +type htmlPrinter struct { + *Printer +} + +// 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: + out.WriteString("

") + p.text(out, x.Text) + out.WriteString("\n") + } +} + +// 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)) + } + } +} + +// 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 0000000000000..888868e1307d5 --- /dev/null +++ b/src/go/doc/comment/markdown.go @@ -0,0 +1,115 @@ +// 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" +) + +// An mdPrinter holds the state needed for printing a Doc as Markdown. +type mdPrinter struct { + *Printer + 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} + + 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") + } +} + +// 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)) + } + } +} + +// 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/print.go b/src/go/doc/comment/print.go new file mode 100644 index 0000000000000..b1e509d1b638c --- /dev/null +++ b/src/go/doc/comment/print.go @@ -0,0 +1,122 @@ +// 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 +} + +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) + } + + 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") + } +} + +// 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)) + } + } +} + +// 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 + } +} \ No newline at end of file diff --git a/src/go/doc/comment/testdata/blank.txt b/src/go/doc/comment/testdata/blank.txt new file mode 100644 index 0000000000000..9049fde76ed38 --- /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/escape.txt b/src/go/doc/comment/testdata/escape.txt new file mode 100644 index 0000000000000..f54663f5c3ea5 --- /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/hello.txt b/src/go/doc/comment/testdata/hello.txt index 4f669fc363b59..b998c77fef7e4 100644 --- a/src/go/doc/comment/testdata/hello.txt +++ b/src/go/doc/comment/testdata/hello.txt @@ -1,9 +1,9 @@ -- input -- -Hello, -world + Hello, + world -This is -a test. + This is + a test. -- dump -- Doc Paragraph @@ -14,3 +14,24 @@ Doc 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_test.go b/src/go/doc/comment/testdata_test.go index a94e76ca029ff..d00b6364428ac 100644 --- a/src/go/doc/comment/testdata_test.go +++ b/src/go/doc/comment/testdata_test.go @@ -44,11 +44,20 @@ func TestTestdata(t *testing.T) { want = want[:len(want)-1] } var out []byte + var pr Printer 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)) diff --git a/src/go/doc/comment/text.go b/src/go/doc/comment/text.go new file mode 100644 index 0000000000000..2e755674640a8 --- /dev/null +++ b/src/go/doc/comment/text.go @@ -0,0 +1,69 @@ +// 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" +) + +// A textPrinter holds the state needed for printing a Doc as plain text. +type textPrinter struct { + *Printer +} + +// 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, + } + var out bytes.Buffer + for i, x := range d.Content { + if i > 0 && blankBefore(x) { + writeNL(&out) + } + tp.block(&out, x) + } + 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: + p.text(out, x.Text) + } +} + +// text prints the text sequence x to out. +// TODO: Wrap lines. +func (p *textPrinter) text(out *bytes.Buffer, x []Text) { + for _, t := range x { + switch t := t.(type) { + case Plain: + out.WriteString(string(t)) + } + } + writeNL(out) +} From ae3d890202b2356fc0936f84349bdf08083884ac Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 08:59:56 -0400 Subject: [PATCH 049/137] go/doc/comment: parse and print identifiers, automatic links [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement parsing and printing of unmarked identifiers and automatic URL links in plain text. For #51082. Change-Id: Ib83ad482937501a6fc14fa788eab289533a68e3a Reviewed-on: https://go-review.googlesource.com/c/go/+/397280 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/go/doc/comment/html.go | 10 ++++ src/go/doc/comment/markdown.go | 10 ++++ src/go/doc/comment/parse.go | 75 ++++++++++++++++++++++++++- src/go/doc/comment/print.go | 4 ++ src/go/doc/comment/testdata/hello.txt | 6 +-- src/go/doc/comment/testdata/link.txt | 17 ++++++ src/go/doc/comment/testdata/para.txt | 17 ++++++ src/go/doc/comment/testdata/text2.txt | 14 +++++ src/go/doc/comment/testdata/words.txt | 10 ++++ src/go/doc/comment/testdata_test.go | 13 ++++- src/go/doc/comment/text.go | 16 +++++- 11 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 src/go/doc/comment/testdata/link.txt create mode 100644 src/go/doc/comment/testdata/para.txt create mode 100644 src/go/doc/comment/testdata/text2.txt create mode 100644 src/go/doc/comment/testdata/words.txt diff --git a/src/go/doc/comment/html.go b/src/go/doc/comment/html.go index d41e36cefe58f..a7c3ff2a5532d 100644 --- a/src/go/doc/comment/html.go +++ b/src/go/doc/comment/html.go @@ -44,6 +44,16 @@ func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) { 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("") } } } diff --git a/src/go/doc/comment/markdown.go b/src/go/doc/comment/markdown.go index 888868e1307d5..bdfdcdf565ed2 100644 --- a/src/go/doc/comment/markdown.go +++ b/src/go/doc/comment/markdown.go @@ -77,6 +77,16 @@ func (p *mdPrinter) rawText(out *bytes.Buffer, x []Text) { 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(")") } } } diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index b12a0d84b95d0..7c7b69966d668 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -261,7 +261,12 @@ func (p *Parser) Parse(text string) *Doc { } // Second pass: interpret all the Plain text now that we know the links. - // TODO: Actually interpret the plain text. + for _, b := range d.Content { + switch b := b.(type) { + case *Paragraph: + b.Text = d.parseText(string(b.Text[0].(Plain))) + } + } return d.Doc } @@ -401,6 +406,74 @@ func (d *parseDoc) paragraph(lines []string) (b Block, rest []string) { return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}}, rest } +// parseText parses s as text and returns the parsed Text elements. +func (d *parseDoc) parseText(s string) []Text { + var out []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:] + const autoLink = true + 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. diff --git a/src/go/doc/comment/print.go b/src/go/doc/comment/print.go index b1e509d1b638c..6c8782c802fad 100644 --- a/src/go/doc/comment/print.go +++ b/src/go/doc/comment/print.go @@ -103,6 +103,10 @@ func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { switch t := t.(type) { case Plain: p.indent(out, indent, string(t)) + case Italic: + p.indent(out, indent, string(t)) + case *Link: + p.text(out, indent, t.Text) } } } diff --git a/src/go/doc/comment/testdata/hello.txt b/src/go/doc/comment/testdata/hello.txt index b998c77fef7e4..fb07f1eb7575d 100644 --- a/src/go/doc/comment/testdata/hello.txt +++ b/src/go/doc/comment/testdata/hello.txt @@ -30,8 +30,6 @@ Hello, world This is a test. -- text -- -Hello, -world +Hello, world -This is -a test. +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 0000000000000..551e3065ce79a --- /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/para.txt b/src/go/doc/comment/testdata/para.txt new file mode 100644 index 0000000000000..2355fa8172a95 --- /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/text2.txt b/src/go/doc/comment/testdata/text2.txt new file mode 100644 index 0000000000000..a099d0b8c6860 --- /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/words.txt b/src/go/doc/comment/testdata/words.txt new file mode 100644 index 0000000000000..63c7e1a1b2b44 --- /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 index d00b6364428ac..43687c5d4ea69 100644 --- a/src/go/doc/comment/testdata_test.go +++ b/src/go/doc/comment/testdata_test.go @@ -6,6 +6,7 @@ package comment import ( "bytes" + "encoding/json" "fmt" "internal/diff" "internal/txtar" @@ -20,6 +21,10 @@ func TestTestdata(t *testing.T) { t.Fatalf("no testdata") } var p Parser + p.Words = map[string]string{ + "italicword": "", + "linkedword": "https://example.com/linkedword", + } stripDollars := func(b []byte) []byte { // Remove trailing $ on lines. @@ -30,10 +35,17 @@ func TestTestdata(t *testing.T) { } 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") } @@ -44,7 +56,6 @@ func TestTestdata(t *testing.T) { want = want[:len(want)-1] } var out []byte - var pr Printer switch f.Name { default: t.Fatalf("unknown output file %q", f.Name) diff --git a/src/go/doc/comment/text.go b/src/go/doc/comment/text.go index 2e755674640a8..768cef32fb237 100644 --- a/src/go/doc/comment/text.go +++ b/src/go/doc/comment/text.go @@ -7,11 +7,13 @@ package comment import ( "bytes" "fmt" + "strings" ) // A textPrinter holds the state needed for printing a Doc as plain text. type textPrinter struct { *Printer + long bytes.Buffer } // Text returns a textual formatting of the Doc. @@ -59,11 +61,23 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) { // text prints the text sequence x to out. // TODO: Wrap lines. func (p *textPrinter) text(out *bytes.Buffer, x []Text) { + p.oneLongLine(&p.long, x) + out.WriteString(strings.ReplaceAll(p.long.String(), "\n", " ")) + p.long.Reset() + writeNL(out) +} + +// oneLongLine prints the text sequence x to out as one long line, +// without worrying about line wrapping. +func (p *textPrinter) oneLongLine(out *bytes.Buffer, 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) } } - writeNL(out) } From 910a33a0eed723696dc808d7801032cd3c035a6d Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 5 Apr 2022 14:12:41 -0400 Subject: [PATCH 050/137] go/doc/comment: parse and print doc links [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement parsing and printing of documentation links, like [math.Sqrt] or [*golang.org/x/text/runes.Set]. For #51082. Change-Id: I1cc73afbac1c6568773f921ce4b73e52f17acef6 Reviewed-on: https://go-review.googlesource.com/c/go/+/397281 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- api/next/51082.txt | 2 + src/go/doc/comment/html.go | 11 + src/go/doc/comment/markdown.go | 14 ++ src/go/doc/comment/mkstd.sh | 24 +++ src/go/doc/comment/parse.go | 247 ++++++++++++++++++++++- src/go/doc/comment/print.go | 52 +++++ src/go/doc/comment/std.go | 44 ++++ src/go/doc/comment/std_test.go | 35 ++++ src/go/doc/comment/testdata/README.md | 42 ++++ src/go/doc/comment/testdata/doclink.txt | 19 ++ src/go/doc/comment/testdata/doclink2.txt | 8 + src/go/doc/comment/testdata/doclink3.txt | 8 + src/go/doc/comment/testdata/doclink4.txt | 7 + src/go/doc/comment/testdata/doclink5.txt | 5 + src/go/doc/comment/testdata/doclink6.txt | 5 + src/go/doc/comment/testdata/doclink7.txt | 4 + src/go/doc/comment/testdata_test.go | 14 ++ src/go/doc/comment/text.go | 3 + 18 files changed, 538 insertions(+), 6 deletions(-) create mode 100755 src/go/doc/comment/mkstd.sh create mode 100644 src/go/doc/comment/std.go create mode 100644 src/go/doc/comment/std_test.go create mode 100644 src/go/doc/comment/testdata/README.md create mode 100644 src/go/doc/comment/testdata/doclink.txt create mode 100644 src/go/doc/comment/testdata/doclink2.txt create mode 100644 src/go/doc/comment/testdata/doclink3.txt create mode 100644 src/go/doc/comment/testdata/doclink4.txt create mode 100644 src/go/doc/comment/testdata/doclink5.txt create mode 100644 src/go/doc/comment/testdata/doclink6.txt create mode 100644 src/go/doc/comment/testdata/doclink7.txt diff --git a/api/next/51082.txt b/api/next/51082.txt index e078547b55864..0e5cbc5880ac2 100644 --- a/api/next/51082.txt +++ b/api/next/51082.txt @@ -1,3 +1,5 @@ +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 (*List) BlankBefore() bool #51082 pkg go/doc/comment, method (*List) BlankBetween() bool #51082 pkg go/doc/comment, method (*Parser) Parse(string) *Doc #51082 diff --git a/src/go/doc/comment/html.go b/src/go/doc/comment/html.go index a7c3ff2a5532d..da2300d12821b 100644 --- a/src/go/doc/comment/html.go +++ b/src/go/doc/comment/html.go @@ -54,6 +54,17 @@ func (p *htmlPrinter) text(out *bytes.Buffer, x []Text) { 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("") + } } } } diff --git a/src/go/doc/comment/markdown.go b/src/go/doc/comment/markdown.go index bdfdcdf565ed2..309e1805731c3 100644 --- a/src/go/doc/comment/markdown.go +++ b/src/go/doc/comment/markdown.go @@ -7,6 +7,7 @@ package comment import ( "bytes" "fmt" + "strings" ) // An mdPrinter holds the state needed for printing a Doc as Markdown. @@ -87,6 +88,19 @@ func (p *mdPrinter) rawText(out *bytes.Buffer, x []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(")") + } } } } diff --git a/src/go/doc/comment/mkstd.sh b/src/go/doc/comment/mkstd.sh new file mode 100755 index 0000000000000..c9dee8c55e69b --- /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/parse.go b/src/go/doc/comment/parse.go index 7c7b69966d668..af7e1931d919c 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -5,6 +5,7 @@ package comment import ( + "sort" "strings" "unicode" "unicode/utf8" @@ -27,7 +28,7 @@ type LinkDef struct { } // A Block is block-level content in a doc comment, -// one of *[Code], *[Heading], *[List], or *[Paragraph]. +// one of [*Code], [*Heading], [*List], or [*Paragraph]. type Block interface { block() } @@ -131,7 +132,7 @@ type Code struct { func (*Code) block() {} // A Text is text-level content in a doc comment, -// one of [Plain], [Italic], *[Link], or *[DocLink]. +// one of [Plain], [Italic], [*Link], or [*DocLink]. type Text interface { text() } @@ -231,6 +232,54 @@ type parseDoc struct { 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 { @@ -264,7 +313,7 @@ func (p *Parser) Parse(text string) *Doc { for _, b := range d.Content { switch b := b.(type) { case *Paragraph: - b.Text = d.parseText(string(b.Text[0].(Plain))) + b.Text = d.parseLinkedText(string(b.Text[0].(Plain))) } } @@ -406,9 +455,131 @@ func (d *parseDoc) paragraph(lines []string) (b Block, rest []string) { return &Paragraph{Text: []Text{Plain(strings.Join(lines, "\n"))}}, rest } -// parseText parses s as text and returns the parsed Text elements. -func (d *parseDoc) parseText(s string) []Text { +// 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) { @@ -424,7 +595,6 @@ func (d *parseDoc) parseText(s string) []Text { } for i := 0; i < len(s); { t := s[i:] - const autoLink = true if autoLink { if url, ok := autoURL(t); ok { flush(i) @@ -692,6 +862,10 @@ func ident(s string) (id string, ok bool) { // 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/print.go b/src/go/doc/comment/print.go index 6c8782c802fad..4f316fb75c9dc 100644 --- a/src/go/doc/comment/print.go +++ b/src/go/doc/comment/print.go @@ -55,6 +55,54 @@ type Printer struct { TextWidth int } +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 +} + type commentPrinter struct { *Printer headingPrefix string @@ -107,6 +155,10 @@ func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { p.indent(out, indent, string(t)) case *Link: p.text(out, indent, t.Text) + case *DocLink: + out.WriteString("[") + p.text(out, indent, t.Text) + out.WriteString("]") } } } diff --git a/src/go/doc/comment/std.go b/src/go/doc/comment/std.go new file mode 100644 index 0000000000000..71f15f47b1c84 --- /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 0000000000000..ae32dcd984aa0 --- /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 0000000000000..d6f2c549605d2 --- /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/doclink.txt b/src/go/doc/comment/testdata/doclink.txt new file mode 100644 index 0000000000000..c4e772dd079ae --- /dev/null +++ b/src/go/doc/comment/testdata/doclink.txt @@ -0,0 +1,19 @@ +-- 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 0000000000000..ecd8e4e0bce4f --- /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 0000000000000..0ccfb3df70826 --- /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 0000000000000..c7095276bfea3 --- /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 0000000000000..ac7b3ae100d80 --- /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 0000000000000..1acd03b61639b --- /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 0000000000000..d34979a385457 --- /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_test.go b/src/go/doc/comment/testdata_test.go index 43687c5d4ea69..0676d864b2941 100644 --- a/src/go/doc/comment/testdata_test.go +++ b/src/go/doc/comment/testdata_test.go @@ -25,6 +25,20 @@ func TestTestdata(t *testing.T) { "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. diff --git a/src/go/doc/comment/text.go b/src/go/doc/comment/text.go index 768cef32fb237..531675d5a44e6 100644 --- a/src/go/doc/comment/text.go +++ b/src/go/doc/comment/text.go @@ -69,6 +69,7 @@ func (p *textPrinter) text(out *bytes.Buffer, x []Text) { // 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 *bytes.Buffer, x []Text) { for _, t := range x { switch t := t.(type) { @@ -78,6 +79,8 @@ func (p *textPrinter) oneLongLine(out *bytes.Buffer, x []Text) { out.WriteString(string(t)) case *Link: p.oneLongLine(out, t.Text) + case *DocLink: + p.oneLongLine(out, t.Text) } } } From 036b615c2c69c0e800d0cc4e1a18ac086b1e7ea6 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 16:16:46 -0400 Subject: [PATCH 051/137] go/doc/comment: parse and print explicit links [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement parsing and printing of explicit links, like: Visit the [Go home page]. [Go home page]: https://go.dev For #51082. Change-Id: If8104e45558314dae0346df614b03d5664421cf1 Reviewed-on: https://go-review.googlesource.com/c/go/+/397282 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/go/doc/comment/parse.go | 50 ++++++++++++++++- src/go/doc/comment/print.go | 31 ++++++++++- src/go/doc/comment/testdata/link2.txt | 29 ++++++++++ src/go/doc/comment/testdata/link3.txt | 14 +++++ src/go/doc/comment/testdata/link4.txt | 77 +++++++++++++++++++++++++++ src/go/doc/comment/testdata/link5.txt | 36 +++++++++++++ src/go/doc/comment/testdata/link6.txt | 47 ++++++++++++++++ src/go/doc/comment/testdata/link7.txt | 25 +++++++++ src/go/doc/comment/text.go | 15 ++++++ 9 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 src/go/doc/comment/testdata/link2.txt create mode 100644 src/go/doc/comment/testdata/link3.txt create mode 100644 src/go/doc/comment/testdata/link4.txt create mode 100644 src/go/doc/comment/testdata/link5.txt create mode 100644 src/go/doc/comment/testdata/link6.txt create mode 100644 src/go/doc/comment/testdata/link7.txt diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index af7e1931d919c..920b446c7e988 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -303,7 +303,9 @@ func (p *Parser) Parse(text string) *Doc { if line != "" { var b Block b, lines = d.paragraph(lines) - d.Content = append(d.Content, b) + if b != nil { + d.Content = append(d.Content, b) + } } else { lines = lines[1:] } @@ -434,7 +436,7 @@ func isOldHeading(line string, all []string, off int) bool { return true } -// parargraph returns a paragraph block built from the +// 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. @@ -452,9 +454,53 @@ func (d *parseDoc) paragraph(lines []string) (b Block, rest []string) { 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 +} + // parseLinkedText parses text that is allowed to contain explicit links, // such as [math.Sin] or [Go home page], into a slice of Text items. // diff --git a/src/go/doc/comment/print.go b/src/go/doc/comment/print.go index 4f316fb75c9dc..2ef8d7375deda 100644 --- a/src/go/doc/comment/print.go +++ b/src/go/doc/comment/print.go @@ -121,6 +121,29 @@ func (p *Printer) Comment(d *Doc) []byte { 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() } @@ -154,7 +177,13 @@ func (p *commentPrinter) text(out *bytes.Buffer, indent string, x []Text) { case Italic: p.indent(out, indent, string(t)) case *Link: - p.text(out, indent, t.Text) + 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) diff --git a/src/go/doc/comment/testdata/link2.txt b/src/go/doc/comment/testdata/link2.txt new file mode 100644 index 0000000000000..a19835c4f67db --- /dev/null +++ b/src/go/doc/comment/testdata/link2.txt @@ -0,0 +1,29 @@ +-- 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 0000000000000..5a115b5cb7199 --- /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 0000000000000..75f194c845c2e --- /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 0000000000000..b4fb5889f47dc --- /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 0000000000000..579b35d211ac8 --- /dev/null +++ b/src/go/doc/comment/testdata/link6.txt @@ -0,0 +1,47 @@ +-- 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 0000000000000..89a8b3170e011 --- /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/text.go b/src/go/doc/comment/text.go index 531675d5a44e6..e9941bc957161 100644 --- a/src/go/doc/comment/text.go +++ b/src/go/doc/comment/text.go @@ -29,6 +29,21 @@ func (p *Printer) Text(d *Doc) []byte { } 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() } From e4e033a74cfcc75cb828cbd37e8279703e4620a3 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 16:21:18 -0400 Subject: [PATCH 052/137] go/doc/comment: add text wrapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement wrapping of text output, for the “go doc” command. The algorithm is from D. S. Hirschberg and L. L. Larmore, “The least weight subsequence problem,” FOCS 1985, pp. 137-143. For #51082. Change-Id: I07787be3b4f1716b8ed9de9959f94ecbc596cc43 Reviewed-on: https://go-review.googlesource.com/c/go/+/397283 Run-TryBot: Russ Cox Reviewed-by: Ian Lance Taylor Reviewed-by: Jonathan Amsterdam TryBot-Result: Gopher Robot --- src/go/doc/comment/testdata/doclink.txt | 4 +- src/go/doc/comment/testdata/link2.txt | 4 +- src/go/doc/comment/testdata/link6.txt | 7 +- src/go/doc/comment/testdata/quote.txt | 12 ++ src/go/doc/comment/testdata/text3.txt | 28 ++++ src/go/doc/comment/testdata/text4.txt | 29 ++++ src/go/doc/comment/testdata/text5.txt | 38 +++++ src/go/doc/comment/testdata/text6.txt | 18 +++ src/go/doc/comment/testdata/text7.txt | 21 +++ src/go/doc/comment/text.go | 193 +++++++++++++++++++++++- src/go/doc/comment/wrap_test.go | 141 +++++++++++++++++ 11 files changed, 488 insertions(+), 7 deletions(-) create mode 100644 src/go/doc/comment/testdata/quote.txt create mode 100644 src/go/doc/comment/testdata/text3.txt create mode 100644 src/go/doc/comment/testdata/text4.txt create mode 100644 src/go/doc/comment/testdata/text5.txt create mode 100644 src/go/doc/comment/testdata/text6.txt create mode 100644 src/go/doc/comment/testdata/text7.txt create mode 100644 src/go/doc/comment/wrap_test.go diff --git a/src/go/doc/comment/testdata/doclink.txt b/src/go/doc/comment/testdata/doclink.txt index c4e772dd079ae..a9323471fd450 100644 --- a/src/go/doc/comment/testdata/doclink.txt +++ b/src/go/doc/comment/testdata/doclink.txt @@ -9,7 +9,9 @@ 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. +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 -- diff --git a/src/go/doc/comment/testdata/link2.txt b/src/go/doc/comment/testdata/link2.txt index a19835c4f67db..8637a32f01ab7 100644 --- a/src/go/doc/comment/testdata/link2.txt +++ b/src/go/doc/comment/testdata/link2.txt @@ -15,7 +15,9 @@ 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. +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. diff --git a/src/go/doc/comment/testdata/link6.txt b/src/go/doc/comment/testdata/link6.txt index 579b35d211ac8..ff629b4573653 100644 --- a/src/go/doc/comment/testdata/link6.txt +++ b/src/go/doc/comment/testdata/link6.txt @@ -23,9 +23,12 @@ 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. +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}. +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/quote.txt b/src/go/doc/comment/testdata/quote.txt new file mode 100644 index 0000000000000..799663af8025c --- /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/text3.txt b/src/go/doc/comment/testdata/text3.txt new file mode 100644 index 0000000000000..75d2c3765ccce --- /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 0000000000000..e429985077e24 --- /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 0000000000000..2408fc559d3b3 --- /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 0000000000000..d6deff52cf830 --- /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 0000000000000..c9fb6d37547d1 --- /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/text.go b/src/go/doc/comment/text.go index e9941bc957161..d6d651b5d613d 100644 --- a/src/go/doc/comment/text.go +++ b/src/go/doc/comment/text.go @@ -7,13 +7,17 @@ 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 bytes.Buffer + long bytes.Buffer + prefix string + width int } // Text returns a textual formatting of the Doc. @@ -21,7 +25,13 @@ type textPrinter struct { func (p *Printer) Text(d *Doc) []byte { tp := &textPrinter{ Printer: p, + prefix: p.TextPrefix, + width: p.TextWidth, } + 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) { @@ -69,6 +79,7 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) { fmt.Fprintf(out, "?%T\n", x) case *Paragraph: + out.WriteString(p.prefix) p.text(out, x.Text) } } @@ -77,9 +88,27 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) { // TODO: Wrap lines. func (p *textPrinter) text(out *bytes.Buffer, x []Text) { p.oneLongLine(&p.long, x) - out.WriteString(strings.ReplaceAll(p.long.String(), "\n", " ")) + words := strings.Fields(p.long.String()) p.long.Reset() - writeNL(out) + + var seq []int + if p.width < 0 { + seq = []int{0, len(words)} // one long line + } else { + seq = wrap(words, p.width) + } + for i := 0; i+1 < len(seq); i++ { + if i > 0 { + out.WriteString(p.prefix) + } + 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, @@ -99,3 +128,161 @@ func (p *textPrinter) oneLongLine(out *bytes.Buffer, x []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 0000000000000..f9802c9c445fd --- /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)] +} From 6eceabf11936638c7000a7a12b4c285ffe9b58f9 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 16:30:08 -0400 Subject: [PATCH 053/137] go/doc/comment: parse and print headings [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement both old-style and new-style headings, like: Text here. Old Style Heading More text here. # New Style Heading More text here. For #51082. Change-Id: I0d735782d0d345794fc2d4e1bdaa0251b8d4bba2 Reviewed-on: https://go-review.googlesource.com/c/go/+/397284 Run-TryBot: Russ Cox Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Jonathan Amsterdam --- api/next/51082.txt | 1 + src/go/doc/comment/html.go | 16 +++++ src/go/doc/comment/markdown.go | 18 +++++- src/go/doc/comment/parse.go | 49 ++++++++++++-- src/go/doc/comment/print.go | 48 ++++++++++++++ src/go/doc/comment/testdata/head.txt | 92 +++++++++++++++++++++++++++ src/go/doc/comment/testdata/head2.txt | 36 +++++++++++ src/go/doc/comment/testdata/head3.txt | 7 ++ src/go/doc/comment/text.go | 9 ++- 9 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 src/go/doc/comment/testdata/head.txt create mode 100644 src/go/doc/comment/testdata/head2.txt create mode 100644 src/go/doc/comment/testdata/head3.txt diff --git a/api/next/51082.txt b/api/next/51082.txt index 0e5cbc5880ac2..72c5b2e246ef1 100644 --- a/api/next/51082.txt +++ b/api/next/51082.txt @@ -1,5 +1,6 @@ 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 diff --git a/src/go/doc/comment/html.go b/src/go/doc/comment/html.go index da2300d12821b..f6ea588b3d557 100644 --- a/src/go/doc/comment/html.go +++ b/src/go/doc/comment/html.go @@ -7,6 +7,7 @@ package comment import ( "bytes" "fmt" + "strconv" ) // An htmlPrinter holds the state needed for printing a Doc as HTML. @@ -35,6 +36,21 @@ func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { out.WriteString("

") p.text(out, x.Text) out.WriteString("\n") + + case *Heading: + out.WriteString("") + p.text(out, x.Text) + out.WriteString("\n") } } diff --git a/src/go/doc/comment/markdown.go b/src/go/doc/comment/markdown.go index 309e1805731c3..44ea727daeeaa 100644 --- a/src/go/doc/comment/markdown.go +++ b/src/go/doc/comment/markdown.go @@ -13,13 +13,17 @@ import ( // An mdPrinter holds the state needed for printing a Doc as Markdown. type mdPrinter struct { *Printer - raw bytes.Buffer + 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} + mp := &mdPrinter{ + Printer: p, + headingPrefix: strings.Repeat("#", p.headingLevel()) + " ", + } var out bytes.Buffer for i, x := range d.Content { @@ -40,6 +44,16 @@ func (p *mdPrinter) block(out *bytes.Buffer, x Block) { 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") } } diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index 920b446c7e988..25b5f10f2fcce 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -298,15 +298,34 @@ func (p *Parser) Parse(text string) *Doc { // 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] - if line != "" { - var b Block + n := len(lines) + var b Block + + switch { + case line == "": + // emit nothing + + 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) - if b != nil { - d.Content = append(d.Content, b) - } - } else { + didHeading = false + } + + if b != nil { + d.Content = append(d.Content, b) + } + if len(lines) == n { lines = lines[1:] } } @@ -436,6 +455,24 @@ func isOldHeading(line string, all []string, off int) bool { 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:]))}} +} + // 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, diff --git a/src/go/doc/comment/print.go b/src/go/doc/comment/print.go index 2ef8d7375deda..db520e81925e9 100644 --- a/src/go/doc/comment/print.go +++ b/src/go/doc/comment/print.go @@ -55,6 +55,20 @@ type Printer struct { 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) @@ -103,6 +117,35 @@ func (l *DocLink) DefaultURL(baseURL string) string { 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 @@ -165,6 +208,11 @@ func (p *commentPrinter) block(out *bytes.Buffer, x Block) { case *Paragraph: p.text(out, "", x.Text) out.WriteString("\n") + + case *Heading: + out.WriteString("# ") + p.text(out, "", x.Text) + out.WriteString("\n") } } diff --git a/src/go/doc/comment/testdata/head.txt b/src/go/doc/comment/testdata/head.txt new file mode 100644 index 0000000000000..b99a8c59f3fe7 --- /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 0000000000000..d3576325e0499 --- /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 0000000000000..dbb7cb3ffb8d2 --- /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/text.go b/src/go/doc/comment/text.go index d6d651b5d613d..1eddad30fd51e 100644 --- a/src/go/doc/comment/text.go +++ b/src/go/doc/comment/text.go @@ -15,7 +15,7 @@ import ( // A textPrinter holds the state needed for printing a Doc as plain text. type textPrinter struct { *Printer - long bytes.Buffer + long strings.Builder prefix string width int } @@ -81,6 +81,11 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) { case *Paragraph: out.WriteString(p.prefix) p.text(out, x.Text) + + case *Heading: + out.WriteString(p.prefix) + out.WriteString("# ") + p.text(out, x.Text) } } @@ -114,7 +119,7 @@ func (p *textPrinter) text(out *bytes.Buffer, x []Text) { // 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 *bytes.Buffer, x []Text) { +func (p *textPrinter) oneLongLine(out *strings.Builder, x []Text) { for _, t := range x { switch t := t.(type) { case Plain: From 3f5d099663fab4a59133bbe7643f40deb5460509 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 16:40:03 -0400 Subject: [PATCH 054/137] go/doc/comment: parse and print code [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement indented code blocks. For #51082. Change-Id: I0eacbf56e101424a875386cb6f26174b239561f5 Reviewed-on: https://go-review.googlesource.com/c/go/+/397285 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/go/doc/comment/html.go | 5 ++ src/go/doc/comment/markdown.go | 12 ++++ src/go/doc/comment/parse.go | 43 +++++++++++- src/go/doc/comment/print.go | 12 ++++ src/go/doc/comment/testdata/code.txt | 94 +++++++++++++++++++++++++++ src/go/doc/comment/testdata/code2.txt | 31 +++++++++ src/go/doc/comment/testdata/code3.txt | 33 ++++++++++ src/go/doc/comment/testdata/text9.txt | 12 ++++ src/go/doc/comment/text.go | 30 +++++++-- 9 files changed, 263 insertions(+), 9 deletions(-) create mode 100644 src/go/doc/comment/testdata/code.txt create mode 100644 src/go/doc/comment/testdata/code2.txt create mode 100644 src/go/doc/comment/testdata/code3.txt create mode 100644 src/go/doc/comment/testdata/text9.txt diff --git a/src/go/doc/comment/html.go b/src/go/doc/comment/html.go index f6ea588b3d557..14a20b91e5e91 100644 --- a/src/go/doc/comment/html.go +++ b/src/go/doc/comment/html.go @@ -51,6 +51,11 @@ func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { out.WriteString("\n") + + case *Code: + out.WriteString("
")
+		p.escape(out, x.Text)
+		out.WriteString("
\n") } } diff --git a/src/go/doc/comment/markdown.go b/src/go/doc/comment/markdown.go index 44ea727daeeaa..9e86cd8aeff4e 100644 --- a/src/go/doc/comment/markdown.go +++ b/src/go/doc/comment/markdown.go @@ -54,6 +54,18 @@ func (p *mdPrinter) block(out *bytes.Buffer, x Block) { 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") + } } } diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index 25b5f10f2fcce..7f97e41a62a42 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -309,6 +309,9 @@ func (p *Parser) Parse(text string) *Doc { case line == "": // emit nothing + 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 @@ -473,17 +476,51 @@ 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) { - // TODO: Paragraph should be interrupted by any indented line, + // Paragraph is interrupted by any indented line, // which is either a list or a code block, // and of course by a blank line. - // It should not be interrupted by a # line - headings must stand alone. + // It is not interrupted by a # line - headings must stand alone. i := 0 - for i < len(lines) && lines[i] != "" { + for i < len(lines) && lines[i] != "" && !isIndented(lines[i]) { i++ } lines, rest = lines[:i], lines[i:] diff --git a/src/go/doc/comment/print.go b/src/go/doc/comment/print.go index db520e81925e9..d426b8176187b 100644 --- a/src/go/doc/comment/print.go +++ b/src/go/doc/comment/print.go @@ -213,6 +213,18 @@ func (p *commentPrinter) block(out *bytes.Buffer, x Block) { 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") + } } } diff --git a/src/go/doc/comment/testdata/code.txt b/src/go/doc/comment/testdata/code.txt new file mode 100644 index 0000000000000..06b1519574c75 --- /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 0000000000000..0810bed41c207 --- /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 0000000000000..4a96a0e9ab9e8 --- /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/text9.txt b/src/go/doc/comment/testdata/text9.txt new file mode 100644 index 0000000000000..07a64aa2276a9 --- /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/text.go b/src/go/doc/comment/text.go index 1eddad30fd51e..e35e5ccfd1a68 100644 --- a/src/go/doc/comment/text.go +++ b/src/go/doc/comment/text.go @@ -15,18 +15,23 @@ import ( // A textPrinter holds the state needed for printing a Doc as plain text. type textPrinter struct { *Printer - long strings.Builder - prefix string - width int + 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, - width: p.TextWidth, + 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) @@ -35,6 +40,7 @@ func (p *Printer) Text(d *Doc) []byte { 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) @@ -86,6 +92,18 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) { 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) + } } } From e1b0862925c8ed97bdaf9277f4a2ba38e0b58cbe Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 3 Apr 2022 16:45:18 -0400 Subject: [PATCH 055/137] go/doc/comment: parse and print lists [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Implement lists, like: Three numbers: - One - Two - Three For #51082. Change-Id: Id87d9c19bca677be968f3803809a9ea6c705f3ad Reviewed-on: https://go-review.googlesource.com/c/go/+/397286 Run-TryBot: Russ Cox Reviewed-by: Ian Lance Taylor Reviewed-by: Jonathan Amsterdam TryBot-Result: Gopher Robot --- src/go/doc/comment/html.go | 48 +++++++++- src/go/doc/comment/markdown.go | 23 +++++ src/go/doc/comment/parse.go | 91 ++++++++++++++++++ src/go/doc/comment/print.go | 23 +++++ src/go/doc/comment/testdata/list.txt | 48 ++++++++++ src/go/doc/comment/testdata/list2.txt | 57 ++++++++++++ src/go/doc/comment/testdata/list3.txt | 32 +++++++ src/go/doc/comment/testdata/list4.txt | 38 ++++++++ src/go/doc/comment/testdata/list5.txt | 40 ++++++++ src/go/doc/comment/testdata/list6.txt | 129 ++++++++++++++++++++++++++ src/go/doc/comment/testdata/list7.txt | 98 +++++++++++++++++++ src/go/doc/comment/testdata/list8.txt | 56 +++++++++++ src/go/doc/comment/testdata/text.txt | 62 +++++++++++++ src/go/doc/comment/testdata/text8.txt | 94 +++++++++++++++++++ src/go/doc/comment/text.go | 35 ++++++- 15 files changed, 869 insertions(+), 5 deletions(-) create mode 100644 src/go/doc/comment/testdata/list.txt create mode 100644 src/go/doc/comment/testdata/list2.txt create mode 100644 src/go/doc/comment/testdata/list3.txt create mode 100644 src/go/doc/comment/testdata/list4.txt create mode 100644 src/go/doc/comment/testdata/list5.txt create mode 100644 src/go/doc/comment/testdata/list6.txt create mode 100644 src/go/doc/comment/testdata/list7.txt create mode 100644 src/go/doc/comment/testdata/list8.txt create mode 100644 src/go/doc/comment/testdata/text.txt create mode 100644 src/go/doc/comment/testdata/text8.txt diff --git a/src/go/doc/comment/html.go b/src/go/doc/comment/html.go index 14a20b91e5e91..bc076f6a58424 100644 --- a/src/go/doc/comment/html.go +++ b/src/go/doc/comment/html.go @@ -13,6 +13,7 @@ import ( // 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. @@ -33,7 +34,9 @@ func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { fmt.Fprintf(out, "?%T", x) case *Paragraph: - out.WriteString("

") + if !p.tight { + out.WriteString("

") + } p.text(out, x.Text) out.WriteString("\n") @@ -56,7 +59,50 @@ func (p *htmlPrinter) block(out *bytes.Buffer, x Block) { 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. diff --git a/src/go/doc/comment/markdown.go b/src/go/doc/comment/markdown.go index 9e86cd8aeff4e..d8550f2e39d30 100644 --- a/src/go/doc/comment/markdown.go +++ b/src/go/doc/comment/markdown.go @@ -66,6 +66,29 @@ func (p *mdPrinter) block(out *bytes.Buffer, x Block) { } 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") + } + } } } diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index 7f97e41a62a42..c881bbab5b25f 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -309,6 +309,10 @@ func (p *Parser) Parse(text string) *Doc { 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) @@ -575,6 +579,93 @@ func parseLink(line string) (*LinkDef, bool) { 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. // diff --git a/src/go/doc/comment/print.go b/src/go/doc/comment/print.go index d426b8176187b..cdbc7cc460e62 100644 --- a/src/go/doc/comment/print.go +++ b/src/go/doc/comment/print.go @@ -225,6 +225,29 @@ func (p *commentPrinter) block(out *bytes.Buffer, x Block) { } 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") + } + } } } diff --git a/src/go/doc/comment/testdata/list.txt b/src/go/doc/comment/testdata/list.txt new file mode 100644 index 0000000000000..455782f864ffe --- /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 0000000000000..c390b3d59a5bb --- /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 0000000000000..d7d345d2d3ca3 --- /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 0000000000000..9c28d65b6c3e2 --- /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 0000000000000..a5128e5b7c065 --- /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 0000000000000..ffc0122f52ad5 --- /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 0000000000000..446605061f6d0 --- /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 0000000000000..fc46b0d835155 --- /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/text.txt b/src/go/doc/comment/testdata/text.txt new file mode 100644 index 0000000000000..c4de6e20d22d2 --- /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/text8.txt b/src/go/doc/comment/testdata/text8.txt new file mode 100644 index 0000000000000..560ac951c1774 --- /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/text.go b/src/go/doc/comment/text.go index e35e5ccfd1a68..e9684f066bfb1 100644 --- a/src/go/doc/comment/text.go +++ b/src/go/doc/comment/text.go @@ -86,12 +86,12 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) { case *Paragraph: out.WriteString(p.prefix) - p.text(out, x.Text) + p.text(out, "", x.Text) case *Heading: out.WriteString(p.prefix) out.WriteString("# ") - p.text(out, x.Text) + p.text(out, "", x.Text) case *Code: text := x.Text @@ -104,12 +104,38 @@ func (p *textPrinter) block(out *bytes.Buffer, x Block) { } 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, x []Text) { +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() @@ -118,11 +144,12 @@ func (p *textPrinter) text(out *bytes.Buffer, x []Text) { if p.width < 0 { seq = []int{0, len(words)} // one long line } else { - seq = wrap(words, p.width) + 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 { From 078cc6a04f1dc0ef46f1bd5c27dc20a6fcfbabcf Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sat, 29 Jan 2022 18:25:41 -0500 Subject: [PATCH 056/137] go/printer: format doc comments [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Use go/doc/comment to reformat doc comments into a standard form, enabling future expansion later and generally making it easier to edit and read doc comments. For #51082. Change-Id: I6ab3b80846f03d781951111e4c36f86f47d21bb2 Reviewed-on: https://go-review.googlesource.com/c/go/+/384264 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor --- src/go/ast/ast.go | 1 + src/go/printer/comment.go | 152 +++++++++++++++++++++++ src/go/printer/printer.go | 19 ++- src/go/printer/printer_test.go | 4 +- src/go/printer/testdata/comments.golden | 11 +- src/go/printer/testdata/comments.input | 18 ++- src/go/printer/testdata/comments.x | 1 - src/go/printer/testdata/comments2.golden | 1 - src/go/printer/testdata/doc.golden | 21 ++++ src/go/printer/testdata/doc.input | 20 +++ 10 files changed, 234 insertions(+), 14 deletions(-) create mode 100644 src/go/printer/comment.go create mode 100644 src/go/printer/testdata/doc.golden create mode 100644 src/go/printer/testdata/doc.input diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index 61855359f86b6..8d467a78284ee 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -159,6 +159,7 @@ func (g *CommentGroup) Text() string { } // isDirective reports whether c is a comment directive. +// This code is also in go/printer. func isDirective(c string) bool { // "//line " is a line directive. // (The // has been removed.) diff --git a/src/go/printer/comment.go b/src/go/printer/comment.go new file mode 100644 index 0000000000000..9749146739730 --- /dev/null +++ b/src/go/printer/comment.go @@ -0,0 +1,152 @@ +// 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. + // (The // has been removed.) + if strings.HasPrefix(c, "line ") { + 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/printer.go b/src/go/printer/printer.go index 5014f59ab5c5d..25eec6bd751a2 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 ad2d86052a460..cb62b3e4f35db 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 1a21fff331473..d03da3b65afd0 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 */ @@ -691,6 +695,7 @@ func _() { // 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 aa428a2aa68bf..2a15fa44a5dd9 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 */, @@ -718,10 +724,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 ae7729286e5a6..5d088ab2c3802 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 8b3a94ddcd03a..83213d1a9db50 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 0000000000000..7ac241a4bb68b --- /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 0000000000000..5c057ed2c4cca --- /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 + + From 8b4ded3ef379c721fbd7bff76cf8dd4504b4738c Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 3 Feb 2022 14:09:32 -0500 Subject: [PATCH 057/137] cmd/go: gofmt alldocs.go [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Reformat alldocs.go using the new doc comment formatter. This file is so large it gets its own gofmt CL. For #51082. Change-Id: Ie14cf1aad776e6f4180d88245d05a86e5fb6a3b0 Reviewed-on: https://go-review.googlesource.com/c/go/+/384267 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/cmd/go/alldocs.go | 2253 ++++++++++++++++++++--------------------- 1 file changed, 1105 insertions(+), 1148 deletions(-) diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index f9d78b59e32f5..6800e1c7d2e18 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,110 @@ // 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. -// -// -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. +// -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. // // The -asmflags, -gccgoflags, -gcflags, and -ldflags flags accept a // space-separated list of arguments to pass to an underlying tool @@ -240,12 +238,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 +253,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 +301,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 +317,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 +328,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 +353,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 +361,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 json.Decoder.Decode -// go doc json.decoder.decode -// go doc json.decode -// cd go/src/encoding/json; go doc decode +// +// 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 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. -// -// -// Print Go environment information +// +// -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 // // Usage: // -// go env [-json] [-u] [-w] [var ...] +// go env [-json] [-u] [-w] [var ...] // // Env prints Go environment information. // @@ -448,12 +445,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 +464,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 +486,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 +502,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 +525,25 @@ // 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. +// $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 +559,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 +590,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 +606,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 +618,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 +636,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 +671,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 +730,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 +743,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 -// -// // 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 -// -// // 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 -// -// // Error information -// Incomplete bool // this package or a dependency has an error -// Error *PackageError // error loading package -// DepsErrors []*PackageError // errors loading dependencies -// } +// 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 +// +// // 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 +// +// // 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 +// } // // 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 +829,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 +842,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 +921,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 ModuleError struct { -// Err string // the error itself -// } +// 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 +// } // // 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 +948,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 +972,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 +1017,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 +1028,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 +1068,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 +1086,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 +1148,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 GoMod struct { -// Module ModPath -// Go string -// Require []Require -// Exclude []Module -// Replace []Replace -// Retract []Retract -// } -// -// type ModPath struct { -// Path string -// Deprecated string -// } -// -// type Require struct { -// Path string -// Version string -// Indirect bool -// } -// -// type Replace struct { -// Old Module -// New Module -// } -// -// type Retract struct { -// Low string -// High string -// Rationale string -// } +// type Module struct { +// Path string +// Version string +// } +// +// type GoMod struct { +// Module ModPath +// Go string +// Require []Require +// Exclude []Module +// Replace []Replace +// Retract []Retract +// } +// +// type ModPath struct { +// Path string +// Deprecated string +// } +// +// type Require struct { +// Path string +// Version string +// Indirect bool +// } +// +// type Replace struct { +// Old Module +// New Module +// } +// +// 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 +1193,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 +1210,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 +1230,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 +1264,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 +1287,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 +1302,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 +1327,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 +1364,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 +1399,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 +1455,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 +1499,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 +1524,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 +1545,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 +1569,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 +1590,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 +1681,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 +1717,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 +1731,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 +1757,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 +1777,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 +1786,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 +1809,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 +1852,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 +1874,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=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=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=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=plugin -// Build the listed main packages, plus all packages that they -// import, into a Go plugin. Packages not 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-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=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=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. // // 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 +1940,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 +1985,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 +2001,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. -// -// -// File types +// 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 // // 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 +2218,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 +2243,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 +2287,21 @@ // // Here's an example directory layout: // -// 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) +// 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) // // Go searches each directory listed in GOPATH to find source code, // but new packages are always downloaded into the first directory @@ -2339,33 +2309,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 +2344,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 +2358,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 +2396,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 +2458,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 +2468,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 +2499,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 +2507,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 +2538,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 +2578,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 +2596,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 +2622,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 +2638,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 +2655,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 +2679,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 +2690,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 +2773,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 +2786,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 +2800,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 +2814,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 +2827,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. -// -// -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. -// -// -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. -// -// -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. -// -// -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. -// -// -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). -// -// -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. -// -// -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. -// -// -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. -// -// -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. -// -// -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. +// -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). +// +// -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. +// +// -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. +// +// -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. +// +// -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). +// +// -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. +// +// -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'). +// +// -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. +// +// -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). +// +// -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. // // 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 +3036,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 +3075,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 +3102,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 +3129,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 +3155,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 +3204,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 +3221,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 From 27b7b1fa19b5d8c8855859ca64b52f960a446ce7 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 7 Feb 2022 17:24:40 -0500 Subject: [PATCH 058/137] go/doc: use go/doc/comment [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Use go/doc/comment to implement the existing go/doc comment APIs, as well as adding new APIs more tailored to the new world. For #51082. Change-Id: I05b97ecedbf7cf7b8dede7ace6736ed6d89204a9 Reviewed-on: https://go-review.googlesource.com/c/go/+/384265 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- api/next/51082.txt | 6 + src/go/build/deps_test.go | 2 +- src/go/doc/comment.go | 543 +++--------------------------- src/go/doc/comment_test.go | 274 +++------------ src/go/doc/doc.go | 132 +++++++- src/go/doc/doc_test.go | 9 - src/go/doc/reader.go | 95 +++++- src/go/doc/synopsis.go | 91 +++-- src/go/doc/synopsis_test.go | 14 +- src/go/doc/testdata/pkgdoc/doc.go | 19 ++ 10 files changed, 390 insertions(+), 795 deletions(-) create mode 100644 src/go/doc/testdata/pkgdoc/doc.go diff --git a/api/next/51082.txt b/api/next/51082.txt index 72c5b2e246ef1..b05997f985c31 100644 --- a/api/next/51082.txt +++ b/api/next/51082.txt @@ -1,3 +1,9 @@ +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 diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go index 7117e08c3bfcb..a43f72fea106c 100644 --- a/src/go/build/deps_test.go +++ b/src/go/build/deps_test.go @@ -294,7 +294,7 @@ var depsRules = ` < go/printer < go/format; - go/parser, internal/lazyregexp, text/template + go/doc/comment, go/parser, internal/lazyregexp, text/template < go/doc; math/big, go/token diff --git a/src/go/doc/comment.go b/src/go/doc/comment.go index f1aa69d97400c..4f73664ba3191 100644 --- a/src/go/doc/comment.go +++ b/src/go/doc/comment.go @@ -2,515 +2,70 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Godoc comment extraction and comment -> HTML formatting. - package doc import ( - "bytes" - "internal/lazyregexp" + "go/doc/comment" "io" - "strings" - "text/template" // for HTMLEscape - "unicode" - "unicode/utf8" -) - -const ( - ldquo = "“" - rdquo = "”" - ulquo = "“" - urquo = "”" ) -var ( - htmlQuoteReplacer = strings.NewReplacer(ulquo, ldquo, urquo, rdquo) - unicodeQuoteReplacer = strings.NewReplacer("``", ulquo, "''", urquo) -) - -// Escape comment text for HTML. If nice is set, also replace: +// ToHTML converts comment text to formatted HTML. // -// `` -> “ -// '' -> ” +// 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. // -func commentEscape(w io.Writer, text string, nice bool) { - if nice { - // In the first pass, we convert `` and '' into their unicode equivalents. - // This prevents them from being escaped in HTMLEscape. - text = convertQuotes(text) - var buf bytes.Buffer - template.HTMLEscape(&buf, []byte(text)) - // Now we convert the unicode quotes to their HTML escaped entities to maintain old behavior. - // We need to use a temp buffer to read the string back and do the conversion, - // otherwise HTMLEscape will escape & to & - htmlQuoteReplacer.WriteString(w, buf.String()) - return - } - template.HTMLEscape(w, []byte(text)) -} - -func convertQuotes(text string) string { - return unicodeQuoteReplacer.Replace(text) -} - -const ( - // Regexp for Go identifiers - identRx = `[\pL_][\pL_0-9]*` - - // Regexp for URLs - // Match parens, and check later for balance - see #5043, #22285 - // Match .,:;?! within path, but not at end - see #18139, #16565 - // This excludes some rare yet valid urls ending in common punctuation - // in order to allow sentences ending in URLs. - - // protocol (required) e.g. http - protoPart = `(https?|ftp|file|gopher|mailto|nntp)` - // host (required) e.g. www.example.com or [::1]:8080 - hostPart = `([a-zA-Z0-9_@\-.\[\]:]+)` - // path+query+fragment (optional) e.g. /path/index.html?q=foo#bar - pathPart = `([.,:;?!]*[a-zA-Z0-9$'()*+&#=@~_/\-\[\]%])*` - - urlRx = protoPart + `://` + hostPart + pathPart -) - -var matchRx = lazyregexp.New(`(` + urlRx + `)|(` + identRx + `)`) - -var ( - html_a = []byte(``) - html_enda = []byte("") - html_i = []byte("") - html_endi = []byte("") - html_p = []byte("

\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. +// Given the *[doc.Package] p where text was found, +// ToHTML(w, text, nil) can be replaced by: // -// 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. +// w.Write(p.HTML(text)) // -// A span of indented lines is converted into a
 block,
-// with the common indent prefix removed.
+// which is in turn shorthand for:
 //
-// 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.Printer().HTML(p.Parser().Parse(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 (”).
+// If words may be non-nil, the longer replacement is:
 //
-// 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.
+//	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))
-				}
-			}
-		}
-	}
-}
-
-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
+// 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,
+	}
+	w.Write(pr.Text(d))
 }
diff --git a/src/go/doc/comment_test.go b/src/go/doc/comment_test.go
index 6d1b209e1e1f9..e1e5f15bdf502 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")
 	}
-}
-
-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()
+	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))) } -} -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 -} + var buf bytes.Buffer -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() + 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 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}, + buf.Reset() + ToText(&buf, input, "", "\t", 80) + if b := buf.Bytes(); string(b) != wantOldText { + t.Errorf("%s", diff.Diff("ToText", b, "want", []byte(wantOldText))) } - 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 f0c1b5dd32713..651a2c1f6c83b 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 5a5fbd8bf3ce6..b79087e5387d3 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/reader.go b/src/go/doc/reader.go index c591059e5c878..492e03970340e 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 ca607cc4e568d..3c9e7e9b9e77b 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) + return s } -const ( - keepNL = 1 << iota -) +// 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) +} -// 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) +// 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 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) +// 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(strings.ToLower(s), prefix) { + if strings.HasPrefix(lower, prefix) { return "" } } - s = convertQuotes(s) - return s -} - -var IllegalPrefixes = []string{ - "copyright", - "all rights", - "author", + 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 3f443dc757883..158c734bf02bb 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 0000000000000..61bd4e32f9328 --- /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 From 017933163ab6a2b254f0310c61b57db65cded92e Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Tue, 8 Feb 2022 13:43:40 -0500 Subject: [PATCH 059/137] cmd/doc: use new go/doc APIs [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Use the new per-Package go/doc API instead of the top-level functions from go/doc. These handle links better. For #51082. Change-Id: I169b46d973673abdb6f126352c9f1e30f9fe1122 Reviewed-on: https://go-review.googlesource.com/c/go/+/384266 Run-TryBot: Russ Cox Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/cmd/doc/pkg.go | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/cmd/doc/pkg.go b/src/cmd/doc/pkg.go index 49b68873b6f9c..35f2eb24bf2fc 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()) } From 19309779ac5e2f5a2fd3cbb34421dafb2855ac21 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Thu, 3 Feb 2022 14:12:08 -0500 Subject: [PATCH 060/137] all: gofmt main repo [This CL is part of a sequence implementing the proposal #51082. The design doc is at https://go.dev/s/godocfmt-design.] Run the updated gofmt, which reformats doc comments, on the main repository. Vendored files are excluded. For #51082. Change-Id: I7332f099b60f716295fb34719c98c04eb1a85407 Reviewed-on: https://go-review.googlesource.com/c/go/+/384268 Reviewed-by: Jonathan Amsterdam Reviewed-by: Ian Lance Taylor --- misc/cgo/gmp/gmp.go | 7 +- misc/ios/go_ios_exec.go | 8 +- src/archive/tar/common.go | 9 +- src/archive/tar/reader.go | 6 +- src/archive/tar/strconv.go | 1 + src/builtin/builtin.go | 10 + src/bytes/bytes.go | 14 +- src/cmd/addr2line/main.go | 1 + src/cmd/asm/doc.go | 4 +- src/cmd/asm/internal/asm/parse.go | 8 +- src/cmd/buildid/doc.go | 1 + src/cmd/cgo/doc.go | 18 +- src/cmd/cgo/gcc.go | 28 +- src/cmd/compile/internal/amd64/ssa.go | 4 +- src/cmd/compile/internal/inline/inl.go | 6 +- src/cmd/compile/internal/ir/expr.go | 10 +- src/cmd/compile/internal/ir/node.go | 3 +- src/cmd/compile/internal/liveness/plive.go | 12 +- src/cmd/compile/internal/noder/unified.go | 28 +- src/cmd/compile/internal/pkginit/init.go | 6 +- src/cmd/compile/internal/reflectdata/alg.go | 22 +- .../compile/internal/reflectdata/reflect.go | 16 +- src/cmd/compile/internal/s390x/ssa.go | 6 +- .../compile/internal/ssa/addressingmodes.go | 10 +- src/cmd/compile/internal/ssa/block.go | 47 +-- src/cmd/compile/internal/ssa/branchelim.go | 10 +- src/cmd/compile/internal/ssa/compile.go | 4 +- src/cmd/compile/internal/ssa/cse.go | 17 +- src/cmd/compile/internal/ssa/debug.go | 38 +-- src/cmd/compile/internal/ssa/debug_test.go | 2 +- src/cmd/compile/internal/ssa/expand_calls.go | 38 +-- src/cmd/compile/internal/ssa/func.go | 17 +- src/cmd/compile/internal/ssa/fuse.go | 18 +- .../internal/ssa/fuse_branchredirect.go | 27 +- .../compile/internal/ssa/fuse_comparisons.go | 20 +- src/cmd/compile/internal/ssa/location.go | 20 +- src/cmd/compile/internal/ssa/loopbce.go | 28 +- src/cmd/compile/internal/ssa/looprotate.go | 24 +- src/cmd/compile/internal/ssa/magic.go | 6 +- src/cmd/compile/internal/ssa/op.go | 19 +- src/cmd/compile/internal/ssa/phielim.go | 16 +- src/cmd/compile/internal/ssa/phiopt.go | 22 +- src/cmd/compile/internal/ssa/poset.go | 10 +- src/cmd/compile/internal/ssa/prove.go | 40 +-- src/cmd/compile/internal/ssa/rewrite.go | 17 +- .../compile/internal/ssa/rewriteCond_test.go | 6 +- src/cmd/compile/internal/ssa/schedule.go | 16 +- src/cmd/compile/internal/ssa/shift_test.go | 2 +- src/cmd/compile/internal/ssa/shortcircuit.go | 30 +- src/cmd/compile/internal/ssa/sparsetree.go | 1 + src/cmd/compile/internal/ssa/trim.go | 10 +- src/cmd/compile/internal/ssagen/ssa.go | 8 +- src/cmd/compile/internal/syntax/branches.go | 8 +- src/cmd/compile/internal/typecheck/const.go | 3 +- src/cmd/compile/internal/typecheck/expr.go | 8 +- src/cmd/compile/internal/typecheck/iexport.go | 2 +- src/cmd/compile/internal/typecheck/syms.go | 3 +- .../compile/internal/typecheck/typecheck.go | 12 +- src/cmd/compile/internal/types/type.go | 13 +- src/cmd/compile/internal/types2/api.go | 1 - src/cmd/compile/internal/types2/check_test.go | 2 +- src/cmd/compile/internal/types2/infer.go | 8 +- src/cmd/compile/internal/types2/lookup.go | 16 +- src/cmd/compile/internal/types2/selection.go | 7 +- src/cmd/compile/internal/types2/sizes.go | 26 +- src/cmd/compile/internal/types2/typeterm.go | 8 +- src/cmd/compile/internal/walk/assign.go | 78 ++--- src/cmd/compile/internal/walk/builtin.go | 26 +- src/cmd/compile/internal/walk/compare.go | 6 +- src/cmd/compile/internal/walk/expr.go | 3 +- src/cmd/compile/internal/walk/order.go | 30 +- src/cmd/compile/internal/walk/stmt.go | 3 +- src/cmd/compile/internal/x86/ssa.go | 4 +- src/cmd/cover/cover.go | 2 +- src/cmd/cover/cover_test.go | 4 +- src/cmd/cover/doc.go | 1 + src/cmd/dist/build.go | 2 + src/cmd/dist/doc.go | 20 +- src/cmd/doc/doc_test.go | 6 + src/cmd/doc/main.go | 5 + src/cmd/fix/cftype.go | 8 +- src/cmd/fix/doc.go | 3 +- src/cmd/fix/egltype.go | 16 +- src/cmd/fix/jnitype.go | 8 +- src/cmd/go/internal/generate/generate_test.go | 20 +- src/cmd/go/internal/imports/build.go | 21 +- src/cmd/go/internal/load/test.go | 6 +- .../internal/lockedfile/lockedfile_plan9.go | 6 +- src/cmd/go/internal/modget/get.go | 8 +- src/cmd/go/internal/modload/buildlist.go | 84 +++--- src/cmd/go/internal/modload/edit.go | 22 +- src/cmd/go/internal/modload/load.go | 16 +- src/cmd/go/internal/modload/query.go | 28 +- src/cmd/go/internal/robustio/robustio.go | 6 +- src/cmd/go/internal/str/str.go | 2 + src/cmd/go/internal/test/testflag.go | 1 + src/cmd/go/internal/work/build.go | 6 + src/cmd/go/internal/work/exec.go | 1 + src/cmd/gofmt/doc.go | 8 +- src/cmd/internal/bio/buf_mmap.go | 12 +- src/cmd/internal/gcprog/gcprog.go | 3 +- src/cmd/internal/goobj/objfile.go | 63 ++-- src/cmd/internal/obj/arm64/doc.go | 201 +++++++------ src/cmd/internal/obj/inl.go | 38 +-- src/cmd/internal/obj/objfile.go | 16 +- src/cmd/internal/obj/ppc64/doc.go | 151 +++++----- src/cmd/internal/obj/ppc64/obj9.go | 1 + src/cmd/internal/obj/x86/asm6.go | 37 +-- src/cmd/internal/obj/x86/evex.go | 9 +- src/cmd/internal/src/pos.go | 6 +- src/cmd/internal/test2json/test2json.go | 2 +- src/cmd/link/doc.go | 4 +- src/cmd/link/internal/benchmark/bench.go | 40 +-- src/cmd/link/internal/ld/asmb.go | 10 +- src/cmd/link/internal/ld/data.go | 2 +- src/cmd/link/internal/ld/deadcode.go | 15 +- src/cmd/link/internal/ld/decodesym.go | 1 + src/cmd/link/internal/ld/dwarf_test.go | 4 +- src/cmd/link/internal/ld/elf.go | 53 ++-- src/cmd/link/internal/ld/outbuf.go | 44 +-- src/cmd/link/internal/ld/pcln.go | 24 +- src/cmd/link/internal/ld/xcoff.go | 4 +- src/cmd/link/internal/loader/loader.go | 26 +- src/cmd/nm/doc.go | 2 +- src/cmd/pack/doc.go | 3 +- src/cmd/test2json/main.go | 3 +- src/cmd/trace/annotations_test.go | 6 +- src/cmd/trace/doc.go | 20 +- src/cmd/vet/doc.go | 52 ++-- src/compress/flate/dict_decoder.go | 24 +- src/compress/flate/huffman_bit_writer.go | 12 +- src/container/heap/heap.go | 1 - src/container/list/list.go | 2 +- src/context/context.go | 16 +- src/crypto/cipher/gcm.go | 9 +- src/crypto/crypto.go | 14 +- .../ed25519/internal/edwards25519/doc.go | 2 +- .../edwards25519/field/fe_alias_test.go | 4 +- .../ed25519/internal/edwards25519/scalar.go | 22 +- .../elliptic/internal/fiat/p224_fiat64.go | 123 +++++--- .../elliptic/internal/fiat/p384_fiat64.go | 123 +++++--- .../elliptic/internal/fiat/p521_fiat64.go | 123 +++++--- src/crypto/elliptic/p256.go | 50 ++-- src/crypto/rand/rand_linux.go | 6 +- src/crypto/rsa/pkcs1v15.go | 10 +- src/crypto/rsa/pkcs1v15_test.go | 3 +- src/crypto/tls/cipher_suites.go | 54 ++-- src/crypto/tls/conn.go | 6 +- src/crypto/x509/pkcs8_test.go | 15 +- src/crypto/x509/pkix/pkix.go | 14 +- src/crypto/x509/sec1.go | 6 +- src/crypto/x509/x509.go | 108 ++++--- src/crypto/x509/x509_test.go | 18 +- src/database/sql/driver/driver.go | 32 ++- src/database/sql/driver/types.go | 30 +- src/database/sql/fakedb_test.go | 21 +- src/database/sql/sql.go | 82 +++--- src/debug/dwarf/entry.go | 31 +- src/embed/embed.go | 9 +- src/encoding/binary/varint.go | 12 +- src/encoding/gob/debug.go | 27 ++ src/encoding/gob/decoder.go | 2 + src/encoding/gob/doc.go | 6 +- src/encoding/json/encode.go | 30 +- src/encoding/json/fold.go | 5 +- src/encoding/pem/pem.go | 10 +- src/encoding/xml/marshal.go | 60 ++-- src/encoding/xml/read.go | 88 +++--- src/expvar/expvar.go | 2 +- src/flag/flag.go | 23 +- src/fmt/doc.go | 48 +++- src/go/ast/ast.go | 1 - src/go/build/build.go | 22 +- src/go/build/deps_test.go | 10 +- src/go/build/doc.go | 9 +- src/go/constant/value.go | 33 ++- src/go/doc/comment/parse.go | 2 + src/go/doc/comment/print.go | 2 +- src/go/doc/example.go | 8 +- src/go/format/benchmark_test.go | 21 +- src/go/internal/gccgoimporter/parser.go | 26 +- src/go/parser/parser.go | 1 - src/go/printer/nodes.go | 56 ++-- src/go/scanner/scanner.go | 1 - src/go/token/token.go | 1 - src/go/types/api.go | 1 - src/go/types/check_test.go | 2 +- src/go/types/gotype.go | 4 +- src/go/types/infer.go | 10 +- src/go/types/lookup.go | 16 +- src/go/types/selection.go | 7 +- src/go/types/sizes.go | 26 +- src/go/types/typeterm.go | 8 +- src/hash/adler32/adler32.go | 1 + src/hash/maphash/maphash.go | 7 +- src/html/template/context.go | 4 +- src/html/template/doc.go | 132 ++++----- src/html/template/error.go | 17 +- src/html/template/escape.go | 12 +- src/html/template/html.go | 10 +- src/html/template/template.go | 2 + src/html/template/url.go | 18 +- src/image/image.go | 2 + src/image/jpeg/writer.go | 28 +- src/image/png/reader.go | 4 +- src/image/ycbcr.go | 1 + src/index/suffixarray/suffixarray.go | 1 - src/internal/fmtsort/sort.go | 24 +- src/internal/nettrace/nettrace.go | 3 +- src/internal/profile/legacy_profile.go | 30 +- src/internal/reflectlite/export_test.go | 2 + src/internal/reflectlite/type.go | 3 +- src/internal/syscall/windows/registry/key.go | 1 - src/internal/txtar/archive.go | 8 +- src/log/log.go | 11 +- src/log/syslog/doc.go | 2 +- src/math/abs.go | 1 + src/math/acosh.go | 1 + src/math/asin.go | 2 + src/math/asinh.go | 1 + src/math/atan.go | 5 +- src/math/atan2.go | 1 + src/math/atanh.go | 1 + src/math/big/example_rat_test.go | 9 +- src/math/big/float.go | 8 +- src/math/big/floatconv.go | 18 +- src/math/big/int.go | 40 +-- src/math/big/nat.go | 2 +- src/math/big/natconv.go | 17 +- src/math/big/rat.go | 6 +- src/math/big/sqrt.go | 4 +- src/math/cbrt.go | 1 + src/math/cmplx/pow.go | 1 + src/math/dim.go | 3 + src/math/erf.go | 2 + src/math/erfinv.go | 2 + src/math/exp.go | 2 + src/math/expm1.go | 2 + src/math/floor.go | 5 + src/math/frexp.go | 1 + src/math/gamma.go | 1 + src/math/hypot.go | 1 + src/math/j0.go | 2 + src/math/j1.go | 2 + src/math/jn.go | 2 + src/math/ldexp.go | 1 + src/math/lgamma.go | 1 + src/math/log.go | 1 + src/math/log1p.go | 1 + src/math/logb.go | 2 + src/math/mod.go | 1 + src/math/modf.go | 1 + src/math/nextafter.go | 2 + src/math/pow.go | 1 + src/math/pow10.go | 1 + src/math/rand/exp.go | 2 +- src/math/rand/normal.go | 2 +- src/math/rand/rand.go | 4 +- src/math/remainder.go | 1 + src/math/sin.go | 2 + src/math/sincos.go | 1 + src/math/sinh.go | 2 + src/math/sqrt.go | 1 + src/math/tan.go | 1 + src/math/tanh.go | 1 + src/math/trig_reduce.go | 2 + src/mime/multipart/multipart.go | 3 +- src/mime/type.go | 10 +- src/net/cgo_unix.go | 12 +- src/net/conf.go | 16 +- src/net/dial.go | 3 + src/net/http/cgi/host.go | 9 +- src/net/http/client.go | 46 +-- src/net/http/cookie.go | 12 +- src/net/http/cookiejar/jar.go | 6 +- src/net/http/cookiejar/jar_test.go | 19 +- src/net/http/doc.go | 1 - src/net/http/filetransport.go | 10 +- src/net/http/fs.go | 2 +- src/net/http/h2_bundle.go | 18 +- src/net/http/httptest/server.go | 2 + src/net/http/internal/chunked.go | 9 +- src/net/http/pprof/pprof.go | 8 +- src/net/http/request.go | 9 +- src/net/http/response.go | 21 +- src/net/http/serve_test.go | 9 +- src/net/http/server.go | 5 +- src/net/http/transfer.go | 9 +- src/net/http/transport_test.go | 1 + src/net/ipsock_posix.go | 38 +-- src/net/mac.go | 1 + src/net/mail/message.go | 12 +- src/net/net.go | 3 +- src/net/netip/slow_test.go | 36 +-- src/net/rpc/server.go | 19 +- src/net/smtp/smtp.go | 4 +- src/net/sock_linux.go | 4 +- src/net/textproto/reader.go | 22 +- src/net/textproto/textproto.go | 1 - src/net/url/url.go | 27 +- src/os/file.go | 1 - src/os/file_windows.go | 7 +- src/os/signal/doc.go | 17 +- src/path/filepath/path.go | 14 +- src/path/filepath/path_windows_test.go | 16 +- src/path/filepath/symlink_windows.go | 11 +- src/path/path.go | 12 +- src/reflect/makefunc.go | 6 +- src/reflect/type.go | 1 + src/reflect/value.go | 4 +- src/regexp/regexp.go | 18 +- src/regexp/syntax/doc.go | 185 ++++++------ src/regexp/syntax/parse.go | 11 +- src/runtime/chan.go | 5 +- src/runtime/chan_test.go | 2 + src/runtime/debug.go | 5 +- src/runtime/debug/garbage.go | 4 +- src/runtime/error.go | 3 +- src/runtime/extern.go | 6 +- src/runtime/float.go | 1 + src/runtime/lock_sema.go | 3 +- src/runtime/map_test.go | 5 +- src/runtime/mbitmap.go | 3 +- src/runtime/metrics/doc.go | 8 +- src/runtime/mfinal.go | 21 +- src/runtime/mgc.go | 2 +- src/runtime/mgcwork.go | 6 +- src/runtime/mheap.go | 16 +- src/runtime/mpagealloc_64bit.go | 3 +- src/runtime/mwbbuf.go | 10 +- src/runtime/netpoll.go | 21 +- src/runtime/os_linux.go | 2 + src/runtime/pprof/pprof.go | 64 ++--- src/runtime/pprof/proto.go | 14 +- src/runtime/proc.go | 16 +- src/runtime/stack.go | 4 +- src/runtime/string.go | 8 +- src/runtime/symtab_test.go | 16 +- src/runtime/sys_darwin.go | 5 +- src/runtime/time.go | 3 +- src/runtime/trace/annotation.go | 16 +- src/runtime/trace/trace.go | 65 +++-- src/runtime/type.go | 1 + src/runtime/vlrt.go | 11 +- src/strconv/atof.go | 2 + src/strconv/doc.go | 5 +- src/strconv/eisel_lemire.go | 4 +- src/strconv/ftoaryu.go | 10 +- src/strconv/quote.go | 8 +- src/strings/replace.go | 16 +- src/strings/strings.go | 14 +- src/sync/atomic/atomic_test.go | 3 +- src/sync/cond.go | 12 +- src/sync/once.go | 7 +- src/syscall/exec_windows.go | 10 +- src/syscall/js/js.go | 20 +- src/syscall/syscall.go | 1 - src/syscall/syscall_js.go | 1 + src/syscall/syscall_unix.go | 1 + src/testing/fuzz.go | 2 +- src/testing/quick/quick.go | 18 +- src/testing/testing.go | 272 +++++++++--------- src/text/template/funcs.go | 2 + src/text/template/helper.go | 1 + src/text/template/option.go | 1 + src/text/template/parse/parse.go | 30 ++ src/time/format.go | 8 + src/time/sleep.go | 14 +- src/time/time.go | 8 +- src/time/zoneinfo.go | 8 +- src/unicode/graphic.go | 2 + src/unicode/letter.go | 3 + src/unsafe/unsafe.go | 9 +- 373 files changed, 3242 insertions(+), 2536 deletions(-) diff --git a/misc/cgo/gmp/gmp.go b/misc/cgo/gmp/gmp.go index 971a10aaac644..0835fdc8dea66 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/ios/go_ios_exec.go b/misc/ios/go_ios_exec.go index 34a734cda780e..c275dd339cc9b 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 c99b5c19207f7..f6d701d925cba 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 4b11909bc9527..f1b35c34f6f25 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 275db6f0263f2..ac3196370e611 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/builtin/builtin.go b/src/builtin/builtin.go index 8997902f8f997..e3e4df9de6818 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 979cf1ccf075b..2a00ce33546b1 100644 --- a/src/bytes/bytes.go +++ b/src/bytes/bytes.go @@ -372,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) } @@ -383,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) } diff --git a/src/cmd/addr2line/main.go b/src/cmd/addr2line/main.go index 018802940b24f..6e005a8fac96d 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 4a0c785aad419..098f06390901d 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 59aedbf0cc59f..acd03e139946d 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/buildid/doc.go b/src/cmd/buildid/doc.go index d1ec155c97617..a554d798c062b 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 a6787f6405011..4c62c5d70e43b 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 9877182fc4b82..a52163fd6567d 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/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 9628ce5644022..2dae55ba864d5 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 { diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index be01914d08011..8c2ea49c8f46d 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/expr.go b/src/cmd/compile/internal/ir/expr.go index b5c0983d6a3a8..4f1f582fa1f03 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 5e5868abb2a71..24908f3a13978 100644 --- a/src/cmd/compile/internal/ir/node.go +++ b/src/cmd/compile/internal/ir/node.go @@ -551,7 +551,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/liveness/plive.go b/src/cmd/compile/internal/liveness/plive.go index 3202e506c8f91..bd0a6fa1a3308 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/noder/unified.go b/src/cmd/compile/internal/noder/unified.go index 2c1f2362ad9bc..e7a4001cec2ab 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 40f14082600c3..32e95bedc23e5 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/reflectdata/alg.go b/src/cmd/compile/internal/reflectdata/alg.go index 526315d5570c3..9fe90da0fe558 100644 --- a/src/cmd/compile/internal/reflectdata/alg.go +++ b/src/cmd/compile/internal/reflectdata/alg.go @@ -681,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) @@ -690,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) { @@ -714,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) { @@ -750,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/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index 9961c8f65a466..a8d81b9a21592 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 @@ -1794,13 +1794,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/s390x/ssa.go b/src/cmd/compile/internal/s390x/ssa.go index deb6c790069ca..8f9c157d9a96e 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 { diff --git a/src/cmd/compile/internal/ssa/addressingmodes.go b/src/cmd/compile/internal/ssa/addressingmodes.go index d600e31666eb7..c18ea68665735 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 diff --git a/src/cmd/compile/internal/ssa/block.go b/src/cmd/compile/internal/ssa/block.go index 4d21ade3e3b59..db7df3f3384e1 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 59773ef31b979..7a08654f4e1ef 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/compile.go b/src/cmd/compile/internal/ssa/compile.go index f95140eaf9ef1..5e898ab96f4a4 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/cse.go b/src/cmd/compile/internal/ssa/cse.go index ade5e0648e78d..f4b799394c0bb 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 08dc5c468ecd3..2c18d35204851 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 2fc12557c055d..c807863ea65bb 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 a3cea855f2fa4..b774ea78b1a3a 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 { diff --git a/src/cmd/compile/internal/ssa/func.go b/src/cmd/compile/internal/ssa/func.go index 0b5392f0f03de..35a93826634af 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 fec2ba877372e..2b176dfa7b2a7 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 27449db55afcd..59570968a2751 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 d843fc3fda1ea..f5fb84b0d7353 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/location.go b/src/cmd/compile/internal/ssa/location.go index d69db404ed3b8..00aea879363d6 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 206aab2c5ef5b..dd63541771d3c 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 -// -// enter_loop: -// do something -// nxt = inc + ind -// goto loop +// loop: +// ind = (Phi min nxt), +// if ind < max +// then goto enter_loop +// else goto exit_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 35010a78d8e04..2eefda1c8b366 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 93f8801bce49e..e903d92bb6190 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 a1835dcd3026e..a3e8dcd2f624f 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/phielim.go b/src/cmd/compile/internal/ssa/phielim.go index 761cb7a392fb7..4fc942375fdef 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 0357442ae9fbf..037845eacf2db 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 200106e66db42..a3b4f0fea4889 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 +746,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). diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index eb8fa0c02abe4..248060d27d993 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -962,8 +962,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 +986,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 +1792,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 diff --git a/src/cmd/compile/internal/ssa/rewriteCond_test.go b/src/cmd/compile/internal/ssa/rewriteCond_test.go index 2c26fdf1427e2..ca74ed59471de 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/schedule.go b/src/cmd/compile/internal/ssa/schedule.go index c5130b2ee50b9..170d8b70950f9 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 3876d8df12c26..06c2f6720ff7f 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 c0b9eacf4174d..5f1f8921207a4 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 732bb8e3214cd..9f4e0007d3a93 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 c930a205c17f3..1fd7b33d5f2d5 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/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 883772b341cef..bd0b9250196ab 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) diff --git a/src/cmd/compile/internal/syntax/branches.go b/src/cmd/compile/internal/syntax/branches.go index 56e97c71d8ace..607909742655f 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/typecheck/const.go b/src/cmd/compile/internal/typecheck/const.go index 1422ab003126f..a626c000be3e0 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 e6adc05a658b5..f0b7b74aedd5e 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 5d319eaca3632..12159b71e1bbd 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/syms.go b/src/cmd/compile/internal/typecheck/syms.go index 6c2e84680bc23..1f60f318510a9 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 85de653a82a6c..2eb9e6d718ceb 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 147194c369752..987352babc7d8 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 34bb29cadcce4..54cddaee28d0a 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/check_test.go b/src/cmd/compile/internal/types2/check_test.go index ec242c5e222be..2e1ae0d2bee89 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/infer.go b/src/cmd/compile/internal/types2/infer.go index 9f7e593eeb367..9e77d67a7de2e 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 93defd661852d..684bbf7a8bce5 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 ee63214407139..c820a29fad2c7 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 7a34b6474cd44..f530849a9d944 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/typeterm.go b/src/cmd/compile/internal/types2/typeterm.go index 3d82a37ab8347..97791324e1e75 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 9b09e097fac68..c44d934f21ee5 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 7ec5494d99686..d7b553ed0c492 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 625e216050375..993f1392aa1e1 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 4c1e7adddd7ea..26a23c4d0910c 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 cc37f95764882..80806478beb55 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 f09e9165464bc..4f38cb2c81d23 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 diff --git a/src/cmd/compile/internal/x86/ssa.go b/src/cmd/compile/internal/x86/ssa.go index 32e29f347b9c7..12d9d0f365e1c 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 { diff --git a/src/cmd/cover/cover.go b/src/cmd/cover/cover.go index 9c8529f7eb600..86ef128f2cccb 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 8bd31514a0499..28be231121710 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 e2c849419abc8..e091ce9e30084 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 565efc91c6a43..bbaf595421d0e 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -1225,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 diff --git a/src/cmd/dist/doc.go b/src/cmd/dist/doc.go index a4e6aa5cbfd6f..ad26aa2dc06ee 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/doc/doc_test.go b/src/cmd/doc/doc_test.go index ead4f722f60b2..5887ad3395b19 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 dee5d7bbcd714..3c45dd76dfa38 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/fix/cftype.go b/src/cmd/fix/cftype.go index 27e4088aa9fa7..e4988b1c625b0 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 0570169576b62..062eb79285628 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 cb0f7a73de1dd..a096db6665a5f 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 29abe0f007830..111be8e70c6be 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/internal/generate/generate_test.go b/src/cmd/go/internal/generate/generate_test.go index b546218a3c589..15b1279f36bd7 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/imports/build.go b/src/cmd/go/internal/imports/build.go index 10e90fc216a72..53fa1967f742e 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/load/test.go b/src/cmd/go/internal/load/test.go index 39f1131a4379b..3780f358f4b3d 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 35669388e0531..a2ce794b96752 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/modget/get.go b/src/cmd/go/internal/modget/get.go index 751f15aaacb7b..08a474f61b3ab 100644 --- a/src/cmd/go/internal/modget/get.go +++ b/src/cmd/go/internal/modget/get.go @@ -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 6f9072c8c48c3..5b8d6051f3226 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 0f37e3b2e9477..c556664c35156 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 c17069953547c..e85a33dd50666 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 33808ea1097a7..27af78d99eb73 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/robustio/robustio.go b/src/cmd/go/internal/robustio/robustio.go index ce3dbbde6db93..15b33773cf5f5 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/str/str.go b/src/cmd/go/internal/str/str.go index 021bfbff77916..975869d760165 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/test/testflag.go b/src/cmd/go/internal/test/testflag.go index c046caca25d59..f3cd0b1392f00 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/work/build.go b/src/cmd/go/internal/work/build.go index 42f052d341085..e63e209a142d9 100644 --- a/src/cmd/go/internal/work/build.go +++ b/src/cmd/go/internal/work/build.go @@ -532,16 +532,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 diff --git a/src/cmd/go/internal/work/exec.go b/src/cmd/go/internal/work/exec.go index 0c8e1dcdafd12..9c9d58b2a178d 100644 --- a/src/cmd/go/internal/work/exec.go +++ b/src/cmd/go/internal/work/exec.go @@ -1884,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/gofmt/doc.go b/src/cmd/gofmt/doc.go index e34066559415e..8ac9c6a931711 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 b9755c7e50e8b..89ae39f736abb 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 c8bf20646870d..eeea53daf4a5e 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 af2a0df338ba3..3e36c461fa482 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/doc.go b/src/cmd/internal/obj/arm64/doc.go index 2763cf4139af1..c12f618e93910 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 -(10) TBNZ, TBZ $, ,

+// +//
+// // 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 650e7147a3668..5d1631b266361 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 6bb5a2027f677..5c51f772cbde3 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 2b4027348ae28..54fbcdca33354 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 a99f69231cbe6..30b64dff04087 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 93905586a2f41..9d0be39022505 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/image.go b/src/image/image.go index 930d9ac6c7bac..dfb70d4eaf627 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 a600499004725..0027f78294a25 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 4c65038cb5bd5..95b507cf68ceb 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 328b90d152565..78f5ebe1d8161 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 cdc6e81a800cb..7fca0fd7ba997 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/fmtsort/sort.go b/src/internal/fmtsort/sort.go index 09e987d0e6f51..278a89bd75d42 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/nettrace/nettrace.go b/src/internal/nettrace/nettrace.go index 6e0dbe73bbff5..0a2bf925e9a7f 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/profile/legacy_profile.go b/src/internal/profile/legacy_profile.go index 377a43d5858f0..fee420986eee4 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 adae229e92c3c..3e5c258fb177b 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 34677b400ec92..bc6fc947738f4 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/syscall/windows/registry/key.go b/src/internal/syscall/windows/registry/key.go index ec38cf9288a6f..ce6397f1e29c7 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/txtar/archive.go b/src/internal/txtar/archive.go index 214256617b58d..81b31454512a1 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/log/log.go b/src/log/log.go index 5e79b1952241e..f7e48d5599b3e 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 bd12bea581f52..9a33eeb5d5764 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/math/abs.go b/src/math/abs.go index df83add69561d..08be14548dd77 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 f74e0b62fbaa6..a85d003d3eabe 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 989a74155be28..8e1b2ab4916ed 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 6dcb241c1fefa..6f6e9e460828d 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 69af8601614d4..e722e99757fd2 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 11d7e81acdf93..c324ed0a1578a 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 fe8bd6d8a4335..9d594625a5c04 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/example_rat_test.go b/src/math/big/example_rat_test.go index a97117001c0b5..dc67430980172 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 70c7b794a4197..84666d817bb56 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 30b6dc4332e92..3bb51c7dea862 100644 --- a/src/math/big/floatconv.go +++ b/src/math/big/floatconv.go @@ -229,15 +229,15 @@ 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. diff --git a/src/math/big/int.go b/src/math/big/int.go index a111451eaf6c2..a31cf27418f76 100644 --- a/src/math/big/int.go +++ b/src/math/big/int.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) } @@ -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/nat.go b/src/math/big/nat.go index ee0c63eb28105..5cc42b80dc772 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 8fdf4b6f9e343..21fdab53fd46c 100644 --- a/src/math/big/natconv.go +++ b/src/math/big/natconv.go @@ -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 @@ -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 e77da67d1b9a1..700a6432659c4 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/sqrt.go b/src/math/big/sqrt.go index 0d50164557c5a..b4b03743f4da7 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/cbrt.go b/src/math/cbrt.go index 45c8ecb3a8cf2..e5e9548cb1f39 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/pow.go b/src/math/cmplx/pow.go index 5a405f8e9607f..666bba28c5b59 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/dim.go b/src/math/dim.go index 6a857bbe41fc0..6a286cdc75a46 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 4d6fe472f1af9..ba00c7d03edc1 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 ee423d33e4272..eed0feb42ddee 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 d05eb91fb004d..760795f46feef 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 66d3421661ea1..ff1c82f524143 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 7913a900e37c8..cb5856424b4e7 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 3c8a909ed0cf4..e194947e646af 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 cc9e869496b11..86c6723258015 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 12af17766d144..4e79de0e9bc3e 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 cb5f07bca63f5..a311e18d62d4d 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 7c7d279730dc6..cc19e75b95002 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 b1aca8ff6be74..3491692a96ca7 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 55c82f1e84203..df365c0b1ac3a 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 7af5871744bc0..4058ad6631eb2 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 1b3e306adfcb1..695a545e7f00e 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 c117f7245db84..3a7b3854a8103 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 f2769d4fd7544..04ba3e968e730 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 6bc5f28832547..6f24250cfb461 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 bf08dc65568b7..613a75fc9a686 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 9088e4d248a9c..ec18d542d9c03 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 e45a044ae1cc0..3af8c8b649b9b 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 1234e208850ac..c31ad8dbc778f 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 9a07ba1be00a5..c1162c19b6880 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 48ecdd5adbdd7..6654479a0052c 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 dfbd1fa4e7c84..4cce3dab640a3 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 bf8bfd5553a7a..8e99345c59b79 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 d95bb548e8da0..4793d7e7cd63d 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 5c5726f6898a7..e3fb96094fa75 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 9fe9b4e17a15a..78b3c299d6689 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 903d57d5e0d06..b6d80c2c6f8e5 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 a25417f527dd4..515dd82f73101 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 a825678424648..94ebc3b6515d5 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 5cdf4fa01379e..5ecdd8375e329 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 c7bcb4d121971..aa05ac8f9c8a5 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/type.go b/src/mime/type.go index bdb8bb319af39..465ecf0d599cc 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/net/cgo_unix.go b/src/net/cgo_unix.go index b156b198ee28a..71d90560ac921 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 716a37ff806cc..9d4752173e1af 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 9159e6b38447f..b24bd2f5f43e5 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/http/cgi/host.go b/src/net/http/cgi/host.go index 95b2e13e4efb8..0d43e140d5689 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 bc0ed1fc50608..490349f7bd01a 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 6e1035330b4fc..9cb0804f8f227 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 e6583da7fe67d..309dfcc0e1716 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 47fb1abdaafa4..b7267b17184cc 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 ae9b708c695bf..67c4246c6080f 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/filetransport.go b/src/net/http/filetransport.go index 32126d7ec0f6c..94684b07a1395 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 d8f924296bc75..7a1d5f4be5f6e 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 e955135c50f3c..4a76e6afe8ce1 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/server.go b/src/net/http/httptest/server.go index 1c0c0f6987569..f254a494d162d 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 37a72e9031a11..5a174415dc401 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 dc855c8a6da9a..de5a4b9752a24 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 dbe947aec44b4..654505d81915b 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -516,6 +516,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 +740,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/response.go b/src/net/http/response.go index 297394eabebed..eb4cd9b0adbf0 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,15 +231,15 @@ 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 { diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index fb18cb2c6f5b2..435f828871a3a 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -5077,10 +5077,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 77e0108426e0a..62bdf1695974e 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 diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go index 6d51178ee9082..d9edf8c725947 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/transport_test.go b/src/net/http/transport_test.go index 84065c70851eb..6fcb458296fdb 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -3439,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 2c724478481e7..9a961b96ab252 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 373ac3d7e2018..53d5b2dbf596b 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 61a3a26b012a4..c91aa3af12467 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 ec718d5e43246..7a97b9dcfd2a6 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 5b46a39a83448..d7c802516416d 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 0b3e6e3c58bac..109ebba5413e5 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 c1f00a04e1f9e..3bd2061b0c907 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 9f62ed3deed37..2513f9ba7b977 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 65974f9cc21ba..1f7afc57665cd 100644 --- a/src/net/textproto/reader.go +++ b/src/net/textproto/reader.go @@ -215,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, @@ -251,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}. @@ -600,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] } diff --git a/src/net/textproto/textproto.go b/src/net/textproto/textproto.go index 3487a7dfaf438..70038d58886a3 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 f85bdb1580435..58b30411a4c70 100644 --- a/src/net/url/url.go +++ b/src/net/url/url.go @@ -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 != "" { @@ -1207,10 +1207,11 @@ func (u *URL) JoinPath(elem ...string) *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/os/file.go b/src/os/file.go index ea64a662cc2b3..ab017d4af7906 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_windows.go b/src/os/file_windows.go index ab5d6a493de90..db5c27dd30f41 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -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/signal/doc.go b/src/os/signal/doc.go index 7af61d2d81746..ab262edc58a2e 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/path/filepath/path.go b/src/path/filepath/path.go index 668b87bb24b34..ec9e6d8a1f861 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. diff --git a/src/path/filepath/path_windows_test.go b/src/path/filepath/path_windows_test.go index 76a459ac96b39..37019210fa034 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 d72279e2bbc1a..9a436d59780d9 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 5c5bc445ac080..547b9debce14c 100644 --- a/src/path/path.go +++ b/src/path/path.go @@ -52,12 +52,12 @@ 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 "/". // diff --git a/src/reflect/makefunc.go b/src/reflect/makefunc.go index 0a680765cda77..ee0729903e38c 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. diff --git a/src/reflect/type.go b/src/reflect/type.go index 53c17f9e552f5..e8882664750c5 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 c5c212ea364d4..2496cbe463667 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,7 +1827,7 @@ func (iter *MapIter) Reset(v Value) { // Example: // // iter := reflect.ValueOf(m).MapRange() -// for iter.Next() { +// for iter.Next() { // k := iter.Key() // v := iter.Value() // ... diff --git a/src/regexp/regexp.go b/src/regexp/regexp.go index 26ac5f48b230b..7958a397285d3 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 b3f9136b5f319..f6a4b43f7aebb 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 + + 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) + 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 ebf8e11915d8d..cfb703d2855a2 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/runtime/chan.go b/src/runtime/chan.go index 308667d7bc986..6511d80c2c45e 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -596,10 +596,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 9471d4596c2e1..1e0aa53213e74 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 diff --git a/src/runtime/debug.go b/src/runtime/debug.go index 2703a0ce019d3..0ab23e0eb79cc 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 00f92c3ddfb69..ce4bb10407f8d 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/error.go b/src/runtime/error.go index 43114f092e1cd..b11473c634624 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/extern.go b/src/runtime/extern.go index 39bdd09849e59..9dd59e0985b68 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 @@ -172,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 7aef78a2ecc73..c80c8b7abfb0c 100644 --- a/src/runtime/float.go +++ b/src/runtime/float.go @@ -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/lock_sema.go b/src/runtime/lock_sema.go index 6961c2ea9bc87..c5e8cfe24a7f1 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/map_test.go b/src/runtime/map_test.go index 0c83dd4ddf8b4..5c458b4a4930f 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/mbitmap.go b/src/runtime/mbitmap.go index 665a9c6f63ece..a3a6590d65f17 100644 --- a/src/runtime/mbitmap.go +++ b/src/runtime/mbitmap.go @@ -99,10 +99,9 @@ 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) diff --git a/src/runtime/metrics/doc.go b/src/runtime/metrics/doc.go index 91ef03072de49..63bea8c4488eb 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 979e0b4a2c7bc..bf537b417cb88 100644 --- a/src/runtime/mfinal.go +++ b/src/runtime/mfinal.go @@ -447,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 ba679e0af57e9..9f17e474889a3 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/mgcwork.go b/src/runtime/mgcwork.go index 5c47006cc286f..424de2fcca34f 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 diff --git a/src/runtime/mheap.go b/src/runtime/mheap.go index a8a1e61ef226a..d99363d9913d8 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. diff --git a/src/runtime/mpagealloc_64bit.go b/src/runtime/mpagealloc_64bit.go index 1bacfbe0fa511..76b54baa5566d 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/mwbbuf.go b/src/runtime/mwbbuf.go index 78d9382620bed..39ce0b46a90a6 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 6dcc60953f3a6..ac6bc89530ed7 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 diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 812a0b4ad3040..a6e7a331915c9 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -52,7 +52,9 @@ const ( ) // Atomically, +// // if(*addr == val) sleep +// // Might be woken up spuriously; that's allowed. // Don't sleep longer than ns; ns < 0 means forever. // diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go index e3cd6b9d2a265..f0b25c131fe8d 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/proto.go b/src/runtime/pprof/proto.go index 68dac42d20011..f0769935ae801 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 ae4440786e4fc..4535f620532bd 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -1664,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 @@ -5602,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. diff --git a/src/runtime/stack.go b/src/runtime/stack.go index 54a02173c385a..b7df231722326 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 bef097c87ed40..8b20c93fd780f 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), diff --git a/src/runtime/symtab_test.go b/src/runtime/symtab_test.go index 79a114b02b96e..1a0c55af9766e 100644 --- a/src/runtime/symtab_test.go +++ b/src/runtime/symtab_test.go @@ -206,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 ea81fd4f46d67..1547fdceb0ca0 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -233,11 +233,10 @@ func closefd(fd int32) int32 { } func close_trampoline() -//go:nosplit -//go:cgo_unsafe_args -// // This is exported via linkname to assembly in runtime/cgo. // +//go:nosplit +//go:cgo_unsafe_args //go:linkname exit func exit(code int32) { libcCall(unsafe.Pointer(abi.FuncPCABI0(exit_trampoline)), unsafe.Pointer(&code)) diff --git a/src/runtime/time.go b/src/runtime/time.go index 3ff3b668c00ff..e4d8269987198 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -225,8 +225,9 @@ func stopTimer(t *timer) bool { // 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)) diff --git a/src/runtime/trace/annotation.go b/src/runtime/trace/annotation.go index bf3dbc3d797dc..9171633b07e52 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 b34aef03c511a..e0c3ca7a1e75b 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) -// -// 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) -// }() -// +// 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) +// }() // // 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 a00394f3b3907..44f36a85cadf4 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 diff --git a/src/runtime/vlrt.go b/src/runtime/vlrt.go index 1dcb125aef548..4b12f593c8a8e 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/strconv/atof.go b/src/strconv/atof.go index 57556c704751b..60098efed0acf 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 8db725f96ae5c..769ecd9a21ca9 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 fecd1b93451d0..03842e50797ca 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 f2e74bed17794..b975cdc9b92e7 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 6c022846c0812..1b5bddfeaea7b 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/replace.go b/src/strings/replace.go index ee728bb22b655..73bc78a07ec39 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 8294f7ec35547..a563f37cf59c1 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. diff --git a/src/sync/atomic/atomic_test.go b/src/sync/atomic/atomic_test.go index 8a53094cb787e..09f93a4fe32c2 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 d86ebc8b5070e..841be96896e11 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 e5ba257d875bf..38373160b9ffc 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/syscall/exec_windows.go b/src/syscall/exec_windows.go index 41e58d4355a92..92464e089c5e8 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 a5210faf7fd26..2f4f5adda0273 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 98e30052531a5..62bfa449cff5f 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 cd9549906360b..c9c65229804c5 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_unix.go b/src/syscall/syscall_unix.go index 56d21b4ec1309..e12f024fe75bd 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 b5e1339debd22..b9f3a3d159801 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 e73d307c13a7c..95a635badec19 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 badc159159ec6..1f701e0b217b7 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 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) -// } -// }) -// } +// 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) -// }) -// // -// } -// -// Main +// 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 // // 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 1f63b361f88bb..390d47ebbb65c 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 57905e613a436..48af3928b39db 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 8d7d436bd079d..ea2fd80c06983 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/parse.go b/src/text/template/parse/parse.go index ce548b08865d8..67e2f5b2f4ab1 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 95fe08b772431..2f66df668b743 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 1ffaabec6749b..cdab4782ada3a 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/time.go b/src/time/time.go index 7dc1e49bc17e1..95963b6bf319a 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 ( @@ -1379,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) { @@ -1396,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/zoneinfo.go b/src/time/zoneinfo.go index 9bcb183d77bbd..b3313583d8945 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 ca6241949a23f..2af29778bf396 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 f4c950a883c94..f3f8e52964829 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 ae69dea4af794..da15902b29359 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. // From 0605bf6052807e71e52fc3864b18b221ce61b047 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Mon, 11 Apr 2022 17:19:09 -0400 Subject: [PATCH 061/137] go/ast, go/printer: recognize export and extern line directives Now that gofmt is reformatting these, we can't get away with not knowing about directives such as //export and //extern (for gccgo). Otherwise "//export foo" and "//extern foo" turn into "// export foo", and "// extern foo", which are completely different meanings. For #51082. Change-Id: Id0970331fa0b52ab5fa621631b5fa460767068bb Reviewed-on: https://go-review.googlesource.com/c/go/+/399734 Run-TryBot: Russ Cox TryBot-Result: Gopher Robot Auto-Submit: Russ Cox Reviewed-by: Ian Lance Taylor --- src/go/ast/ast.go | 4 +++- src/go/ast/ast_test.go | 3 +++ src/go/printer/comment.go | 4 +++- src/go/printer/testdata/comments.golden | 6 ++++++ src/go/printer/testdata/comments.input | 6 ++++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index 3ae5a60a1033b..1e089b9e70f03 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -161,8 +161,10 @@ func (g *CommentGroup) Text() string { // This code is also in go/printer. 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 ") { + if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { return true } diff --git a/src/go/ast/ast_test.go b/src/go/ast/ast_test.go index 71b2d6ca4b80e..66ae88486780e 100644 --- a/src/go/ast/ast_test.go +++ b/src/go/ast/ast_test.go @@ -68,6 +68,9 @@ var isDirectiveTests = []struct { {"go:", false}, {"go:*", false}, {"go:x*", true}, + {"export foo", true}, + {"extern foo", true}, + {"expert foo", false}, } func TestIsDirective(t *testing.T) { diff --git a/src/go/printer/comment.go b/src/go/printer/comment.go index 9749146739730..76dd31efc73a8 100644 --- a/src/go/printer/comment.go +++ b/src/go/printer/comment.go @@ -111,8 +111,10 @@ func formatDocComment(list []*ast.Comment) []*ast.Comment { // 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 ") { + if strings.HasPrefix(c, "line ") || strings.HasPrefix(c, "extern ") || strings.HasPrefix(c, "export ") { return true } diff --git a/src/go/printer/testdata/comments.golden b/src/go/printer/testdata/comments.golden index d03da3b65afd0..62f37ea091069 100644 --- a/src/go/printer/testdata/comments.golden +++ b/src/go/printer/testdata/comments.golden @@ -692,6 +692,12 @@ func _() { } } +//extern foo +func foo() {} + +//export bar +func bar() {} + // Print line directives correctly. // The following is a legal line directive. diff --git a/src/go/printer/testdata/comments.input b/src/go/printer/testdata/comments.input index 2a15fa44a5dd9..4bdafc3781de3 100644 --- a/src/go/printer/testdata/comments.input +++ b/src/go/printer/testdata/comments.input @@ -691,6 +691,12 @@ func _() { } } +//extern foo +func foo() {} + +//export bar +func bar() {} + // Print line directives correctly. // The following is a legal line directive. From 11c450fa58b1aa712d7202d71285ae1b0612d201 Mon Sep 17 00:00:00 2001 From: yangwenmai Date: Sun, 10 Apr 2022 16:04:22 +0800 Subject: [PATCH 062/137] A+C: add Wen Yang (individual CLA) Change-Id: Iaac18d78b4a11698d0b5f70b1d5143c0da8a6943 Reviewed-on: https://go-review.googlesource.com/c/go/+/399299 Reviewed-by: mzh Reviewed-by: Dmitri Shuralyov Run-TryBot: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- AUTHORS | 1 + CONTRIBUTORS | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 96704bd564f2c..e2c8150ab08ab 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 f79c4132b8943..ea8b1b964e17c 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 From ea7e3e3c0f561d1115d647e3e24ca61d0382e1ac Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 11 Apr 2022 14:56:11 -0700 Subject: [PATCH 063/137] runtime: align m.procid to 8 bytes on 32-bit systems https://go-review.googlesource.com/c/go/+/383434 started using atomic Load64 on this field, which breaks 32 bit platforms which require 64-bit alignment of uint64s that are passed to atomic operations. Not sure why this doesn't break everywhere, but I saw it break on my laptop during all.bash. Change-Id: Ida27b23068b3cc7208fce3c97b69a464ccf68209 Reviewed-on: https://go-review.googlesource.com/c/go/+/399754 Run-TryBot: Keith Randall Reviewed-by: Michael Pratt TryBot-Result: Gopher Robot Reviewed-by: Keith Randall --- src/runtime/runtime2.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/runtime/runtime2.go b/src/runtime/runtime2.go index b903cc8011102..b2c42d0e5c120 100644 --- a/src/runtime/runtime2.go +++ b/src/runtime/runtime2.go @@ -516,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 From ff14e844d26090e09aa335d836f737c09a7a0402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Soul=C3=A9?= Date: Thu, 17 Mar 2022 17:01:24 +0100 Subject: [PATCH 064/137] net/http/httptest: allow multiple fields be present in one Trailer field Fixes #51761 Change-Id: Ibaa17076ba51b666e25333e78180b8c7c4c940ec Reviewed-on: https://go-review.googlesource.com/c/go/+/393616 Reviewed-by: Damien Neil Run-TryBot: Damien Neil TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/net/http/httptest/recorder.go | 24 +++++++++++++----------- src/net/http/httptest/recorder_test.go | 3 +-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/net/http/httptest/recorder.go b/src/net/http/httptest/recorder.go index 1b712ef2b0a8e..1c1d8801558ed 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 8cb32dd740368..4782eced43e6c 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") From ec5e5dba6fbc4549f167c6db509a800e163296c8 Mon Sep 17 00:00:00 2001 From: zhouguangyuan Date: Sun, 20 Mar 2022 00:07:37 +0800 Subject: [PATCH 065/137] runtime: fix name of type parameter CL 372774 is for reflect, this CL is for _type in runtime. Add a test case to ensure the name method of _type can be exercised. Updates #50208 Change-Id: I26ccf8c5c574dd9e78510cf29eb40ae7c8d449ab Reviewed-on: https://go-review.googlesource.com/c/go/+/393917 Reviewed-by: Keith Randall Reviewed-by: Keith Randall Run-TryBot: Keith Randall Auto-Submit: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/runtime/debug/heapdump_test.go | 26 ++++++++++++++++++++++++++ src/runtime/type.go | 9 ++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/runtime/debug/heapdump_test.go b/src/runtime/debug/heapdump_test.go index 768934d05db38..ee6b054b117af 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/type.go b/src/runtime/type.go index 44f36a85cadf4..b650d6d795981 100644 --- a/src/runtime/type.go +++ b/src/runtime/type.go @@ -127,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:] From be0262a1279ab37ff2cae562de37afd333f5ada4 Mon Sep 17 00:00:00 2001 From: nimelehin Date: Mon, 11 Apr 2022 19:54:30 +0300 Subject: [PATCH 066/137] cmd/compile: fix compilation crash with several blank labels Fixes #52278 Change-Id: Ibf67c7b019feec277d316e04d93b458efea133fb Reviewed-on: https://go-review.googlesource.com/c/go/+/399574 Reviewed-by: Keith Randall Run-TryBot: Keith Randall Reviewed-by: Keith Randall Auto-Submit: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/cmd/compile/internal/escape/escape.go | 3 +++ src/cmd/compile/internal/escape/stmt.go | 3 +++ test/fixedbugs/issue52278.go | 12 ++++++++++++ 3 files changed, 18 insertions(+) create mode 100644 test/fixedbugs/issue52278.go diff --git a/src/cmd/compile/internal/escape/escape.go b/src/cmd/compile/internal/escape/escape.go index 4713ecddcaea5..4408a531eca5f 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 0afb5d64ef681..4e8dd904ffb59 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/test/fixedbugs/issue52278.go b/test/fixedbugs/issue52278.go new file mode 100644 index 0000000000000..56169e687144f --- /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() { +_: +_: +} From 370cadd0e43ddfcfd494eefefc5bd4e85d7efcf8 Mon Sep 17 00:00:00 2001 From: hopehook Date: Fri, 8 Apr 2022 13:37:40 +0800 Subject: [PATCH 067/137] cmd/compile: add a test case and some comments for deadlock on syntax error After CL 398014 fixed a compiler deadlock on syntax errors, this CL adds a test case and more details for that. How it was fixed: CL 57751 introduced a channel "sem" to limit the number of simultaneously open files. Unfortunately, when the number of syntax processing goroutines exceeds this limit, will easily trigger deadlock. In the original implementation, "sem" only limited the number of open files, not the number of concurrent goroutines, which will cause extra goroutines to block on "sem". When the p.err of the following iteration happens to be held by the blocking goroutine, it will fall into a circular wait, which is a deadlock. CL 398014 fixed the above deadlock, also see issue #52127. First, move "sem <- struct{}{}" to the outside of the syntax processing goroutine, so that the number of concurrent goroutines does not exceed the number of open files, to ensure that all goroutines in execution can eventually write to p.err. Second, move the entire syntax processing logic into a separate goroutine to avoid blocking on the producer side. Change-Id: I1bb89bfee3d2703784f0c0d4ded82baab2ae867a Reviewed-on: https://go-review.googlesource.com/c/go/+/399054 Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Matthew Dempsky Auto-Submit: Matthew Dempsky Reviewed-by: Ian Lance Taylor --- src/cmd/compile/internal/noder/noder.go | 1 + test/fixedbugs/issue52127.go | 62 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 test/fixedbugs/issue52127.go diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index bbd73aa8bee06..cc5610acda911 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -40,6 +40,7 @@ func LoadPackage(filenames []string) { noders[i] = &p } + // 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 diff --git a/test/fixedbugs/issue52127.go b/test/fixedbugs/issue52127.go new file mode 100644 index 0000000000000..7738c3fabf728 --- /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 From a362d5461483190cbaf995e6cb7aaa47c32ebe36 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Sat, 9 Apr 2022 21:00:59 -0700 Subject: [PATCH 068/137] os: mark Solaris nam/door/port files as irregular No test because I'm too lazy to figure out how to create such files. Fixes #52259 Change-Id: I7a07f49993cf046888729e9206ed53dddcf9cb13 Reviewed-on: https://go-review.googlesource.com/c/go/+/399435 Run-TryBot: Ian Lance Taylor Reviewed-by: Emmanuel Odeke Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Russ Cox --- src/os/stat_solaris.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/os/stat_solaris.go b/src/os/stat_solaris.go index 316c26c7cab7a..4e00ecb075f3a 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 From ca7c6ef33d9eca2dbc7eb46601a051dc7dc4e411 Mon Sep 17 00:00:00 2001 From: champly Date: Tue, 12 Apr 2022 01:54:45 +0000 Subject: [PATCH 069/137] runtime/chan.go: improve closed channel receive performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use this benchmark ut: ```go func BenchmarkReceiveDataFromClosedChan(b *testing.B) { count := b.N ch := make(chan struct{}, count) for i := 0; i < count; i++ { ch <- struct{}{} } b.ResetTimer() for range ch { } } ``` Benchmark 10 times(`go test -bench=.`), and then use `benchstat` got the result: ```shell name old time/op new time/op delta ReceiveDataFromClosedChan-5 12.0ns ± 1% 11.4ns ± 0% -5.54% (p=0.000 n=10+8) ``` Fixes: #52067 Change-Id: I8db398cc8c04a46cb66ffb6768ab72a87903812f GitHub-Last-Rev: 1e0142416f223c1ebfc4a7c136bb8fca242d7934 GitHub-Pull-Request: golang/go#52068 Reviewed-on: https://go-review.googlesource.com/c/go/+/396884 Reviewed-by: Keith Randall Reviewed-by: Russ Cox Reviewed-by: Keith Randall Run-TryBot: Keith Randall Auto-Submit: Keith Randall --- src/runtime/chan.go | 36 ++++++++++++++++++++---------------- src/runtime/chan_test.go | 13 +++++++++++++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 6511d80c2c45e..993af7063b98d 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -510,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 { diff --git a/src/runtime/chan_test.go b/src/runtime/chan_test.go index 1e0aa53213e74..a8627e9898892 100644 --- a/src/runtime/chan_test.go +++ b/src/runtime/chan_test.go @@ -1127,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() { From ace7672526692f8290bd98e339169c6eca76ba07 Mon Sep 17 00:00:00 2001 From: Xiaodong Liu Date: Thu, 25 Nov 2021 14:20:39 +0800 Subject: [PATCH 070/137] cmd/compile/internal: fix test error on loong64 For TestLogOpt test case, add loong64 support to test the host architecture and os. The Ctz64 is not intrinsified on loong64 for TestIntendedInlining. Contributors to the loong64 port are: Weining Lu Lei Wang Lingqin Gong Xiaolin Zhao Meidan Li Xiaojuan Zhai Qiyuan Pu Guoqi Chen This port has been updated to Go 1.15.6: https://github.com/loongson/go Updates #46229 Change-Id: I4ca290bf725425a9a6ac2c6767a5bf4ff2339d0e Reviewed-on: https://go-review.googlesource.com/c/go/+/367043 Reviewed-by: David Chase Run-TryBot: David Chase TryBot-Result: Gopher Robot Reviewed-by: Russ Cox Auto-Submit: Russ Cox --- src/cmd/compile/internal/logopt/logopt_test.go | 2 +- src/cmd/compile/internal/test/inl_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmd/compile/internal/logopt/logopt_test.go b/src/cmd/compile/internal/logopt/logopt_test.go index 8d07a49cc0fd6..411319f9e9eff 100644 --- a/src/cmd/compile/internal/logopt/logopt_test.go +++ b/src/cmd/compile/internal/logopt/logopt_test.go @@ -155,7 +155,7 @@ func s15a8(x *[15]int64) [15]int64 { arches := []string{runtime.GOARCH} goos0 := runtime.GOOS if runtime.GOARCH == "amd64" { // Test many things with "linux" (wasm will get "js") - arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "ppc64le", "riscv64", "s390x", "wasm"} + arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "loong64", "ppc64le", "riscv64", "s390x", "wasm"} goos0 = "linux" } diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go index b10d37a17cfd0..9e93cdd0c56a5 100644 --- a/src/cmd/compile/internal/test/inl_test.go +++ b/src/cmd/compile/internal/test/inl_test.go @@ -163,10 +163,10 @@ func TestIntendedInlining(t *testing.T) { }, } - if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" { + if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" { // nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable. // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386. - // On mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive + // On loong64, mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive // to inline (Issue 22239). want["runtime"] = append(want["runtime"], "nextFreeFast") } From d4dbad53ca080d767798ee4267999868d7f2c22d Mon Sep 17 00:00:00 2001 From: Bryan Mills Date: Tue, 12 Apr 2022 02:53:29 +0000 Subject: [PATCH 071/137] Revert "cmd/compile/internal: fix test error on loong64" This reverts CL 367043. Reason for revert: auto-submitted prematurely, breaking tests on most builders. Change-Id: I6da319fb042b629bcd6f549be638497a357e7d28 Reviewed-on: https://go-review.googlesource.com/c/go/+/399795 Run-TryBot: Bryan Mills Auto-Submit: Bryan Mills Reviewed-by: Dmitri Shuralyov TryBot-Result: Gopher Robot --- src/cmd/compile/internal/logopt/logopt_test.go | 2 +- src/cmd/compile/internal/test/inl_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmd/compile/internal/logopt/logopt_test.go b/src/cmd/compile/internal/logopt/logopt_test.go index 411319f9e9eff..8d07a49cc0fd6 100644 --- a/src/cmd/compile/internal/logopt/logopt_test.go +++ b/src/cmd/compile/internal/logopt/logopt_test.go @@ -155,7 +155,7 @@ func s15a8(x *[15]int64) [15]int64 { arches := []string{runtime.GOARCH} goos0 := runtime.GOOS if runtime.GOARCH == "amd64" { // Test many things with "linux" (wasm will get "js") - arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "loong64", "ppc64le", "riscv64", "s390x", "wasm"} + arches = []string{"arm", "arm64", "386", "amd64", "mips", "mips64", "ppc64le", "riscv64", "s390x", "wasm"} goos0 = "linux" } diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go index 9e93cdd0c56a5..b10d37a17cfd0 100644 --- a/src/cmd/compile/internal/test/inl_test.go +++ b/src/cmd/compile/internal/test/inl_test.go @@ -163,10 +163,10 @@ func TestIntendedInlining(t *testing.T) { }, } - if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" { + if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" { // nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable. // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386. - // On loong64, mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive + // On mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive // to inline (Issue 22239). want["runtime"] = append(want["runtime"], "nextFreeFast") } From e299381cd1b4b7efecf7642ebfcd5e76ffa65e6d Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Fri, 8 Apr 2022 15:23:14 -0400 Subject: [PATCH 072/137] cmd/go: fix TestScript/build_trimpath_goroot when built with a mismatched GOROOT_FINAL Fixes #52236. Updates #51461. Change-Id: Ie91e0256afd45e9bbd60fd8cdc696363027ab696 Reviewed-on: https://go-review.googlesource.com/c/go/+/399156 Run-TryBot: Bryan Mills Reviewed-by: Russ Cox TryBot-Result: Gopher Robot --- src/cmd/go/go_test.go | 4 ++ src/cmd/go/script_test.go | 4 +- src/cmd/go/testdata/script/README | 2 + .../testdata/script/build_trimpath_goroot.txt | 38 ++++++++++++++++--- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 426228a831f5a..b17c77665011e 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/script_test.go b/src/cmd/go/script_test.go index 76c542f32a21c..6254cf97c1c7e 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 17b582d662770..85e575d56eed3 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_trimpath_goroot.txt b/src/cmd/go/testdata/script/build_trimpath_goroot.txt index 7b870ab739d38..91e5107e589db 100644 --- a/src/cmd/go/testdata/script/build_trimpath_goroot.txt +++ b/src/cmd/go/testdata/script/build_trimpath_goroot.txt @@ -8,24 +8,52 @@ # 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] stop +[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). -[short] skip +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 . -go run . +env GOROOT= + +exec ./example.exe stdout '^GOROOT '$TESTGO_GOROOT'$' stdout '^runtime '$TESTGO_GOROOT${/}src${/}runtime'$' -go test -v . +! 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 +[mismatched-goroot] stop + ! go run -trimpath . stdout '^GOROOT $' stderr 'cannot find package "runtime" in any of:\n\t\(\$GOROOT not set\)\n\t'$WORK${/}gopath${/}src${/}runtime' \(from \$GOPATH\)\nexit status 1\n\z' From f2d9ab263b8c62a81d314feb1e7a7fb424bb9c43 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Fri, 8 Apr 2022 15:26:38 -0400 Subject: [PATCH 073/137] cmd/go: set GOROOT explicitly for 'go generate' subprocesses Code generators may reasonably expect to find the GOROOT for which the code is being generated. If the generator invokes 'go run' (which ought to be reasonable to do) and the user has set 'GOFLAGS=trimpath' (which also ought to be reasonable), then either 'go generate' or 'go run' needs to set GOROOT explicitly. I would argue that it is more appropriate for 'go generate' to set GOROOT than for 'go run' to do so, since a user may reasonably invoke 'go run' to reproduce a user-reported bug in a standalone Go program, but should not invoke 'go generate' except to regenerate code for a Go package. Updates #51461. Change-Id: Iceba233b4eebd57c40cf5dcd4af9031d210dc9d8 Reviewed-on: https://go-review.googlesource.com/c/go/+/399157 Run-TryBot: Bryan Mills Reviewed-by: Ian Lance Taylor Reviewed-by: Russ Cox TryBot-Result: Gopher Robot --- src/cmd/go/alldocs.go | 3 +++ src/cmd/go/internal/generate/generate.go | 4 ++++ src/cmd/go/testdata/script/build_trimpath_goroot.txt | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 6800e1c7d2e18..586bc1a7ca57a 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -542,6 +542,9 @@ // 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. // diff --git a/src/cmd/go/internal/generate/generate.go b/src/cmd/go/internal/generate/generate.go index 0021bcc75aa2b..a46f4f8908c44 100644 --- a/src/cmd/go/internal/generate/generate.go +++ b/src/cmd/go/internal/generate/generate.go @@ -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/testdata/script/build_trimpath_goroot.txt b/src/cmd/go/testdata/script/build_trimpath_goroot.txt index 91e5107e589db..a26cfd23be488 100644 --- a/src/cmd/go/testdata/script/build_trimpath_goroot.txt +++ b/src/cmd/go/testdata/script/build_trimpath_goroot.txt @@ -62,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 @@ -69,6 +74,8 @@ go 1.19 -- main.go -- package main +//go:generate go run . + import ( "fmt" "go/build" From 2b31abc5286e4f29f934c4123101feabf0f4aaca Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Mon, 11 Apr 2022 17:35:24 -0700 Subject: [PATCH 074/137] test: add //go:build support to run.go gofmt is rewriting +build comments into //go:build anyway, so update the test script to support both. Change-Id: Ia6d950cfaa2fca9f184b8b2d3625a551bff88dde Reviewed-on: https://go-review.googlesource.com/c/go/+/399794 Run-TryBot: Matthew Dempsky Auto-Submit: Matthew Dempsky TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- test/run.go | 51 +++++++++++++-------------------------------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/test/run.go b/test/run.go index 468379b4a9821..45cd086fc4319 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 + if strings.HasPrefix(line, "package ") { + break } - gcFlags := os.Getenv("GO_GCFLAGS") - ctxt := &context{ - GOOS: goos, - GOARCH: goarch, - cgoEnabled: cgoEnabled, - noOptEnv: strings.Contains(gcFlags, "-N") || strings.Contains(gcFlags, "-l"), - } - - 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"). From d6320f1a58f1f7820daee06a086c83a0274a777f Mon Sep 17 00:00:00 2001 From: Wayne Zuo Date: Fri, 8 Apr 2022 16:44:13 +0800 Subject: [PATCH 075/137] cmd/compile: add SARX instruction for GOAMD64>=3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit name old time/op new time/op delta ShiftArithmeticRight-8 0.68ns ± 5% 0.30ns ± 6% -56.14% (p=0.000 n=10+10) Change-Id: I052a0d7b9e6526d526276444e588b0cc288beff4 Reviewed-on: https://go-review.googlesource.com/c/go/+/399055 Run-TryBot: Wayne Zuo TryBot-Result: Gopher Robot Reviewed-by: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Cherry Mui --- src/cmd/compile/internal/amd64/ssa.go | 4 + .../compile/internal/amd64/versions_test.go | 1 + src/cmd/compile/internal/ssa/gen/AMD64.rules | 44 +- src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 3 + src/cmd/compile/internal/ssa/opGen.go | 30 ++ src/cmd/compile/internal/ssa/rewriteAMD64.go | 422 ++++++++++++++++++ src/cmd/compile/internal/test/shift_test.go | 10 + test/codegen/bmi.go | 10 + 8 files changed, 504 insertions(+), 20 deletions(-) diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 2dae55ba864d5..9fde775358328 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -282,6 +282,10 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Reg = v.Reg() p.SetFrom3Reg(v.Args[1].Reg()) + case ssa.OpAMD64SARXL, ssa.OpAMD64SARXQ: + 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: p := opregreg(s, v.Op.Asm(), v.Reg(), v.Args[1].Reg()) diff --git a/src/cmd/compile/internal/amd64/versions_test.go b/src/cmd/compile/internal/amd64/versions_test.go index 11b4d8436ac70..248f07067fb2c 100644 --- a/src/cmd/compile/internal/amd64/versions_test.go +++ b/src/cmd/compile/internal/amd64/versions_test.go @@ -239,6 +239,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/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index d50bdf2a175bc..3a9de8dd03f52 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -206,6 +206,9 @@ (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 instruction because it has less register restriction on the shift input. +(SAR(Q|L) x y) && buildcfg.GOAMD64 >= 3 => (SARX(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)) @@ -803,28 +806,29 @@ (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)) - -((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)) - -((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)) - -((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)) +((SHLQ|SHRQ|SARQ|SARXQ) x (ADDQconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ (ADDQconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ y)) +((SHLQ|SHRQ|SARQ|SARXQ) x (ANDQconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ (ANDQconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ y)) + +((SHLL|SHRL|SARL|SARXL) x (ADDQconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x y) +((SHLL|SHRL|SARL|SARXL) x (NEGQ (ADDQconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x (NEGQ y)) +((SHLL|SHRL|SARL|SARXL) x (ANDQconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x y) +((SHLL|SHRL|SARL|SARXL) x (NEGQ (ANDQconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x (NEGQ y)) + +((SHLQ|SHRQ|SARQ|SARXQ) x (ADDLconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL (ADDLconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL y)) +((SHLQ|SHRQ|SARQ|SARXQ) x (ANDLconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) +((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL (ANDLconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL y)) + +((SHLL|SHRL|SARL|SARXL) x (ADDLconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x y) +((SHLL|SHRL|SARL|SARXL) x (NEGL (ADDLconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x (NEGL y)) +((SHLL|SHRL|SARL|SARXL) x (ANDLconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x y) +((SHLL|SHRL|SARL|SARXL) x (NEGL (ANDLconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x (NEGL y)) // Constant rotate instructions ((ADDQ|ORQ|XORQ) (SHLQconst x [c]) (SHRQconst x [d])) && d==64-c => (ROLQconst x [c]) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index ab84504d1ab99..2eec6e03249da 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -953,6 +953,9 @@ func init() { {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: "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 diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 1c941e84e17f6..976d8873216bd 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1062,6 +1062,8 @@ const ( OpAMD64MOVBELstoreidx8 OpAMD64MOVBEQstoreidx1 OpAMD64MOVBEQstoreidx8 + OpAMD64SARXQ + OpAMD64SARXL OpAMD64SHLXLload OpAMD64SHLXQload OpAMD64SHRXLload @@ -14117,6 +14119,34 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + 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: "SHLXLload", auxType: auxSymOff, diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index addfaaa3a858e..81f1f1ae4eb9e 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -382,6 +382,10 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpAMD64SARW(v) case OpAMD64SARWconst: return rewriteValueAMD64_OpAMD64SARWconst(v) + case OpAMD64SARXL: + return rewriteValueAMD64_OpAMD64SARXL(v) + case OpAMD64SARXQ: + return rewriteValueAMD64_OpAMD64SARXQ(v) case OpAMD64SBBLcarrymask: return rewriteValueAMD64_OpAMD64SBBLcarrymask(v) case OpAMD64SBBQ: @@ -19844,6 +19848,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 +20083,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 +20371,398 @@ 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 + } + 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 + } + return false +} func rewriteValueAMD64_OpAMD64SBBLcarrymask(v *Value) bool { v_0 := v.Args[0] // match: (SBBLcarrymask (FlagEQ)) diff --git a/src/cmd/compile/internal/test/shift_test.go b/src/cmd/compile/internal/test/shift_test.go index ea88f0a70ae22..58c8dde1a0106 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/test/codegen/bmi.go b/test/codegen/bmi.go index 2908d1b796fea..9dd2b0039ceaf 100644 --- a/test/codegen/bmi.go +++ b/test/codegen/bmi.go @@ -46,6 +46,16 @@ func blsr32(x int32) int32 { return x & (x - 1) } +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 shlrx64(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 From 9ccf5b8e86ce98494a2127196fbc47d72b0a71a5 Mon Sep 17 00:00:00 2001 From: Lynn Boger Date: Fri, 8 Apr 2022 13:50:00 -0500 Subject: [PATCH 076/137] runtime: improve memmove for ppc64x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This improves performance of memmove for larger moves by unrolling the main loop from 32 byte to 64 byte moves. The improvement of the relevant sizes on a power9: Memmove/64 5.11ns ± 0% 5.00ns ± 0% -2.21% Memmove/128 8.26ns ± 0% 5.88ns ± 0% -28.83% Memmove/256 12.7ns ± 0% 8.6ns ± 0% -31.94% Memmove/512 17.9ns ± 0% 14.3ns ± 0% -19.87% Memmove/1024 33.3ns ± 0% 27.0ns ± 0% -18.92% Memmove/2048 72.1ns ± 0% 51.8ns ± 0% -28.25% Memmove/4096 126ns ± 0% 110ns ± 0% -12.63% Change-Id: I74162a9f152d7752a8281da1b89a66da99a3fdc9 Reviewed-on: https://go-review.googlesource.com/c/go/+/399499 Run-TryBot: Lynn Boger TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Reviewed-by: Ian Lance Taylor --- src/runtime/memmove_ppc64x.s | 56 +++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/src/runtime/memmove_ppc64x.s b/src/runtime/memmove_ppc64x.s index 25101a28c7538..5fa51c0a4cf9d 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 From 45c3387d777caf28f4b992ad9a6216e3085bb8fe Mon Sep 17 00:00:00 2001 From: Julie Qiu Date: Tue, 1 Mar 2022 10:19:38 -0600 Subject: [PATCH 077/137] encoding/pem: fix stack overflow in Decode Previously, Decode called decodeError, a recursive function that was prone to stack overflows when given a large PEM file containing errors. Credit to Juho Nurminen of Mattermost who reported the error. Fixes CVE-2022-24675 Fixes #51853 Change-Id: Iffe768be53c8ddc0036fea0671d290f8f797692c Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1391157 Reviewed-by: Damien Neil Reviewed-by: Filippo Valsorda (cherry picked from commit 794ea5e828010e8b68493b2fc6d2963263195a02) Reviewed-on: https://go-review.googlesource.com/c/go/+/399820 Reviewed-by: Dmitri Shuralyov Run-TryBot: Dmitri Shuralyov Auto-Submit: Dmitri Shuralyov Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot --- src/encoding/pem/pem.go | 172 +++++++++++++++-------------------- src/encoding/pem/pem_test.go | 28 +++++- 2 files changed, 100 insertions(+), 100 deletions(-) diff --git a/src/encoding/pem/pem.go b/src/encoding/pem/pem.go index 146f9d0e1a528..d26e4c8399135 100644 --- a/src/encoding/pem/pem.go +++ b/src/encoding/pem/pem.go @@ -90,122 +90,96 @@ func Decode(data []byte) (p *Block, rest []byte) { // pemStart begins with a newline. However, at the very beginning of // the byte array, we'll accept the start string without it. rest = data - if bytes.HasPrefix(data, pemStart[1:]) { - rest = rest[len(pemStart)-1 : len(data)] - } else if _, after, ok := bytes.Cut(data, pemStart); ok { - rest = after - } else { - return nil, data - } - - typeLine, rest := getLine(rest) - if !bytes.HasSuffix(typeLine, pemEndOfLine) { - return decodeError(data, rest) - } - typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)] - - p = &Block{ - Headers: make(map[string]string), - Type: string(typeLine), - } - for { - // This loop terminates because getLine's second result is - // always smaller than its argument. - if len(rest) == 0 { + if bytes.HasPrefix(rest, pemStart[1:]) { + rest = rest[len(pemStart)-1:] + } else if _, after, ok := bytes.Cut(rest, pemStart); ok { + rest = after + } else { return nil, data } - line, next := getLine(rest) - key, val, ok := bytes.Cut(line, colon) - if !ok { - break + var typeLine []byte + typeLine, rest = getLine(rest) + if !bytes.HasSuffix(typeLine, pemEndOfLine) { + continue } + typeLine = typeLine[0 : len(typeLine)-len(pemEndOfLine)] - // TODO(agl): need to cope with values that spread across lines. - key = bytes.TrimSpace(key) - val = bytes.TrimSpace(val) - p.Headers[string(key)] = string(val) - rest = next - } + p = &Block{ + Headers: make(map[string]string), + Type: string(typeLine), + } - var endIndex, endTrailerIndex int + for { + // This loop terminates because getLine's second result is + // always smaller than its argument. + if len(rest) == 0 { + return nil, data + } + line, next := getLine(rest) - // If there were no headers, the END line might occur - // immediately, without a leading newline. - if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) { - endIndex = 0 - endTrailerIndex = len(pemEnd) - 1 - } else { - endIndex = bytes.Index(rest, pemEnd) - endTrailerIndex = endIndex + len(pemEnd) - } + key, val, ok := bytes.Cut(line, colon) + if !ok { + break + } - if endIndex < 0 { - return decodeError(data, rest) - } + // TODO(agl): need to cope with values that spread across lines. + key = bytes.TrimSpace(key) + val = bytes.TrimSpace(val) + p.Headers[string(key)] = string(val) + rest = next + } - // After the "-----" of the ending line, there should be the same type - // and then a final five dashes. - endTrailer := rest[endTrailerIndex:] - endTrailerLen := len(typeLine) + len(pemEndOfLine) - if len(endTrailer) < endTrailerLen { - return decodeError(data, rest) - } + var endIndex, endTrailerIndex int - restOfEndLine := endTrailer[endTrailerLen:] - endTrailer = endTrailer[:endTrailerLen] - if !bytes.HasPrefix(endTrailer, typeLine) || - !bytes.HasSuffix(endTrailer, pemEndOfLine) { - return decodeError(data, rest) - } + // If there were no headers, the END line might occur + // immediately, without a leading newline. + if len(p.Headers) == 0 && bytes.HasPrefix(rest, pemEnd[1:]) { + endIndex = 0 + endTrailerIndex = len(pemEnd) - 1 + } else { + endIndex = bytes.Index(rest, pemEnd) + endTrailerIndex = endIndex + len(pemEnd) + } - // The line must end with only whitespace. - if s, _ := getLine(restOfEndLine); len(s) != 0 { - return decodeError(data, rest) - } + if endIndex < 0 { + continue + } - base64Data := removeSpacesAndTabs(rest[:endIndex]) - p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) - n, err := base64.StdEncoding.Decode(p.Bytes, base64Data) - if err != nil { - return decodeError(data, rest) - } - p.Bytes = p.Bytes[:n] + // After the "-----" of the ending line, there should be the same type + // and then a final five dashes. + endTrailer := rest[endTrailerIndex:] + endTrailerLen := len(typeLine) + len(pemEndOfLine) + if len(endTrailer) < endTrailerLen { + continue + } + + restOfEndLine := endTrailer[endTrailerLen:] + endTrailer = endTrailer[:endTrailerLen] + if !bytes.HasPrefix(endTrailer, typeLine) || + !bytes.HasSuffix(endTrailer, pemEndOfLine) { + continue + } - // the -1 is because we might have only matched pemEnd without the - // leading newline if the PEM block was empty. - _, rest = getLine(rest[endIndex+len(pemEnd)-1:]) + // The line must end with only whitespace. + if s, _ := getLine(restOfEndLine); len(s) != 0 { + continue + } - return -} + base64Data := removeSpacesAndTabs(rest[:endIndex]) + p.Bytes = make([]byte, base64.StdEncoding.DecodedLen(len(base64Data))) + n, err := base64.StdEncoding.Decode(p.Bytes, base64Data) + if err != nil { + continue + } + p.Bytes = p.Bytes[:n] -func decodeError(data, rest []byte) (*Block, []byte) { - // If we get here then we have rejected a likely looking, but - // ultimately invalid PEM block. We need to start over from a new - // position. We have consumed the preamble line and will have consumed - // any lines which could be header lines. However, a valid preamble - // line is not a valid header line, therefore we cannot have consumed - // the preamble line for the any subsequent block. Thus, we will always - // find any valid block, no matter what bytes precede it. - // - // For example, if the input is - // - // -----BEGIN MALFORMED BLOCK----- - // junk that may look like header lines - // or data lines, but no END line - // - // -----BEGIN ACTUAL BLOCK----- - // realdata - // -----END ACTUAL BLOCK----- - // - // we've failed to parse using the first BEGIN line - // and now will try again, using the second BEGIN line. - p, rest := Decode(rest) - if p == nil { - rest = data + // the -1 is because we might have only matched pemEnd without the + // leading newline if the PEM block was empty. + _, rest = getLine(rest[endIndex+len(pemEnd)-1:]) + return p, rest } - return p, rest } const pemLineLength = 64 diff --git a/src/encoding/pem/pem_test.go b/src/encoding/pem/pem_test.go index b2b6b15e73655..c94b5ca53b568 100644 --- a/src/encoding/pem/pem_test.go +++ b/src/encoding/pem/pem_test.go @@ -107,6 +107,12 @@ const pemMissingEndingSpace = ` dGVzdA== -----ENDBAR-----` +const pemMissingEndLine = ` +-----BEGIN FOO----- +Header: 1` + +var pemRepeatingBegin = strings.Repeat("-----BEGIN \n", 10) + var badPEMTests = []struct { name string input string @@ -131,14 +137,34 @@ var badPEMTests = []struct { "missing ending space", pemMissingEndingSpace, }, + { + "repeating begin", + pemRepeatingBegin, + }, + { + "missing end line", + pemMissingEndLine, + }, } func TestBadDecode(t *testing.T) { for _, test := range badPEMTests { - result, _ := Decode([]byte(test.input)) + result, rest := Decode([]byte(test.input)) if result != nil { t.Errorf("unexpected success while parsing %q", test.name) } + if string(rest) != test.input { + t.Errorf("unexpected rest: %q; want = %q", rest, test.input) + } + } +} + +func TestCVE202224675(t *testing.T) { + // Prior to CVE-2022-24675, this input would cause a stack overflow. + input := []byte(strings.Repeat("-----BEGIN \n", 10000000)) + result, rest := Decode(input) + if result != nil || !reflect.DeepEqual(rest, input) { + t.Errorf("Encode of %#v decoded as %#v", input, rest) } } From 8f1d5d0a41840c1e6b36c94d493af8aae1302fd0 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Tue, 29 Mar 2022 08:51:16 +0200 Subject: [PATCH 078/137] cmd/go/internal/modload: remove aix from stat_openfile.go comment syscall.Access is supported and used on aix since CL 263540. Change-Id: Ie50cc3da68b49b22d622d94faec0231c52502037 Reviewed-on: https://go-review.googlesource.com/c/go/+/396374 Run-TryBot: Tobias Klauser TryBot-Result: Gopher Robot Auto-Submit: Tobias Klauser Reviewed-by: Bryan Mills Reviewed-by: Russ Cox --- src/cmd/go/internal/modload/stat_openfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/go/internal/modload/stat_openfile.go b/src/cmd/go/internal/modload/stat_openfile.go index ff7c124af58a2..5773073d90355 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 From 5bf6c97e76c721242a9b064950cd901c33f6f0b9 Mon Sep 17 00:00:00 2001 From: tenkoh Date: Wed, 30 Mar 2022 15:30:19 +0900 Subject: [PATCH 079/137] cmd/go: open correct path when loading embeds from root directory The existing implementation of `load.resolveEmbed` uses an expression like `path[len(pkgdir)+1:]`. Though the `+1` is intended to remove a prefix slash, the expression returns an incorrect path when `pkgdir` is "/". (ex.: when removing "/" from "/foo", want "foo", but got "oo") It seems that `str.TrimFilePathPrefix` would solve the problem, but the function contains the same bug. So, this commit fixes `str.TrimFilePathPrefix` then applies it to `load.resolveEmbed` to solve the issue. The fix is quite simple. First, remove prefix. Then check whether the remained first letter is equal to `filepath.Separator`. If so, remove it then return. Fixed #49570 Change-Id: I26ab727ee4dfcbf51ed9bd0a573957ced2154515 Reviewed-on: https://go-review.googlesource.com/c/go/+/396694 Reviewed-by: Bryan Mills Run-TryBot: Bryan Mills Auto-Submit: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Russ Cox --- src/cmd/go/internal/load/pkg.go | 5 +- src/cmd/go/internal/str/path.go | 9 ++-- src/cmd/go/internal/str/str_test.go | 71 +++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go index 2592cf5447740..10a980fc65188 100644 --- a/src/cmd/go/internal/load/pkg.go +++ b/src/cmd/go/internal/load/pkg.go @@ -2056,7 +2056,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) @@ -2106,7 +2107,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. diff --git a/src/cmd/go/internal/str/path.go b/src/cmd/go/internal/str/path.go index 0c8aaeaca1fba..a69e171f8cec7 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_test.go b/src/cmd/go/internal/str/str_test.go index 8ea758e0a8b64..158fe65dc16ed 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) + } + } +} From 6f5590edf6883583a801ceb9309f074c0cd7c1cd Mon Sep 17 00:00:00 2001 From: Wayne Zuo Date: Fri, 8 Apr 2022 23:44:40 +0800 Subject: [PATCH 080/137] cmd/compile: always write fun[0] in incomplete itab runtime.getitab need filled fun[0] to identify whether implemented the interface. Fixes #51700 Fixes #52228 Change-Id: I0173b98f4e1b45e3a0183a5b60229d289140d1e6 Reviewed-on: https://go-review.googlesource.com/c/go/+/399058 Reviewed-by: Keith Randall Run-TryBot: Keith Randall Auto-Submit: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Reviewed-by: David Chase --- .../compile/internal/reflectdata/reflect.go | 16 +++++----- test/typeparam/issue51700.go | 26 ++++++++++++++++ test/typeparam/issue52228.go | 30 +++++++++++++++++++ 3 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 test/typeparam/issue51700.go create mode 100644 test/typeparam/issue52228.go diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index a8d81b9a21592..3bd5f1e932390 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -1328,21 +1328,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)) diff --git a/test/typeparam/issue51700.go b/test/typeparam/issue51700.go new file mode 100644 index 0000000000000..bf8a1f6289bb5 --- /dev/null +++ b/test/typeparam/issue51700.go @@ -0,0 +1,26 @@ +// 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. + +package main + +func f[B any](b B) { + if b1, ok := any(b).(interface{ m1() }); ok { + panic(1) + _ = b1.(B) + } + if b2, ok := any(b).(interface{ m2() }); ok { + panic(2) + _ = b2.(B) + } +} + +type S struct{} + +func (S) m3() {} + +func main() { + f(S{}) +} diff --git a/test/typeparam/issue52228.go b/test/typeparam/issue52228.go new file mode 100644 index 0000000000000..3fbbde59ab62a --- /dev/null +++ b/test/typeparam/issue52228.go @@ -0,0 +1,30 @@ +// 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. + +package main + +type SomeInterface interface { + Whatever() +} + +func X[T any]() T { + var m T + + // for this example, this block should never run + if _, ok := any(m).(SomeInterface); ok { + var dst SomeInterface + _, _ = dst.(T) + return dst.(T) + } + + return m +} + +type holder struct{} + +func main() { + X[holder]() +} From 4569fe64101c2209e3429bd1c953b5f4021fc43d Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Thu, 7 Apr 2022 17:25:23 -0400 Subject: [PATCH 081/137] cmd/go: allow '-buildvcs=auto' and treat it as the default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we added VCS stamping in the Go 1.18 release, we defaulted to -buildvcs=true, on the theory that most folks will actually want VCS information stamped. We also made -buildvcs=true error out if a VCS directory is found and no VCS tool is available, on the theory that a user who builds with '-buildvcs=true' will be very surprised if the VCS metadata is silently missing. However, that causes a problem for CI environments that don't have the appropriate VCS tool installed. (And we know that's a common situation because we're in that situation ourselves — see #46693!) The new '-buildvcs=auto' setting provides a middle ground: it stamps VCS information by default when the tool is present (and reports explicit errors if the tool errors out), but omits the metadata when the tool isn't present at all. Fixes #51748. Updates #51999. Change-Id: Iebc955c2af0abca9b7517f62ca48b1d944eb2df4 Reviewed-on: https://go-review.googlesource.com/c/go/+/398855 Run-TryBot: Bryan Mills Reviewed-by: Russ Cox Reviewed-by: Michael Matloob Auto-Submit: Bryan Mills TryBot-Result: Gopher Robot --- src/cmd/go/alldocs.go | 12 +-- src/cmd/go/internal/cfg/cfg.go | 6 +- src/cmd/go/internal/list/list.go | 2 +- src/cmd/go/internal/load/pkg.go | 33 +++++-- src/cmd/go/internal/work/build.go | 42 +++++++-- .../testdata/script/build_buildvcs_auto.txt | 87 +++++++++++++++++++ src/cmd/go/testdata/script/test_buildvcs.txt | 2 + .../script/version_buildvcs_nested.txt | 2 +- 8 files changed, 159 insertions(+), 27 deletions(-) create mode 100644 src/cmd/go/testdata/script/build_buildvcs_auto.txt diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 586bc1a7ca57a..6fdb4f93a330c 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -136,11 +136,13 @@ // -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' diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index a11a1a7655537..c6ddfe55d5860 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/list/list.go b/src/cmd/go/internal/list/list.go index e039b9faa178d..17864e1da71db 100644 --- a/src/cmd/go/internal/list/list.go +++ b/src/cmd/go/internal/list/list.go @@ -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 10a980fc65188..e43117f3d3625 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 || @@ -2372,7 +2373,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) @@ -2384,7 +2385,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 { @@ -2398,8 +2406,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 { @@ -2407,8 +2418,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 { @@ -2435,6 +2449,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/work/build.go b/src/cmd/go/internal/work/build.go index e63e209a142d9..e9a8ee6cb3a5c 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 @@ -609,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/testdata/script/build_buildvcs_auto.txt b/src/cmd/go/testdata/script/build_buildvcs_auto.txt new file mode 100644 index 0000000000000..9eac568045987 --- /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/test_buildvcs.txt b/src/cmd/go/testdata/script/test_buildvcs.txt index a0689195e8111..a669966036458 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/version_buildvcs_nested.txt b/src/cmd/go/testdata/script/version_buildvcs_nested.txt index 08d4c92bafb69..a0c69f9c12acf 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 From f66925e854e71e0c54b581885380a490d7afa30c Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Tue, 12 Apr 2022 13:38:17 -0700 Subject: [PATCH 082/137] syscall: check correct group in Faccessat The Faccessat call checks the user, group, or other permission bits of a file to see if the calling process can access it. The test to see if the group permissions should be used was made with the wrong group id, using the process's group id rather than the file's group id. Fix this to use the correct group id. No test since we cannot easily change file permissions when not running as root and the test is meaningless if running as root. For #52313 Change-Id: I4e2c84754b0af7830b40fd15dedcbc58374d75ee Reviewed-on: https://go-review.googlesource.com/c/go/+/399539 Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/syscall/syscall_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syscall/syscall_linux.go b/src/syscall/syscall_linux.go index a00d8c94a26b6..74322caea12b9 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 From d85694ab4fdf1d8f99d6cb96878bfb7acab8df13 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Fri, 8 Apr 2022 16:02:59 -0400 Subject: [PATCH 083/137] doc/go1.19: document cmd/go changes involving -trimpath Updates #51461. Change-Id: Ie878a9f630062d62027de895750a070b50428a9f Reviewed-on: https://go-review.googlesource.com/c/go/+/399214 Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- doc/go1.19.html | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/doc/go1.19.html b/doc/go1.19.html index c1523c57ecb81..21781c3e3329a 100644 --- a/doc/go1.19.html +++ b/doc/go1.19.html @@ -32,7 +32,27 @@

Tools

Go command

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

+ + +

+ Passing the -trimpath flag to go commands now + causes runtime.GOROOT() in the resulting binary to return the + empty string instead of the string "go". +

+

+ 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

From 6183920a33c21725ad21d67bee8c1eebb5d30a90 Mon Sep 17 00:00:00 2001 From: Archana R Date: Wed, 9 Feb 2022 07:37:12 -0600 Subject: [PATCH 084/137] math/big: Implement shlVU and shrVU in ASM for PPC64 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently the shift left and shift right functions are coded in .go on PPC64. Implementing them in ASM just like AMD and ARM results in overall speedup of shift benchmarks on POWER8/9/10. name old time/op new time/op delta NonZeroShifts/1/shrVU 8.50ns ± 0% 5.21ns ± 0% -38.66% NonZeroShifts/1/shlVU 8.85ns ± 1% 5.24ns ± 0% -40.78% NonZeroShifts/2/shrVU 9.16ns ± 0% 5.51ns ± 0% -39.80% NonZeroShifts/2/shlVU 9.24ns ± 2% 5.61ns ± 0% -39.28% NonZeroShifts/3/shrVU 10.6ns ± 0% 6.8ns ± 0% -35.78% NonZeroShifts/3/shlVU 10.7ns ± 2% 6.4ns ± 0% -40.82% NonZeroShifts/4/shrVU 12.4ns ± 0% 7.7ns ± 0% -38.12% NonZeroShifts/4/shlVU 12.3ns ± 1% 7.5ns ± 0% -38.67% NonZeroShifts/5/shrVU 13.2ns ± 0% 8.5ns ± 0% -35.51% NonZeroShifts/5/shlVU 13.3ns ± 2% 9.3ns ± 0% -30.05% NonZeroShifts/10/shrVU 16.5ns ± 0% 13.1ns ± 0% -20.12% NonZeroShifts/10/shlVU 16.8ns ± 1% 14.1ns ± 0% -16.02% NonZeroShifts/100/shrVU 122ns ± 0% 94ns ± 0% -22.87% NonZeroShifts/100/shlVU 115ns ± 0% 103ns ± 0% -10.50% NonZeroShifts/1000/shrVU 1.10µs ± 0% 0.91µs ± 0% -17.03% NonZeroShifts/1000/shlVU 1.02µs ± 0% 0.93µs ± 0% -8.74% NonZeroShifts/10000/shrVU 10.9µs ± 0% 9.1µs ± 0% -16.66% NonZeroShifts/10000/shlVU 10.1µs ± 0% 9.3µs ± 0% -8.19% NonZeroShifts/100000/shrVU 109µs ± 0% 91µs ± 0% -16.01% NonZeroShifts/100000/shlVU 101µs ± 0% 94µs ± 0% -7.16% Change-Id: Ia31951cc29a4169beb494d2951427cbe1e963b11 Reviewed-on: https://go-review.googlesource.com/c/go/+/384474 Reviewed-by: Cherry Mui Reviewed-by: Lynn Boger Run-TryBot: Lynn Boger TryBot-Result: Gopher Robot Run-TryBot: Russ Cox Auto-Submit: Russ Cox Reviewed-by: Ian Lance Taylor --- src/math/big/arith_ppc64x.s | 154 +++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 2 deletions(-) diff --git a/src/math/big/arith_ppc64x.s b/src/math/big/arith_ppc64x.s index 68c6286494612..601cafe6bb748 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 From fc1d4c11dfff3cad0389b32e9fa698a389807e3a Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Mon, 11 Apr 2022 17:52:55 -0700 Subject: [PATCH 085/137] cmd/go: fix TestScript/test_fuzz_minimize_interesting flake check_testdata/check_testdata.go used the encoding of the corpus entry file, rather than the input string itself, when checking the expected size of the minimized value. Instead, use the actual byte length, which should bypass flakiness. While we are here, use somewhat simpler fuzz targets, that use byte slices rather than strings, and only execute the targets when fuzzing ( skipping the 'run' phase.) Fixes #52285 Change-Id: I48c3780934891eec4a9e38d93abb4666091cb580 Reviewed-on: https://go-review.googlesource.com/c/go/+/399814 Run-TryBot: Roland Shoemaker TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Reviewed-by: Bryan Mills --- .../script/test_fuzz_minimize_interesting.txt | 104 ++++++++---------- 1 file changed, 47 insertions(+), 57 deletions(-) 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 a09e85b972f99..e61c4f9d04eb9 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) - } - 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) + 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) + } } - fmt.Fprintf(os.Stderr, "%s\n", contents) } +var valRe = regexp.MustCompile(`^\[\]byte\(([^)]+)\)$`) + -- check_cache/check_cache.go -- //go:build ignore // +build ignore From 1f97f960e76d1b722b7c4a0dd61e9b20ec6c2a37 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 12 Apr 2022 13:34:42 -0700 Subject: [PATCH 086/137] cmd/link: don't sort pclntab entries They are already in a good order. The sort here does nothing, as all the SymValues are 0. Sorting just arbitrarily permutes items because everything is equal and the sort isn't stable. Not sure why the ordering of these symbols matter. That ordering was added in CL 243223. Change-Id: Iee153394afdb39387701cfe0375bc022cf4bd489 Reviewed-on: https://go-review.googlesource.com/c/go/+/399540 Run-TryBot: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Reviewed-by: Keith Randall Auto-Submit: Keith Randall --- src/cmd/link/internal/ld/data.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index dae74d91d28d8..ce86f73cdab93 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -2112,12 +2112,7 @@ func (state *dodataState) dodataSect(ctxt *Link, symn sym.SymKind, syms []loader return si < sj }) } else { - // PCLNTAB was built internally, and has the proper order based on value. - // Sort the symbols as such. - for k, s := range syms { - sl[k].val = ldr.SymValue(s) - } - sort.Slice(sl, func(i, j int) bool { return sl[i].val < sl[j].val }) + // PCLNTAB was built internally, and already has the proper order. } // Set alignment, construct result From 9c7f0b1ccd37b6f41a5326c451c633c92e93870c Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Mon, 11 Apr 2022 18:57:44 -0400 Subject: [PATCH 087/137] cmd/link: mangle symbol ABI name for linker-generated symbols The ABI mangling code skips symbols that are not loaded from Go objects. Usually that is fine, as other symbols don't need name mangling. But trampolines are linker generated and have the same symbol version (ABI) as the underlying symbol. We need to avoid symbol name collisions for trampolines, such as a trampoline to f and a trampoline to f. We could explicitly incorportate the ABI into the trampoline name. But as we already have the name mangling scheme we could just use that. The original code excludes external symbols probably because symbols from C object don't need mangling. But a C symbol and a Go symbol shouldn't have same name, and so the condition won't apply. Also exclude static symbols as they don't need mangling. Change-Id: I298eb1d64bc0c3da0154f0146b95c4d26ca2f47a Reviewed-on: https://go-review.googlesource.com/c/go/+/399894 Run-TryBot: Cherry Mui TryBot-Result: Gopher Robot Reviewed-by: Than McIntosh --- src/cmd/link/internal/ld/symtab.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go index 39066da286fba..63e140aa7181a 100644 --- a/src/cmd/link/internal/ld/symtab.go +++ b/src/cmd/link/internal/ld/symtab.go @@ -857,7 +857,7 @@ func mangleABIName(ctxt *Link, ldr *loader.Loader, x loader.Sym, name string) st return name } - if !ldr.IsExternal(x) && ldr.SymType(x) == sym.STEXT && ldr.SymVersion(x) != sym.SymVerABIInternal { + if ldr.SymType(x) == sym.STEXT && ldr.SymVersion(x) != sym.SymVerABIInternal && ldr.SymVersion(x) < sym.SymVerStatic { if s2 := ldr.Lookup(name, sym.SymVerABIInternal); s2 != 0 && ldr.SymType(s2) == sym.STEXT { name = fmt.Sprintf("%s.abi%d", name, ldr.SymVersion(x)) } From 65558a4f3da02fd7ad3d7ea93821c2a1be5508d7 Mon Sep 17 00:00:00 2001 From: hopehook Date: Mon, 11 Apr 2022 18:38:08 +0800 Subject: [PATCH 088/137] cmd/asm: update comment to refer to #44505 Updates #44505 Change-Id: I400110c33e69decf133fe9c4b582a450b7258b39 Reviewed-on: https://go-review.googlesource.com/c/go/+/399514 Reviewed-by: Ian Lance Taylor Reviewed-by: Matthew Dempsky Run-TryBot: Matthew Dempsky Auto-Submit: Matthew Dempsky TryBot-Result: Gopher Robot --- src/cmd/asm/internal/lex/tokenizer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/asm/internal/lex/tokenizer.go b/src/cmd/asm/internal/lex/tokenizer.go index 861a2d421d5a8..4db88e20c371e 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 From b55a2fb3b0d67b346bac871737b862f16e5a6447 Mon Sep 17 00:00:00 2001 From: Meng Zhuo Date: Sat, 6 Nov 2021 22:38:51 +0800 Subject: [PATCH 089/137] runtime: port memmove, memclr to register ABI on riscv64 This allows memmove and memclr to be invoked using the new register ABI on riscv64. Change-Id: I3308d52e06547836cffcc533740fe535624e78d8 Reviewed-on: https://go-review.googlesource.com/c/go/+/361975 Run-TryBot: mzh TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Reviewed-by: Ian Lance Taylor --- src/runtime/memclr_riscv64.s | 30 ++++++++++--------- src/runtime/memmove_riscv64.s | 54 ++++++++++++++++++----------------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/runtime/memclr_riscv64.s b/src/runtime/memclr_riscv64.s index 54ddaa4560c77..f0e517a547eb2 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_riscv64.s b/src/runtime/memmove_riscv64.s index 5dec8d0a33fcc..538aee3642245 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 From 517781b39181e26cff880b656787fac65a63092c Mon Sep 17 00:00:00 2001 From: Wayne Zuo Date: Fri, 8 Apr 2022 17:33:50 +0800 Subject: [PATCH 090/137] cmd/compile: add SARXQload and SARXLload Change-Id: I4e8dc5009a5b8af37d71b62f1322f94806d3e9d9 Reviewed-on: https://go-review.googlesource.com/c/go/+/399056 Run-TryBot: Wayne Zuo Reviewed-by: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Keith Randall Reviewed-by: Cherry Mui --- src/cmd/compile/internal/amd64/ssa.go | 7 +- .../compile/internal/ssa/addressingmodes.go | 7 + src/cmd/compile/internal/ssa/gen/AMD64.rules | 1 + src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 7 + src/cmd/compile/internal/ssa/opGen.go | 136 ++++++++++++++++++ src/cmd/compile/internal/ssa/rewriteAMD64.go | 44 ++++++ test/codegen/bmi.go | 16 +++ 7 files changed, 216 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 9fde775358328..1ec86233209f1 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -287,7 +287,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { 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) @@ -295,8 +296,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) diff --git a/src/cmd/compile/internal/ssa/addressingmodes.go b/src/cmd/compile/internal/ssa/addressingmodes.go index c18ea68665735..469ba0d494785 100644 --- a/src/cmd/compile/internal/ssa/addressingmodes.go +++ b/src/cmd/compile/internal/ssa/addressingmodes.go @@ -344,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, diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 3a9de8dd03f52..2ffdea3d559c5 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -2256,5 +2256,6 @@ && clobber(x0, x1, sh) => @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p1 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) (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) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index 2eec6e03249da..23c157c2c56af 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -956,11 +956,18 @@ func init() { {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: "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 diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index 976d8873216bd..c66c8d33d4a03 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1064,10 +1064,17 @@ const ( OpAMD64MOVBEQstoreidx8 OpAMD64SARXQ OpAMD64SARXL + OpAMD64SARXLload + OpAMD64SARXQload OpAMD64SHLXLload OpAMD64SHLXQload OpAMD64SHRXLload OpAMD64SHRXQload + OpAMD64SARXLloadidx1 + OpAMD64SARXLloadidx4 + OpAMD64SARXLloadidx8 + OpAMD64SARXQloadidx1 + OpAMD64SARXQloadidx8 OpAMD64SHLXLloadidx1 OpAMD64SHLXLloadidx4 OpAMD64SHLXLloadidx8 @@ -14147,6 +14154,40 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + 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, @@ -14215,6 +14256,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/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index 81f1f1ae4eb9e..ecea8f09623e8 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -20565,6 +20565,28 @@ func rewriteValueAMD64_OpAMD64SARXL(v *Value) bool { 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_OpAMD64SARXQ(v *Value) bool { @@ -20761,6 +20783,28 @@ func rewriteValueAMD64_OpAMD64SARXQ(v *Value) bool { 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_OpAMD64SBBLcarrymask(v *Value) bool { diff --git a/test/codegen/bmi.go b/test/codegen/bmi.go index 9dd2b0039ceaf..1641d5ddd08d5 100644 --- a/test/codegen/bmi.go +++ b/test/codegen/bmi.go @@ -56,6 +56,22 @@ func sarx32(x, y int32) int32 { 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 []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 From 66f03f79dadc6005d30a6edf4419b8f6c0fa6398 Mon Sep 17 00:00:00 2001 From: Wayne Zuo Date: Sat, 9 Apr 2022 14:40:40 +0800 Subject: [PATCH 091/137] cmd/compile: add SHLX&SHRX without load Change-Id: I79eb5e7d6bcb23f26d3a100e915efff6dae70391 Reviewed-on: https://go-review.googlesource.com/c/go/+/399061 Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Cherry Mui --- src/cmd/compile/internal/amd64/ssa.go | 4 +- src/cmd/compile/internal/ssa/gen/AMD64.rules | 88 +- src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 4 + src/cmd/compile/internal/ssa/opGen.go | 60 + src/cmd/compile/internal/ssa/rewriteAMD64.go | 2422 +++++++++++++++++- test/codegen/bmi.go | 20 +- 6 files changed, 2484 insertions(+), 114 deletions(-) diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 1ec86233209f1..98f90748d6e90 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -282,7 +282,9 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) { p.To.Reg = v.Reg() p.SetFrom3Reg(v.Args[1].Reg()) - case ssa.OpAMD64SARXL, ssa.OpAMD64SARXQ: + 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()) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 2ffdea3d559c5..1bee810fbf30e 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -206,8 +206,10 @@ (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 instruction because it has less register restriction on the shift input. +// 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)) @@ -593,6 +595,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)) @@ -601,6 +605,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)) @@ -612,6 +618,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)) @@ -624,9 +634,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. @@ -638,6 +649,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). @@ -653,6 +666,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 @@ -794,6 +809,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) @@ -801,6 +818,8 @@ (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) @@ -810,25 +829,25 @@ (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|SARXQ) x (ADDQconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) -((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ (ADDQconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ y)) -((SHLQ|SHRQ|SARQ|SARXQ) x (ANDQconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) -((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ (ANDQconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGQ y)) - -((SHLL|SHRL|SARL|SARXL) x (ADDQconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x y) -((SHLL|SHRL|SARL|SARXL) x (NEGQ (ADDQconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x (NEGQ y)) -((SHLL|SHRL|SARL|SARXL) x (ANDQconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x y) -((SHLL|SHRL|SARL|SARXL) x (NEGQ (ANDQconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x (NEGQ y)) - -((SHLQ|SHRQ|SARQ|SARXQ) x (ADDLconst [c] y)) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) -((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL (ADDLconst [c] y))) && c & 63 == 0 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL y)) -((SHLQ|SHRQ|SARQ|SARXQ) x (ANDLconst [c] y)) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x y) -((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL (ANDLconst [c] y))) && c & 63 == 63 => ((SHLQ|SHRQ|SARQ|SARXQ) x (NEGL y)) - -((SHLL|SHRL|SARL|SARXL) x (ADDLconst [c] y)) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x y) -((SHLL|SHRL|SARL|SARXL) x (NEGL (ADDLconst [c] y))) && c & 31 == 0 => ((SHLL|SHRL|SARL|SARXL) x (NEGL y)) -((SHLL|SHRL|SARL|SARXL) x (ANDLconst [c] y)) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x y) -((SHLL|SHRL|SARL|SARXL) x (NEGL (ANDLconst [c] y))) && c & 31 == 31 => ((SHLL|SHRL|SARL|SARXL) x (NEGL 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|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|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|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]) @@ -860,9 +879,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) @@ -877,6 +900,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]))) @@ -887,6 +919,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) @@ -920,6 +961,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 @@ -2257,5 +2299,5 @@ => @mergePoint(b,x0,x1) (MOVBEQload [i] {s} p1 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) -(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) +(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) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index 23c157c2c56af..becee876dfcda 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -955,6 +955,10 @@ func init() { // 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 diff --git a/src/cmd/compile/internal/ssa/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index c66c8d33d4a03..db52b53a28f19 100644 --- a/src/cmd/compile/internal/ssa/opGen.go +++ b/src/cmd/compile/internal/ssa/opGen.go @@ -1064,6 +1064,10 @@ const ( OpAMD64MOVBEQstoreidx8 OpAMD64SARXQ OpAMD64SARXL + OpAMD64SHLXQ + OpAMD64SHLXL + OpAMD64SHRXQ + OpAMD64SHRXL OpAMD64SARXLload OpAMD64SARXQload OpAMD64SHLXLload @@ -14154,6 +14158,62 @@ var opcodeTable = [...]opInfo{ }, }, }, + { + 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, diff --git a/src/cmd/compile/internal/ssa/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index ecea8f09623e8..f5ec7dc003753 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -442,6 +442,10 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpAMD64SHLQ(v) case OpAMD64SHLQconst: return rewriteValueAMD64_OpAMD64SHLQconst(v) + case OpAMD64SHLXL: + return rewriteValueAMD64_OpAMD64SHLXL(v) + case OpAMD64SHLXQ: + return rewriteValueAMD64_OpAMD64SHLXQ(v) case OpAMD64SHRB: return rewriteValueAMD64_OpAMD64SHRB(v) case OpAMD64SHRBconst: @@ -458,6 +462,10 @@ func rewriteValueAMD64(v *Value) bool { return rewriteValueAMD64_OpAMD64SHRW(v) case OpAMD64SHRWconst: return rewriteValueAMD64_OpAMD64SHRWconst(v) + case OpAMD64SHRXL: + return rewriteValueAMD64_OpAMD64SHRXL(v) + case OpAMD64SHRXQ: + return rewriteValueAMD64_OpAMD64SHRXQ(v) case OpAMD64SUBL: return rewriteValueAMD64_OpAMD64SUBL(v) case OpAMD64SUBLconst: @@ -2704,6 +2712,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) @@ -3121,6 +3152,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 { @@ -3142,6 +3189,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 { @@ -3170,6 +3233,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) @@ -3873,6 +3959,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) @@ -3925,6 +4027,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 { @@ -3981,6 +4099,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 { @@ -15890,6 +16024,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) @@ -16200,6 +16353,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) @@ -16408,6 +16761,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) @@ -16616,6 +17177,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 { @@ -17509,6 +18278,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) @@ -17789,6 +18577,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 { @@ -17837,6 +18825,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 { @@ -22062,6 +23098,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)) @@ -22487,6 +23577,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) @@ -23978,6 +25134,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)) @@ -24403,6 +25613,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) @@ -24917,6 +26193,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 { @@ -25107,28 +26396,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 { @@ -25173,6 +26440,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 { @@ -25363,28 +26643,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 { @@ -25437,6 +26695,442 @@ 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_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_OpAMD64SHRB(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -25524,6 +27218,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 { @@ -25714,28 +27421,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 { @@ -25768,6 +27453,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 { @@ -25958,28 +27656,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 { @@ -26091,6 +27767,442 @@ 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_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_OpAMD64SUBL(v *Value) bool { v_1 := v.Args[1] v_0 := v.Args[0] @@ -27374,6 +29486,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) @@ -27911,6 +30042,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) @@ -34533,6 +36683,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)) @@ -35336,6 +37534,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/test/codegen/bmi.go b/test/codegen/bmi.go index 1641d5ddd08d5..3b125a1b5901c 100644 --- a/test/codegen/bmi.go +++ b/test/codegen/bmi.go @@ -72,7 +72,23 @@ func sarx32_load(x []int32, i int) int32 { return s } -func shlrx64(x []uint64, i int, s uint64) uint64 { +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]*` @@ -80,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]*` From a78db879b31b072c37c6d46cc404d8e131d54349 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Tue, 12 Apr 2022 21:22:22 -0700 Subject: [PATCH 092/137] crypto/x509: omit empty extensions SEQUENCE In CreateCertificate, if there are no extensions, don't include the extensions SEQUENCE in the encoded certificate. Why, you might ask, does the encoding/asn1 tag 'optional' not do the same thing as 'omitempty'? Good question, no clue, fixing that would probably break things in horrific ways. Fixes #52319 Change-Id: I84fdd5ff3e4e0b0a59e3bf86e7439753b1e1477b Reviewed-on: https://go-review.googlesource.com/c/go/+/399827 Run-TryBot: Roland Shoemaker TryBot-Result: Gopher Robot Reviewed-by: Damien Neil Reviewed-by: Roland Shoemaker Auto-Submit: Roland Shoemaker --- src/crypto/x509/x509.go | 2 +- src/crypto/x509/x509_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index bcc14a0056d5e..e28e213dc123a 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -155,7 +155,7 @@ type tbsCertificate struct { PublicKey publicKeyInfo UniqueId asn1.BitString `asn1:"optional,tag:1"` SubjectUniqueId asn1.BitString `asn1:"optional,tag:2"` - Extensions []pkix.Extension `asn1:"optional,explicit,tag:3"` + Extensions []pkix.Extension `asn1:"omitempty,optional,explicit,tag:3"` } type dsaAlgorithmParameters struct { diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index d8dde25019546..818a9750c34c9 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -3568,3 +3568,27 @@ func TestRevocationListCheckSignatureFrom(t *testing.T) { }) } } + +func TestOmitEmptyExtensions(t *testing.T) { + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + tmpl := &Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: ":)", + }, + NotAfter: time.Now().Add(time.Hour), + NotBefore: time.Now().Add(-time.Hour), + } + der, err := CreateCertificate(rand.Reader, tmpl, tmpl, k.Public(), k) + if err != nil { + t.Fatal(err) + } + + emptyExtSeq := []byte{0xA3, 0x02, 0x30, 0x00} + if bytes.Contains(der, emptyExtSeq) { + t.Error("DER encoding contains the an empty extensions SEQUENCE") + } +} From d65a41329ee87f46f35719129d1d4b03d4a07cc8 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 13 Apr 2022 08:58:01 -0700 Subject: [PATCH 093/137] crypto/x509: support nil pools in CertPool.Equal Otherwise we panic if either pool is nil. Change-Id: I8598e3c0f3a5294135f1c330e319128d552ebb67 Reviewed-on: https://go-review.googlesource.com/c/go/+/399161 Reviewed-by: Damien Neil Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker Reviewed-by: Roland Shoemaker TryBot-Result: Gopher Robot --- src/crypto/x509/cert_pool.go | 3 + src/crypto/x509/cert_pool_test.go | 124 +++++++++++++++++++++--------- 2 files changed, 90 insertions(+), 37 deletions(-) diff --git a/src/crypto/x509/cert_pool.go b/src/crypto/x509/cert_pool.go index ae43c84424bcf..266d1ea04a147 100644 --- a/src/crypto/x509/cert_pool.go +++ b/src/crypto/x509/cert_pool.go @@ -252,6 +252,9 @@ func (s *CertPool) Subjects() [][]byte { // Equal reports whether s and other are equal. func (s *CertPool) Equal(other *CertPool) bool { + if s == nil || other == nil { + return s == other + } if s.systemPool != other.systemPool || len(s.haveSum) != len(other.haveSum) { return false } diff --git a/src/crypto/x509/cert_pool_test.go b/src/crypto/x509/cert_pool_test.go index d1ec9aaefd8c6..a12beda83d353 100644 --- a/src/crypto/x509/cert_pool_test.go +++ b/src/crypto/x509/cert_pool_test.go @@ -7,52 +7,102 @@ package x509 import "testing" func TestCertPoolEqual(t *testing.T) { - a, b := NewCertPool(), NewCertPool() - if !a.Equal(b) { - t.Error("two empty pools not equal") - } - tc := &Certificate{Raw: []byte{1, 2, 3}, RawSubject: []byte{2}} - a.AddCert(tc) - if a.Equal(b) { - t.Error("empty pool equals non-empty pool") - } - - b.AddCert(tc) - if !a.Equal(b) { - t.Error("two non-empty pools not equal") - } - otherTC := &Certificate{Raw: []byte{9, 8, 7}, RawSubject: []byte{8}} - a.AddCert(otherTC) - if a.Equal(b) { - t.Error("non-equal pools equal") - } - systemA, err := SystemCertPool() + emptyPool := NewCertPool() + nonSystemPopulated := NewCertPool() + nonSystemPopulated.AddCert(tc) + nonSystemPopulatedAlt := NewCertPool() + nonSystemPopulatedAlt.AddCert(otherTC) + emptySystem, err := SystemCertPool() if err != nil { - t.Fatalf("unable to load system cert pool: %s", err) + t.Fatal(err) } - systemB, err := SystemCertPool() + populatedSystem, err := SystemCertPool() if err != nil { - t.Fatalf("unable to load system cert pool: %s", err) - } - if !systemA.Equal(systemB) { - t.Error("two empty system pools not equal") + t.Fatal(err) } - - systemA.AddCert(tc) - if systemA.Equal(systemB) { - t.Error("empty system pool equals non-empty system pool") + populatedSystem.AddCert(tc) + populatedSystemAlt, err := SystemCertPool() + if err != nil { + t.Fatal(err) } - - systemB.AddCert(tc) - if !systemA.Equal(systemB) { - t.Error("two non-empty system pools not equal") + populatedSystemAlt.AddCert(otherTC) + tests := []struct { + name string + a *CertPool + b *CertPool + equal bool + }{ + { + name: "two empty pools", + a: emptyPool, + b: emptyPool, + equal: true, + }, + { + name: "one empty pool, one populated pool", + a: emptyPool, + b: nonSystemPopulated, + equal: false, + }, + { + name: "two populated pools", + a: nonSystemPopulated, + b: nonSystemPopulated, + equal: true, + }, + { + name: "two populated pools, different content", + a: nonSystemPopulated, + b: nonSystemPopulatedAlt, + equal: false, + }, + { + name: "two empty system pools", + a: emptySystem, + b: emptySystem, + equal: true, + }, + { + name: "one empty system pool, one populated system pool", + a: emptySystem, + b: populatedSystem, + equal: false, + }, + { + name: "two populated system pools", + a: populatedSystem, + b: populatedSystem, + equal: true, + }, + { + name: "two populated pools, different content", + a: populatedSystem, + b: populatedSystemAlt, + equal: false, + }, + { + name: "two nil pools", + a: nil, + b: nil, + equal: true, + }, + { + name: "one nil pool, one empty pool", + a: nil, + b: emptyPool, + equal: false, + }, } - systemA.AddCert(otherTC) - if systemA.Equal(systemB) { - t.Error("non-equal system pools equal") + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + equal := tc.a.Equal(tc.b) + if equal != tc.equal { + t.Errorf("Unexpected Equal result: got %t, want %t", equal, tc.equal) + } + }) } } From 9298f604f474ef6dcd5fdfb73877d63b1972c29f Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Wed, 13 Apr 2022 11:49:15 -0700 Subject: [PATCH 094/137] crypto/x509: add CertPool.Clone Export the previously private method copy as Clone. Fixes #35044 Change-Id: I5403d6a3b9f344c980c1c89a6823e1a49dcda26b Reviewed-on: https://go-review.googlesource.com/c/go/+/400175 Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker Reviewed-by: Roland Shoemaker Reviewed-by: Damien Neil TryBot-Result: Gopher Robot --- api/next/35044.txt | 1 + src/crypto/x509/cert_pool.go | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 api/next/35044.txt diff --git a/api/next/35044.txt b/api/next/35044.txt new file mode 100644 index 0000000000000..0ed6f2e4d04d0 --- /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/src/crypto/x509/cert_pool.go b/src/crypto/x509/cert_pool.go index 266d1ea04a147..e9b2c122b9633 100644 --- a/src/crypto/x509/cert_pool.go +++ b/src/crypto/x509/cert_pool.go @@ -77,7 +77,8 @@ func (s *CertPool) cert(n int) (*Certificate, error) { return s.lazyCerts[n].getCert() } -func (s *CertPool) copy() *CertPool { +// Clone returns a copy of s. +func (s *CertPool) Clone() *CertPool { p := &CertPool{ byName: make(map[string][]int, len(s.byName)), lazyCerts: make([]lazyCert, len(s.lazyCerts)), @@ -109,7 +110,7 @@ func (s *CertPool) copy() *CertPool { // New changes in the system cert pool might not be reflected in subsequent calls. func SystemCertPool() (*CertPool, error) { if sysRoots := systemRootsPool(); sysRoots != nil { - return sysRoots.copy(), nil + return sysRoots.Clone(), nil } return loadSystemRoots() From 72e77a7f41bbf45d466119444307fd3ae996e257 Mon Sep 17 00:00:00 2001 From: zhangyunhao Date: Tue, 14 Dec 2021 13:52:05 +0800 Subject: [PATCH 095/137] sort: use pdqsort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Across all benchmarks, pdqsort is never significantly slower than the previous algorithm. - In common patterns, pdqsort is often faster (i.e. 10x faster in sorted slices). The pdqsort is described at https://arxiv.org/pdf/2106.05123.pdf This CL is inspired by both C++ implementation and Rust implementation. - C++ implementation: https://github.com/orlp/pdqsort - Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/ For #50154 name old time/op new time/op delta SearchWrappers-16 72.8ns ± 3% 75.1ns ± 2% +3.25% (p=0.000 n=20+10) SortString1K-16 85.2µs ± 3% 86.2µs ± 5% ~ (p=0.247 n=19+10) SortString1K_Slice-16 84.6µs ± 4% 86.1µs ± 4% ~ (p=0.120 n=20+10) StableString1K-16 112µs ± 5% 112µs ± 5% ~ (p=0.604 n=19+10) SortInt1K-16 44.8µs ± 3% 43.2µs ± 2% -3.68% (p=0.000 n=20+10) SortInt1K_Sorted-16 28.2µs ± 3% 3.3µs ± 3% -88.16% (p=0.000 n=19+10) SortInt1K_Reversed-16 29.4µs ± 3% 4.8µs ± 2% -83.59% (p=0.000 n=20+10) SortInt1K_Mod8-16 25.1µs ± 2% 20.0µs ± 2% -20.35% (p=0.000 n=18+10) StableInt1K-16 51.3µs ± 3% 50.9µs ± 2% ~ (p=0.562 n=20+9) StableInt1K_Slice-16 49.5µs ± 2% 50.7µs ± 4% +2.55% (p=0.009 n=19+10) SortInt64K-16 4.73ms ± 3% 4.49ms ± 4% -5.08% (p=0.000 n=20+10) SortInt64K_Slice-16 4.51ms ± 3% 4.35ms ± 1% -3.42% (p=0.000 n=20+8) StableInt64K-16 4.85ms ± 2% 4.82ms ± 2% ~ (p=0.267 n=20+10) Sort1e2-16 27.9µs ± 1% 28.1µs ± 2% ~ (p=0.198 n=20+10) Stable1e2-16 56.6µs ± 2% 55.0µs ± 2% -2.88% (p=0.000 n=20+10) Sort1e4-16 5.51ms ± 1% 5.36ms ± 1% -2.58% (p=0.000 n=19+9) Stable1e4-16 17.8ms ± 1% 17.3ms ± 1% -2.40% (p=0.000 n=20+10) Sort1e6-16 833ms ± 1% 807ms ± 1% -3.02% (p=0.000 n=20+10) Stable1e6-16 3.49s ± 2% 3.44s ± 1% -1.41% (p=0.001 n=20+10) Change-Id: Iecded047d237b9330b5a4101001a5fdc2f50646a Reviewed-on: https://go-review.googlesource.com/c/go/+/371574 Reviewed-by: Emmanuel Odeke Run-TryBot: Ian Lance Taylor Reviewed-by: Keith Randall Run-TryBot: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Eli Bendersky --- src/sort/example_multi_test.go | 2 +- src/sort/export_test.go | 4 + src/sort/gen_sort_variants.go | 391 +++++++++++++++++++++++---------- src/sort/slice.go | 5 +- src/sort/sort.go | 37 +++- src/sort/sort_test.go | 70 ++++++ src/sort/zsortfunc.go | 371 +++++++++++++++++++++---------- src/sort/zsortinterface.go | 371 +++++++++++++++++++++---------- 8 files changed, 885 insertions(+), 366 deletions(-) diff --git a/src/sort/example_multi_test.go b/src/sort/example_multi_test.go index de6ec142d1c44..93f2d3ec5754a 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,146 +247,283 @@ 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] } -func swapRange{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, n int {{.ExtraParam}}) { - for i := 0; i < n; i++ { - {{Swap "data" "a+i" "b+i"}} +// 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 } -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 +// 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 ; b < c && !{{Less "data" "pivot" "b"}}; b++ { // data[b] <= pivot + for i <= j && {{GreaterOrEqual "data" "a" "i"}} { + i++ } - for ; b < c && {{Less "data" "pivot" "c-1"}}; c-- { // data[c-1] > pivot + for i <= j && {{Less "data" "a" "j"}} { + j-- } - if b >= c { + if i > j { 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 + {{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"}} } - if a >= b { - break + } + // 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"}} } - // 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 + return false } -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) +// 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"}} } } - 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"}} - } +} + +// 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}}) } - insertionSort{{.FuncSuffix}}(data, a, b {{.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}}) { + for i := 0; i < n; i++ { + {{Swap "data" "a+i" "b+i"}} } } @@ -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/slice.go b/src/sort/slice.go index ba5c2e2f3d3f1..443182b42ebfd 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 aed0eaba303bd..68e2f0d082f18 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 bfff3528d3ff5..862bba2d4415a 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 80c8a77995987..49b6169b9769d 100644 --- a/src/sort/zsortfunc.go +++ b/src/sort/zsortfunc.go @@ -52,146 +52,283 @@ func heapSort_func(data lessSwap, a, b int) { } } -// Quicksort, loosely following Bentley and McIlroy, -// "Engineering a Sort Function" SP&E November 1993. - -// 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) +// 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 + + 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 } - } - // now data[m0] <= data[m1] <= data[m2] -} -func swapRange_func(data lessSwap, a, b, n int) { - for i := 0; i < n; i++ { - data.Swap(a+i, b+i) + // 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 + } } } -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) +// 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-- } - 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++ { + if i > j { + data.Swap(j, a) + return j, true } - b := a + data.Swap(i, j) + i++ + j-- + for { - for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot + for i <= j && data.Less(i, a) { + i++ } - for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot + for i <= j && !data.Less(j, a) { + j-- } - if b >= c { + if i > j { break } - // data[b] > pivot; data[c-1] <= pivot - data.Swap(b, c-1) - b++ - c-- + data.Swap(i, j) + i++ + j-- } - // 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 + 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-- } - 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 + 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) } - if a >= b { - break + } + // 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) } - // 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 + return false } -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) +// 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) } } - 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) - } +} + +// 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) } - insertionSort_func(data, a, b) + // 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) { + for i := 0; i < n; i++ { + data.Swap(a+i, b+i) } } diff --git a/src/sort/zsortinterface.go b/src/sort/zsortinterface.go index e0d7093678549..51fa5032e9912 100644 --- a/src/sort/zsortinterface.go +++ b/src/sort/zsortinterface.go @@ -52,146 +52,283 @@ func heapSort(data Interface, a, b int) { } } -// Quicksort, loosely following Bentley and McIlroy, -// "Engineering a Sort Function" SP&E November 1993. - -// 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) +// 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 + + 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 } - } - // now data[m0] <= data[m1] <= data[m2] -} -func swapRange(data Interface, a, b, n int) { - for i := 0; i < n; i++ { - data.Swap(a+i, b+i) + // 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 + } } } -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) +// 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-- } - 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++ { + if i > j { + data.Swap(j, a) + return j, true } - b := a + data.Swap(i, j) + i++ + j-- + for { - for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot + for i <= j && data.Less(i, a) { + i++ } - for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot + for i <= j && !data.Less(j, a) { + j-- } - if b >= c { + if i > j { break } - // data[b] > pivot; data[c-1] <= pivot - data.Swap(b, c-1) - b++ - c-- + data.Swap(i, j) + i++ + j-- } - // 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 + 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-- } - 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 + 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) } - if a >= b { - break + } + // 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) } - // 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 + return false } -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) +// 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) } } - 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) - } +} + +// 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) } - insertionSort(data, a, b) + // 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) { + for i := 0; i < n; i++ { + data.Swap(a+i, b+i) } } From d2552037426fe5a190c74172562d897d921fe311 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Wed, 13 Apr 2022 00:32:15 +0000 Subject: [PATCH 096/137] cmd/compile: remove unused offset calculation in ssagen#rtcall This offR accumulation isn't used and some really similar code is done later in the Load results block. Change-Id: I2f77a7bfd568e7e5eb9fc519e7c552401b3af9b8 GitHub-Last-Rev: 2c91e5c8987d21203c494f278ff1e05aa3941211 GitHub-Pull-Request: golang/go#52316 Reviewed-on: https://go-review.googlesource.com/c/go/+/400094 Run-TryBot: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Reviewed-by: Matthew Dempsky --- src/cmd/compile/internal/ssagen/ssa.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index bd0b9250196ab..7da145e08db27 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -5535,13 +5535,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)) From cb702a2a5670d4fa599f573b37b1a01abc9f995a Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Wed, 13 Apr 2022 17:33:24 -0700 Subject: [PATCH 097/137] cmd/compile: fold constant shifts into (SHL|SHR|SAR)Xload ops We should prefer a constant shift op to a X shift op. That way we don't have to materialize the constant to shift by. Should fix GOAMD64=v3 builder Change-Id: I56b45d2940c959382b970e3f962ed4a09cc2a239 Reviewed-on: https://go-review.googlesource.com/c/go/+/400254 Reviewed-by: Cherry Mui Reviewed-by: Wayne Zuo Run-TryBot: Dmitri Shuralyov TryBot-Result: Gopher Robot Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/gen/AMD64.rules | 4 + src/cmd/compile/internal/ssa/rewriteAMD64.go | 240 +++++++++++++++++++ 2 files changed, 244 insertions(+) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 1bee810fbf30e..1fd36bfc88774 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -2301,3 +2301,7 @@ (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/rewriteAMD64.go b/src/cmd/compile/internal/ssa/rewriteAMD64.go index f5ec7dc003753..67ccc99679a1b 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -384,8 +384,12 @@ func rewriteValueAMD64(v *Value) bool { 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: @@ -444,8 +448,12 @@ func rewriteValueAMD64(v *Value) bool { 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: @@ -464,8 +472,12 @@ func rewriteValueAMD64(v *Value) bool { 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: @@ -21625,6 +21637,34 @@ func rewriteValueAMD64_OpAMD64SARXL(v *Value) bool { } 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] @@ -21843,6 +21883,54 @@ func rewriteValueAMD64_OpAMD64SARXQ(v *Value) bool { } 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)) @@ -26913,6 +27001,34 @@ func rewriteValueAMD64_OpAMD64SHLXL(v *Value) bool { } 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] @@ -27131,6 +27247,54 @@ func rewriteValueAMD64_OpAMD64SHLXQ(v *Value) bool { } 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] @@ -27985,6 +28149,34 @@ func rewriteValueAMD64_OpAMD64SHRXL(v *Value) bool { } 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] @@ -28203,6 +28395,54 @@ func rewriteValueAMD64_OpAMD64SHRXQ(v *Value) bool { } 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] From 62b8ec744b8e10b80f9271fed93116387c9128ef Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Wed, 13 Apr 2022 13:31:24 -0700 Subject: [PATCH 098/137] math: improve documentation of Copysign Name the arguments in a way that is more self-describing. Many code editor tools show a snippet of the function and its arguments. However, "x" and "y" are not helpful in determining which is the sign and which is the magnitude, short of reading the documentation itself. Name the sign argument as "sign" to be explicit. This follows the same naming convention as IsInf. Change-Id: Ie3055009e475f96c92d5ea7bfe9828eed908c78b Reviewed-on: https://go-review.googlesource.com/c/go/+/400177 Run-TryBot: Joseph Tsai TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov --- src/math/copysign.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/math/copysign.go b/src/math/copysign.go index 719c64b9eba79..3a30afb41367e 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) } From dd97871282c28f1572f7cfe67395f848f69abb4b Mon Sep 17 00:00:00 2001 From: Than McIntosh Date: Thu, 14 Apr 2022 11:14:36 -0400 Subject: [PATCH 099/137] debug/dwarf: better stmt list attr checking in LineReader Check for insane statement list attribute values when constructing LineReader's for a compilation unit. Fixes #52354. Change-Id: Icb5298db31f6c5fe34c44e0ed4fe277a7cd676b9 Reviewed-on: https://go-review.googlesource.com/c/go/+/400255 Run-TryBot: Than McIntosh Auto-Submit: Than McIntosh TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/debug/dwarf/line.go | 2 +- src/debug/dwarf/line_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/debug/dwarf/line.go b/src/debug/dwarf/line.go index c4937ca7dd863..bb281fbdd93b7 100644 --- a/src/debug/dwarf/line.go +++ b/src/debug/dwarf/line.go @@ -152,7 +152,7 @@ func (d *Data) LineReader(cu *Entry) (*LineReader, error) { // cu has no line table. return nil, nil } - if off > int64(len(d.line)) { + if off < 0 || off > int64(len(d.line)) { return nil, errors.New("AttrStmtList value out of range") } // AttrCompDir is optional if all file names are absolute. Use diff --git a/src/debug/dwarf/line_test.go b/src/debug/dwarf/line_test.go index 9c6b6ff5b0326..163fc3bbb99bb 100644 --- a/src/debug/dwarf/line_test.go +++ b/src/debug/dwarf/line_test.go @@ -389,3 +389,32 @@ func TestPathJoin(t *testing.T) { } } } + +func TestPathLineReaderMalformed(t *testing.T) { + // This test case drawn from issue #52354. What's happening + // here is that the stmtList attribute in the compilation + // unit is malformed (negative). + var aranges, frame, pubnames, ranges, str []byte + abbrev := []byte{0x10, 0x20, 0x20, 0x20, 0x21, 0x20, 0x10, 0x21, 0x61, + 0x0, 0x0, 0xff, 0x20, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20} + info := []byte{0x0, 0x0, 0x0, 0x9, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, + 0x20, 0x10, 0x10} + line := []byte{0x20} + Data0, err := New(abbrev, aranges, frame, info, line, pubnames, ranges, str) + if err != nil { + t.Fatalf("error unexpected: %v", err) + } + Reader0 := Data0.Reader() + Entry0, err := Reader0.Next() + if err != nil { + t.Fatalf("error unexpected: %v", err) + } + LineReader0, err := Data0.LineReader(Entry0) + if err == nil { + t.Fatalf("expected error") + } + if LineReader0 != nil { + t.Fatalf("expected nil line reader") + } +} From 1ba96d8c0909eca59e28c048150c3834982f79fb Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Mon, 4 Oct 2021 12:17:46 -0700 Subject: [PATCH 100/137] cmd/compile: implement jump tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Performance is kind of hard to exactly quantify. One big difference between jump tables and the old binary search scheme is that there's only 1 branch statement instead of O(n) of them. That can be both a blessing and a curse, and can make evaluating jump tables very hard to do. The single branch can become a choke point for the hardware branch predictor. A branch table jump must fit all of its state in a single branch predictor entry (technically, a branch target predictor entry). With binary search that predictor state can be spread among lots of entries. In cases where the case selection is repetitive and thus predictable, binary search can perform better. The big win for a jump table is that it doesn't consume so much of the branch predictor's resources. But that benefit is essentially never observed in microbenchmarks, because the branch predictor can easily keep state for all the binary search branches in a microbenchmark. So that benefit is really hard to measure. So predictable switch microbenchmarks are ~useless - they will almost always favor the binary search scheme. Fully unpredictable switch microbenchmarks are better, as they aren't lying to us quite so much. In a perfectly unpredictable situation, a jump table will expect to incur 1-1/N branch mispredicts, where a binary search would incur lg(N)/2 of them. That makes the crossover point at about N=4. But of course switches in real programs are seldom fully unpredictable, so we'll use a higher crossover point. Beyond the branch predictor, jump tables tend to execute more instructions per switch but have no additional instructions per case, which also argues for a larger crossover. As far as code size goes, with this CL cmd/go has a slightly smaller code segment and a slightly larger overall size (from the jump tables themselves which live in the data segment). This is a case where some FDO (feedback-directed optimization) would be really nice to have. #28262 Some large-program benchmarks might help make the case for this CL. Especially if we can turn on branch mispredict counters so we can see how much using jump tables can free up branch prediction resources that can be gainfully used elsewhere in the program. name old time/op new time/op delta Switch8Predictable 1.89ns ± 2% 1.27ns ± 3% -32.58% (p=0.000 n=9+10) Switch8Unpredictable 9.33ns ± 1% 7.50ns ± 1% -19.60% (p=0.000 n=10+9) Switch32Predictable 2.20ns ± 2% 1.64ns ± 1% -25.39% (p=0.000 n=10+9) Switch32Unpredictable 10.0ns ± 2% 7.6ns ± 2% -24.04% (p=0.000 n=10+10) Fixes #5496 Update #34381 Change-Id: I3ff56011d02be53f605ca5fd3fb96b905517c34f Reviewed-on: https://go-review.googlesource.com/c/go/+/357330 Run-TryBot: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Reviewed-by: Keith Randall --- src/cmd/compile/internal/amd64/ssa.go | 10 ++ src/cmd/compile/internal/gc/obj.go | 3 + src/cmd/compile/internal/ir/node.go | 1 + src/cmd/compile/internal/ir/node_gen.go | 22 ++++ src/cmd/compile/internal/ir/op_string.go | 21 ++-- src/cmd/compile/internal/ir/stmt.go | 32 ++++++ src/cmd/compile/internal/ssa/check.go | 4 + src/cmd/compile/internal/ssa/config.go | 3 + src/cmd/compile/internal/ssa/export_test.go | 3 + src/cmd/compile/internal/ssa/gen/AMD64.rules | 2 + src/cmd/compile/internal/ssa/gen/AMD64Ops.go | 6 + .../compile/internal/ssa/gen/genericOps.go | 13 ++- src/cmd/compile/internal/ssa/gen/rulegen.go | 2 + src/cmd/compile/internal/ssa/opGen.go | 50 +++++---- src/cmd/compile/internal/ssa/rewrite.go | 7 ++ src/cmd/compile/internal/ssa/rewriteAMD64.go | 14 +++ src/cmd/compile/internal/ssagen/ssa.go | 106 ++++++++++++++++++ src/cmd/compile/internal/test/switch_test.go | 94 ++++++++++++++++ src/cmd/compile/internal/walk/stmt.go | 1 + src/cmd/compile/internal/walk/switch.go | 49 +++++++- src/cmd/internal/obj/link.go | 10 ++ src/cmd/internal/obj/x86/asm6.go | 10 ++ src/cmd/internal/sys/arch.go | 5 + 23 files changed, 428 insertions(+), 40 deletions(-) create mode 100644 src/cmd/compile/internal/test/switch_test.go diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 98f90748d6e90..7049d4e163bef 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -1400,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/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 74e4c0a89005d..fe8b6e9d45894 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/ir/node.go b/src/cmd/compile/internal/ir/node.go index 24908f3a13978..9ccb8e3c30edb 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) diff --git a/src/cmd/compile/internal/ir/node_gen.go b/src/cmd/compile/internal/ir/node_gen.go index 22ff885d68e9e..8d6fc8c607862 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 14eb84083a8d9..8927f18ceab72 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 80bd205436c5f..0e76f174408b3 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/ssa/check.go b/src/cmd/compile/internal/ssa/check.go index 28edfd2237d92..df677e674a04b 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/config.go b/src/cmd/compile/internal/ssa/config.go index f112881153b56..ddf2190e527be 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. diff --git a/src/cmd/compile/internal/ssa/export_test.go b/src/cmd/compile/internal/ssa/export_test.go index c4e87ec7d0f9e..87d1b41419cf3 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/gen/AMD64.rules b/src/cmd/compile/internal/ssa/gen/AMD64.rules index 1fd36bfc88774..81fdebaf49d20 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64.rules +++ b/src/cmd/compile/internal/ssa/gen/AMD64.rules @@ -517,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) diff --git a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go index becee876dfcda..fc42fa5e28b82 100644 --- a/src/cmd/compile/internal/ssa/gen/AMD64Ops.go +++ b/src/cmd/compile/internal/ssa/gen/AMD64Ops.go @@ -1001,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/genericOps.go b/src/cmd/compile/internal/ssa/gen/genericOps.go index 4f133b1ff6a1b..e04b7db6e7ca3 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 fe8db4ed1f251..0f7e970372636 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/opGen.go b/src/cmd/compile/internal/ssa/opGen.go index db52b53a28f19..0357fdb12ab46 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] } diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index 248060d27d993..4d615a064d929 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" @@ -1954,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 67ccc99679a1b..36e69781a5f1b 100644 --- a/src/cmd/compile/internal/ssa/rewriteAMD64.go +++ b/src/cmd/compile/internal/ssa/rewriteAMD64.go @@ -36873,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)) @@ -37455,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) diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 7da145e08db27..7b6b69ffc572d 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -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. @@ -6440,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 @@ -7052,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 { @@ -7705,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/test/switch_test.go b/src/cmd/compile/internal/test/switch_test.go new file mode 100644 index 0000000000000..6f7bfcf3d8c5f --- /dev/null +++ b/src/cmd/compile/internal/test/switch_test.go @@ -0,0 +1,94 @@ +// 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 +} + +// 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/walk/stmt.go b/src/cmd/compile/internal/walk/stmt.go index 4f38cb2c81d23..8a42dbf777cd4 100644 --- a/src/cmd/compile/internal/walk/stmt.go +++ b/src/cmd/compile/internal/walk/stmt.go @@ -85,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 3705c5b19265d..a4003ecea42ad 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" @@ -223,6 +224,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 +239,49 @@ 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 || !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) + // TODO: handle the size portion of string switches using a jump table. + return true +} + func (c *exprClause) test(exprname ir.Node) ir.Node { // Integer range. if c.hi != c.lo { @@ -562,7 +609,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/internal/obj/link.go b/src/cmd/internal/obj/link.go index a3eba73906f61..dc06a3aa11b78 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -495,10 +495,20 @@ type FuncInfo struct { ArgInfo *LSym // argument info for traceback ArgLiveInfo *LSym // argument liveness info for traceback WrapInfo *LSym // for wrapper, info of wrapped function + JumpTables []JumpTable FuncInfoSym *LSym } +// JumpTable represents a table used for implementing multi-way +// computed branching, used typically for implementing switches. +// Sym is the table itself, and Targets is a list of target +// instructions to go to for the computed branch index. +type JumpTable struct { + Sym *LSym + Targets []*Prog +} + // NewFuncInfo allocates and returns a FuncInfo for LSym. func (s *LSym) NewFuncInfo() *FuncInfo { if s.Extra != nil { diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go index 0e4c87ddcf764..b625845c09428 100644 --- a/src/cmd/internal/obj/x86/asm6.go +++ b/src/cmd/internal/obj/x86/asm6.go @@ -2230,6 +2230,16 @@ func span6(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { } obj.MarkUnsafePoints(ctxt, s.Func().Text, newprog, useTLS, nil) } + + // Now that we know byte offsets, we can generate jump table entries. + // TODO: could this live in obj instead of obj/$ARCH? + for _, jt := range s.Func().JumpTables { + for i, p := range jt.Targets { + // The ith jumptable entry points to the p.Pc'th + // byte in the function symbol s. + jt.Sym.WriteAddr(ctxt, int64(i)*8, 8, s, p.Pc) + } + } } func instinit(ctxt *obj.Link) { diff --git a/src/cmd/internal/sys/arch.go b/src/cmd/internal/sys/arch.go index ea76b596c15cb..84ed35ba8dd5c 100644 --- a/src/cmd/internal/sys/arch.go +++ b/src/cmd/internal/sys/arch.go @@ -52,6 +52,10 @@ type Arch struct { // can combine adjacent loads into a single larger, possibly unaligned, load. // Note that currently the optimizations must be able to handle little endian byte order. CanMergeLoads bool + + // CanJumpTable reports whether the backend can handle + // compiling a jump table. + CanJumpTable bool } // InFamily reports whether a is a member of any of the specified @@ -85,6 +89,7 @@ var ArchAMD64 = &Arch{ MinLC: 1, Alignment: 1, CanMergeLoads: true, + CanJumpTable: true, } var ArchARM = &Arch{ From 01b9ae22ed3c0f9c9ea29adbcd23bd97de6d18dd Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Sun, 6 Feb 2022 23:25:04 -0800 Subject: [PATCH 101/137] cmd/compile: constant-fold switches early in compilation So that the inliner knows all the other cases are dead and doesn't accumulate any cost for them. The canonical case for this is switching on runtime.GOOS, which occurs several places in the stdlib. Fixes #50253 Change-Id: I44823aaebb6c1b03c9b0c12d10086db81954350f Reviewed-on: https://go-review.googlesource.com/c/go/+/399694 Run-TryBot: Keith Randall Reviewed-by: Cuong Manh Le Reviewed-by: Ian Lance Taylor Reviewed-by: Russ Cox TryBot-Result: Gopher Robot --- src/cmd/compile/internal/deadcode/deadcode.go | 80 +++++++++++++++++++ test/inline.go | 45 +++++++++++ 2 files changed, 125 insertions(+) diff --git a/src/cmd/compile/internal/deadcode/deadcode.go b/src/cmd/compile/internal/deadcode/deadcode.go index c37a5a6990ced..decd2611836fb 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/test/inline.go b/test/inline.go index cb8403e9ce121..400898bceeb28 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" } From 3d8cb26504f9e3f5b45b4ea97b55714a25e016c1 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Sun, 6 Mar 2022 12:07:54 -0800 Subject: [PATCH 102/137] cmd/compile: modify switches of strings to use jump table for lengths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize the way we rewrite expression switches on strings, so that jump tables are naturally used for the outer switch on the string length. The changes to the prove pass in this CL are required so as to not repeat the test for string length in each case. name old time/op new time/op delta SwitchStringPredictable 2.28ns ± 9% 2.08ns ± 5% -9.04% (p=0.000 n=10+10) SwitchStringUnpredictable 10.5ns ± 1% 9.5ns ± 1% -9.08% (p=0.000 n=9+10) Update #5496 Update #34381 Change-Id: Ie6846b1dd27f3e472f7c30dfcc598c68d440b997 Reviewed-on: https://go-review.googlesource.com/c/go/+/395714 Run-TryBot: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Reviewed-by: Keith Randall --- src/cmd/compile/internal/ssa/prove.go | 82 +++++++++++++++----- src/cmd/compile/internal/test/switch_test.go | 43 ++++++++++ src/cmd/compile/internal/walk/switch.go | 68 +++++++++++++--- 3 files changed, 162 insertions(+), 31 deletions(-) diff --git a/src/cmd/compile/internal/ssa/prove.go b/src/cmd/compile/internal/ssa/prove.go index 8f86e16112781..26176af07c836 100644 --- a/src/cmd/compile/internal/ssa/prove.go +++ b/src/cmd/compile/internal/ssa/prove.go @@ -16,6 +16,10 @@ const ( unknown branch = iota positive negative + // The outedges from a jump table are jumpTable0, + // jumpTable0+1, jumpTable0+2, etc. There could be an + // arbitrary number so we can't list them all here. + jumpTable0 ) // relation represents the set of possible relations between @@ -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/test/switch_test.go b/src/cmd/compile/internal/test/switch_test.go index 6f7bfcf3d8c5f..30dee6257e380 100644 --- a/src/cmd/compile/internal/test/switch_test.go +++ b/src/cmd/compile/internal/test/switch_test.go @@ -77,6 +77,49 @@ func benchmarkSwitch32(b *testing.B, predictable bool) { 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 diff --git a/src/cmd/compile/internal/walk/switch.go b/src/cmd/compile/internal/walk/switch.go index a4003ecea42ad..6a2dbe1753e30 100644 --- a/src/cmd/compile/internal/walk/switch.go +++ b/src/cmd/compile/internal/walk/switch.go @@ -67,6 +67,7 @@ func walkSwitchExpr(sw *ir.SwitchStmt) { base.Pos = lno s := exprSwitch{ + pos: lno, exprname: cond, } @@ -113,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 @@ -183,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 } @@ -278,7 +322,6 @@ func (s *exprSwitch) tryJumpTable(cc []exprClause, out *ir.Nodes) bool { } } out.Append(jt) - // TODO: handle the size portion of string switches using a jump table. return true } @@ -587,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))) From c4b2288755d07b9505ef498819bb540b7b0fa215 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 14 Apr 2022 13:14:18 -0700 Subject: [PATCH 103/137] cmd/compile: add jump table codegen test Change-Id: Ic67f676f5ebe146166a0d3c1d78a802881320e49 Reviewed-on: https://go-review.googlesource.com/c/go/+/400375 Run-TryBot: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Cherry Mui Reviewed-by: Keith Randall --- test/codegen/switch.go | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/test/codegen/switch.go b/test/codegen/switch.go index 2ac817d14c849..a6566834a8679 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) + } +} From cc43e191ce562cd879a9baaf7a2e4fb1a7216d31 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Thu, 14 Apr 2022 14:02:25 -0700 Subject: [PATCH 104/137] crypto/x509: don't allow too long serials Don't create certificates that have serial numbers that are longer than 20 octets (when encoded), since these are explicitly disallowed by RFC 5280. Change-Id: I292b7001f45bed0971b2d519b6de26f0b90860ae Reviewed-on: https://go-review.googlesource.com/c/go/+/400377 Reviewed-by: Roland Shoemaker Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker TryBot-Result: Gopher Robot Reviewed-by: Damien Neil --- src/crypto/x509/x509.go | 12 ++++++++++++ src/crypto/x509/x509_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index e28e213dc123a..6d99191fefa59 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -1478,6 +1478,18 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, priv return nil, errors.New("x509: no SerialNumber given") } + // RFC 5280 Section 4.1.2.2: serial number must not be longer than 20 octets + // + // We cannot simply check for len(serialBytes) > 20, because encoding/asn1 may + // pad the slice in order to prevent the integer being mistaken for a negative + // number (DER uses the high bit of the left-most byte to indicate the sign.), + // so we need to double check the composition of the serial if it is exactly + // 20 bytes. + serialBytes := template.SerialNumber.Bytes() + if len(serialBytes) > 20 || (len(serialBytes) == 20 && serialBytes[0]&0x80 != 0) { + return nil, errors.New("x509: serial number exceeds 20 octets") + } + if template.BasicConstraintsValid && !template.IsCA && template.MaxPathLen != -1 && (template.MaxPathLen != 0 || template.MaxPathLenZero) { return nil, errors.New("x509: only CAs are allowed to specify MaxPathLen") } diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index 818a9750c34c9..c294f91ed645d 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -3592,3 +3592,39 @@ func TestOmitEmptyExtensions(t *testing.T) { t.Error("DER encoding contains the an empty extensions SEQUENCE") } } + +func TestCreateCertificateLongSerial(t *testing.T) { + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + + serialBytes := make([]byte, 21) + serialBytes[0] = 0x80 + serialBytes[20] = 1 + tooLong := big.NewInt(0).SetBytes(serialBytes) + + tmpl := &Certificate{ + SerialNumber: tooLong, + Subject: pkix.Name{ + CommonName: ":)", + }, + NotAfter: time.Now().Add(time.Hour), + NotBefore: time.Now().Add(-time.Hour), + } + + expectedErr := "x509: serial number exceeds 20 octets" + + _, err = CreateCertificate(rand.Reader, tmpl, tmpl, k.Public(), k) + if err == nil || err.Error() != expectedErr { + t.Errorf("CreateCertificate returned unexpected error: want %q, got %q", expectedErr, err) + } + + serialBytes = serialBytes[:20] + tmpl.SerialNumber = big.NewInt(0).SetBytes(serialBytes) + + _, err = CreateCertificate(rand.Reader, tmpl, tmpl, k.Public(), k) + if err == nil || err.Error() != expectedErr { + t.Errorf("CreateCertificate returned unexpected error: want %q, got %q", expectedErr, err) + } +} From 78bea702cd38ac5004a97f110e7f659336a04d57 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Thu, 14 Apr 2022 15:04:34 -0700 Subject: [PATCH 105/137] cmd/compile: turn jump tables off with -N The noopt builder is broken, because with -N we get two OpSB opcodes (one for the function as a whole, one introduced by the jumptable rewrite rule), and they fight each other for a register. Without -N, the two OpSB get CSEd, so optimized builds are ok. Maybe we fix regalloc to deal with this case, but it's simpler (and maybe more correct?) to disable jump tables with -N. Change-Id: I75c87f12de6262955d1df787f47c53de976f8a5f Reviewed-on: https://go-review.googlesource.com/c/go/+/400455 Run-TryBot: Keith Randall Reviewed-by: Keith Randall Auto-Submit: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/cmd/compile/internal/walk/switch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/compile/internal/walk/switch.go b/src/cmd/compile/internal/walk/switch.go index 6a2dbe1753e30..5067d5eb49916 100644 --- a/src/cmd/compile/internal/walk/switch.go +++ b/src/cmd/compile/internal/walk/switch.go @@ -289,7 +289,7 @@ func (s *exprSwitch) tryJumpTable(cc []exprClause, out *ir.Nodes) bool { 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 || !ssagen.Arch.LinkArch.CanJumpTable { + if !go119UseJumpTables || base.Flag.N != 0 || !ssagen.Arch.LinkArch.CanJumpTable { return false } if len(cc) < minCases { From 5a4f0b6f1e6d3c022ee30884590526ab7d3f580b Mon Sep 17 00:00:00 2001 From: hopehook Date: Tue, 12 Apr 2022 17:46:36 +0800 Subject: [PATCH 106/137] runtime: don't discard value from panic while panicking In issue #17671, there are a endless loop if printing the panic value panics, CL 30358 has fixed that. As issue #52257 pointed out, above change should not discard the value from panic while panicking. With this CL, when we recover from a panic in error.Error() or stringer.String(), and the recovered value is string, then we can print it normally. Fixes #52257 Change-Id: Icfcc4a1a390635de405eea04904b4607ae9e3055 Reviewed-on: https://go-review.googlesource.com/c/go/+/399874 Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov Reviewed-by: Dmitri Shuralyov --- src/runtime/crash_test.go | 44 ++++++++++++++++ src/runtime/panic.go | 10 +++- src/runtime/testdata/testprog/crash.go | 73 ++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/src/runtime/crash_test.go b/src/runtime/crash_test.go index d8cabcdda2808..01d7cbeb2904b 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/panic.go b/src/runtime/panic.go index f2137c6853bb9..e4cc7bfb315d0 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 { diff --git a/src/runtime/testdata/testprog/crash.go b/src/runtime/testdata/testprog/crash.go index c4990cdda9a07..a2294ba14950a 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 From 35a92f92bd0ce15c658dd6794238ca90b71e4422 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Wed, 13 Apr 2022 13:21:30 -0700 Subject: [PATCH 107/137] encoding/binary: add AppendVarint AppendUvarint This adds a straight-forward implementation of the functionality. A more performant version could be added that unrolls the loop as is done in google.golang.org/protobuf/encoding/protowire, but usages that demand high performance can use that package instead. Fixes #51644 Change-Id: I9d3b615a60cdff47e5200e7e5d2276adf4c93783 Reviewed-on: https://go-review.googlesource.com/c/go/+/400176 Reviewed-by: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov Run-TryBot: Joseph Tsai TryBot-Result: Gopher Robot --- api/next/51644.txt | 2 ++ src/encoding/binary/varint.go | 20 ++++++++++++++++++++ src/encoding/binary/varint_test.go | 12 ++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 api/next/51644.txt diff --git a/api/next/51644.txt b/api/next/51644.txt new file mode 100644 index 0000000000000..d93dbbf18461e --- /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/src/encoding/binary/varint.go b/src/encoding/binary/varint.go index 1b07e2541ddb5..c807d15f442e7 100644 --- a/src/encoding/binary/varint.go +++ b/src/encoding/binary/varint.go @@ -36,6 +36,16 @@ const ( MaxVarintLen64 = 10 ) +// AppendUvarint appends the varint-encoded form of x, +// as generated by PutUvarint, to buf and returns the extended buffer. +func AppendUvarint(buf []byte, x uint64) []byte { + for x >= 0x80 { + buf = append(buf, byte(x)|0x80) + x >>= 7 + } + return append(buf, byte(x)) +} + // PutUvarint encodes a uint64 into buf and returns the number of bytes written. // If the buffer is too small, PutUvarint will panic. func PutUvarint(buf []byte, x uint64) int { @@ -77,6 +87,16 @@ func Uvarint(buf []byte) (uint64, int) { return 0, 0 } +// AppendVarint appends the varint-encoded form of x, +// as generated by PutVarint, to buf and returns the extended buffer. +func AppendVarint(buf []byte, x int64) []byte { + ux := uint64(x) << 1 + if x < 0 { + ux = ^ux + } + return AppendUvarint(buf, ux) +} + // PutVarint encodes an int64 into buf and returns the number of bytes written. // If the buffer is too small, PutVarint will panic. func PutVarint(buf []byte, x int64) int { diff --git a/src/encoding/binary/varint_test.go b/src/encoding/binary/varint_test.go index d025a67538ccd..080a2148f0b4f 100644 --- a/src/encoding/binary/varint_test.go +++ b/src/encoding/binary/varint_test.go @@ -36,6 +36,12 @@ func testVarint(t *testing.T, x int64) { t.Errorf("Varint(%d): got n = %d; want %d", x, m, n) } + buf2 := []byte("prefix") + buf2 = AppendVarint(buf2, x) + if string(buf2) != "prefix"+string(buf[:n]) { + t.Errorf("AppendVarint(%d): got %q, want %q", x, buf2, "prefix"+string(buf[:n])) + } + y, err := ReadVarint(bytes.NewReader(buf)) if err != nil { t.Errorf("ReadVarint(%d): %s", x, err) @@ -56,6 +62,12 @@ func testUvarint(t *testing.T, x uint64) { t.Errorf("Uvarint(%d): got n = %d; want %d", x, m, n) } + buf2 := []byte("prefix") + buf2 = AppendUvarint(buf2, x) + if string(buf2) != "prefix"+string(buf[:n]) { + t.Errorf("AppendUvarint(%d): got %q, want %q", x, buf2, "prefix"+string(buf[:n])) + } + y, err := ReadUvarint(bytes.NewReader(buf)) if err != nil { t.Errorf("ReadUvarint(%d): %s", x, err) From 2c73f5f32fceb31b5da7f9a820c0c637f57a9ab5 Mon Sep 17 00:00:00 2001 From: Bobby Powers Date: Fri, 8 Apr 2022 12:21:33 -0700 Subject: [PATCH 108/137] net/http: remove cloneURL call in WithContext Fixes #52239 Change-Id: I08b75e613e3c976855e39d01a6757d94e4207bf8 Reviewed-on: https://go-review.googlesource.com/c/go/+/399155 Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Damien Neil Run-TryBot: Damien Neil Reviewed-by: Ian Lance Taylor --- src/net/http/request.go | 1 - src/net/http/request_test.go | 14 +++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/net/http/request.go b/src/net/http/request.go index 654505d81915b..312211977d554 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 } diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go index 4363e11033325..d285840c1cec4 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") } From 5c707f5f3ace728f08997960ec67d9f55cdbf1a3 Mon Sep 17 00:00:00 2001 From: Archana R Date: Fri, 1 Apr 2022 12:05:07 -0500 Subject: [PATCH 109/137] internal/bytealg: optimize indexbyte function for ppc64le/power9 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added specific code For POWER9 that does not need prealignment prior to load vector. Optimized vector loop to jump out as soon as there is a match instead of accumulating matches for 4 indices and then processing the same. For small input size 10, the caller function dominates performance. name old time/op new time/op delta IndexByte/10 9.20ns ± 0% 10.40ns ± 0% +13.08% IndexByte/32 9.77ns ± 0% 9.20ns ± 0% -5.84% IndexByte/4K 171ns ± 0% 136ns ± 0% -20.51% IndexByte/4M 154µs ± 0% 126µs ± 0% -17.92% IndexByte/64M 2.48ms ± 0% 2.03ms ± 0% -18.27% IndexAnyASCII/1:32 10.2ns ± 1% 9.2ns ± 0% -9.19% IndexAnyASCII/1:64 11.3ns ± 0% 10.1ns ± 0% -11.29% IndexAnyUTF8/1:64 11.4ns ± 0% 9.8ns ± 0% -13.73% IndexAnyUTF8/16:64 156ns ± 1% 131ns ± 0% -16.23% IndexAnyUTF8/256:64 2.27µs ± 0% 1.86µs ± 0% -18.03% LastIndexAnyUTF8/1:64 11.8ns ± 0% 10.5ns ± 0% -10.81% LastIndexAnyUTF8/16:64 165ns ±11% 132ns ± 0% -19.75% LastIndexAnyUTF8/256:2 1.68µs ± 0% 1.44µs ± 0% -14.33% LastIndexAnyUTF8/256:4 1.68µs ± 0% 1.49µs ± 0% -11.10% LastIndexAnyUTF8/256:8 1.68µs ± 0% 1.50µs ± 0% -11.05% LastIndexAnyUTF8/256:64 2.30µs ± 0% 1.90µs ± 0% -17.56% Change-Id: I3d2550bdfdea38fece2da9960bbe62fe6cb1840c Reviewed-on: https://go-review.googlesource.com/c/go/+/397614 Reviewed-by: Paul Murphy Reviewed-by: Cherry Mui Run-TryBot: Archana Ravindar TryBot-Result: Gopher Robot Reviewed-by: Russ Cox --- src/internal/bytealg/indexbyte_ppc64x.s | 87 +++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/src/internal/bytealg/indexbyte_ppc64x.s b/src/internal/bytealg/indexbyte_ppc64x.s index 4cc2b440876e5..1a6e852d67258 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 From 082cfabf126d63e952e1ac29d47c2a47f1c64bee Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Thu, 14 Apr 2022 17:57:22 -0700 Subject: [PATCH 110/137] crypto/x509: don't create certs with negative serials Refuse to create certificates with negative serial numbers, as they are explicitly disallowed by RFC 5280. We still allow parsing certificates with negative serial numbers, because in the past there were buggy CA implementations which would produce them (although there are currently *no* trusted certificates that have this issue). We may want to revisit this decision if we can find metrics about the prevalence of this issue in enterprise settings. Change-Id: I131262008db99b6354f542f335abc68775a2d6d0 Reviewed-on: https://go-review.googlesource.com/c/go/+/400494 Reviewed-by: Damien Neil Reviewed-by: Roland Shoemaker Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker TryBot-Result: Gopher Robot --- src/crypto/x509/x509.go | 6 ++++- src/crypto/x509/x509_test.go | 43 +++++++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 6d99191fefa59..8823ff8a26298 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -1478,13 +1478,17 @@ func CreateCertificate(rand io.Reader, template, parent *Certificate, pub, priv return nil, errors.New("x509: no SerialNumber given") } - // RFC 5280 Section 4.1.2.2: serial number must not be longer than 20 octets + // RFC 5280 Section 4.1.2.2: serial number must positive and should not be longer + // than 20 octets. // // We cannot simply check for len(serialBytes) > 20, because encoding/asn1 may // pad the slice in order to prevent the integer being mistaken for a negative // number (DER uses the high bit of the left-most byte to indicate the sign.), // so we need to double check the composition of the serial if it is exactly // 20 bytes. + if template.SerialNumber.Sign() == -1 { + return nil, errors.New("x509: serial number must be positive") + } serialBytes := template.SerialNumber.Bytes() if len(serialBytes) > 20 || (len(serialBytes) == 20 && serialBytes[0]&0x80 != 0) { return nil, errors.New("x509: serial number exceeds 20 octets") diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index c294f91ed645d..4806ef3493efd 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -602,11 +602,7 @@ func TestCreateSelfSignedCertificate(t *testing.T) { for _, test := range tests { commonName := "test.example.com" template := Certificate{ - // SerialNumber is negative to ensure that negative - // values are parsed. This is due to the prevalence of - // buggy code that produces certificates with negative - // serial numbers. - SerialNumber: big.NewInt(-1), + SerialNumber: big.NewInt(1), Subject: pkix.Name{ CommonName: commonName, Organization: []string{"Σ Acme Co"}, @@ -3628,3 +3624,40 @@ func TestCreateCertificateLongSerial(t *testing.T) { t.Errorf("CreateCertificate returned unexpected error: want %q, got %q", expectedErr, err) } } + +var negativeSerialCert = `-----BEGIN CERTIFICATE----- +MIIBBTCBraADAgECAgH/MAoGCCqGSM49BAMCMA0xCzAJBgNVBAMTAjopMB4XDTIy +MDQxNDIzNTYwNFoXDTIyMDQxNTAxNTYwNFowDTELMAkGA1UEAxMCOikwWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAQ9ezsIsj+q17K87z/PXE/rfGRN72P/Wyn5d6oo +5M0ZbSatuntMvfKdX79CQxXAxN4oXk3Aov4jVSG12AcDI8ShMAoGCCqGSM49BAMC +A0cAMEQCIBzfBU5eMPT6m5lsR6cXaJILpAaiD9YxOl4v6dT3rzEjAiBHmjnHmAss +RqUAyJKFzqZxOlK2q4j2IYnuj5+LrLGbQA== +-----END CERTIFICATE-----` + +func TestParseNegativeSerial(t *testing.T) { + pemBlock, _ := pem.Decode([]byte(negativeSerialCert)) + _, err := ParseCertificate(pemBlock.Bytes) + if err != nil { + t.Fatalf("failed to parse certificate: %s", err) + } +} + +func TestCreateNegativeSerial(t *testing.T) { + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + tmpl := &Certificate{ + SerialNumber: big.NewInt(-1), + Subject: pkix.Name{ + CommonName: ":)", + }, + NotAfter: time.Now().Add(time.Hour), + NotBefore: time.Now().Add(-time.Hour), + } + expectedErr := "x509: serial number must be positive" + _, err = CreateCertificate(rand.Reader, tmpl, tmpl, k.Public(), k) + if err == nil || err.Error() != expectedErr { + t.Errorf("CreateCertificate returned unexpected error: want %q, got %q", expectedErr, err) + } +} From ac01de5446ec92544768dabee3b0d1faf5f596d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Penteado?= <4219131+joaopenteado@users.noreply.github.com> Date: Tue, 30 Nov 2021 18:59:41 +0000 Subject: [PATCH 111/137] net/http: optimize StatusText implementation The current implementation, although more succinct, relies on a runtime lookup to a "constant" unexported map (which also needs to be initialized at runtime). The proposed implementation is able to be optimized by the compiler at build-time, resulting in *much* more efficient instructions. Additionally, unused string literals may even be removed altogether from the generated binary in some cases. This change is fully backwards-compatible behavior-wise with the existing implementation. Change-Id: I36450320aacff5b322195820552f2831d4fecd52 GitHub-Last-Rev: e2058f132ef7a193529d4b0e84329ac93e5d1dcb GitHub-Pull-Request: golang/go#49811 Reviewed-on: https://go-review.googlesource.com/c/go/+/367201 Run-TryBot: Damien Neil TryBot-Result: Gopher Robot Reviewed-by: Dmitri Shuralyov Run-TryBot: Ian Lance Taylor Reviewed-by: Damien Neil --- src/net/http/response.go | 5 +- src/net/http/server.go | 4 +- src/net/http/status.go | 198 +++++++++++++++++++++++++-------------- 3 files changed, 132 insertions(+), 75 deletions(-) diff --git a/src/net/http/response.go b/src/net/http/response.go index eb4cd9b0adbf0..755c6965575fd 100644 --- a/src/net/http/response.go +++ b/src/net/http/response.go @@ -246,9 +246,8 @@ 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/server.go b/src/net/http/server.go index 62bdf1695974e..d44b0fb256577 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -1516,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) @@ -2192,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) } } diff --git a/src/net/http/status.go b/src/net/http/status.go index 286315f6395a8..75fea0ca35ac8 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 "" + } } From df2421de60215cfc314fe6772ff6c2c6201f7abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 16 Feb 2022 21:00:04 +0000 Subject: [PATCH 112/137] mime: ignore non-extension globs2 entries Change-Id: Ic2315b593dca5648c02f793b7650b5936a997bff GitHub-Last-Rev: ee55edcf087416c6f0d50d5dd51cbddfd1d77620 GitHub-Pull-Request: golang/go#51226 Reviewed-on: https://go-review.googlesource.com/c/go/+/386334 Reviewed-by: Damien Neil Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Dmitri Shuralyov --- src/mime/testdata/test.types.globs2 | 2 ++ src/mime/type_unix.go | 6 +++--- src/mime/type_unix_test.go | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mime/testdata/test.types.globs2 b/src/mime/testdata/test.types.globs2 index cb5b7899b0d5f..fd9df7078bd87 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_unix.go b/src/mime/type_unix.go index 52579c56b9acb..e297ecf5c1aa7 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 6bb408566cd1a..ab14ae6f80cc4 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 { From 0c6d8bb109adfa188190b2e6de59b88853ded378 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 14 Apr 2022 16:25:43 -0700 Subject: [PATCH 113/137] debug/pe: read string table in 10M chunks No separate test because this makes no difference for valid PE files. Fixes #52350 Change-Id: I2aa011a4e8b34cb08052222e94c52627ebe99fbf Reviewed-on: https://go-review.googlesource.com/c/go/+/400378 Run-TryBot: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov TryBot-Result: Gopher Robot Reviewed-by: Than McIntosh Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor --- src/debug/pe/string.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/debug/pe/string.go b/src/debug/pe/string.go index cab0366ade26f..6d9023d8d6f16 100644 --- a/src/debug/pe/string.go +++ b/src/debug/pe/string.go @@ -44,8 +44,29 @@ func readStringTable(fh *FileHeader, r io.ReadSeeker) (StringTable, error) { return nil, nil } l -= 4 - buf := make([]byte, l) - _, err = io.ReadFull(r, buf) + + // If the string table is large, the file may be corrupt. + // Read in chunks to avoid crashing due to out of memory. + const chunk = 10 << 20 // 10M + var buf []byte + if l < chunk { + buf = make([]byte, l) + _, err = io.ReadFull(r, buf) + } else { + for l > 0 { + n := l + if n > chunk { + n = chunk + } + buf1 := make([]byte, n) + _, err = io.ReadFull(r, buf1) + if err != nil { + break + } + buf = append(buf, buf1...) + l -= n + } + } if err != nil { return nil, fmt.Errorf("fail to read string table: %v", err) } From df08c9a82152fd6f2b2811db03b40fea8b6e5e9e Mon Sep 17 00:00:00 2001 From: Cherry Mui Date: Fri, 15 Apr 2022 11:52:01 -0400 Subject: [PATCH 114/137] cmd/link: preserve symbol attributes when cloning to external There are some symbol attributes that are encoded in the object file. Currently, they are lost when cloning a symbol to external. Copy them over. Also delete CopyAttributes as it is no longer called anywhere. Change-Id: I1497e3223a641704bf35aa3e904dd0eda2f8ec3e Reviewed-on: https://go-review.googlesource.com/c/go/+/400574 Run-TryBot: Cherry Mui TryBot-Result: Gopher Robot Reviewed-by: Than McIntosh --- src/cmd/link/internal/loader/loader.go | 29 ++++---------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go index 6fa443a13fc53..a069540035a54 100644 --- a/src/cmd/link/internal/loader/loader.go +++ b/src/cmd/link/internal/loader/loader.go @@ -2341,6 +2341,10 @@ func (l *Loader) cloneToExternal(symIdx Sym) { // need to access the old symbol content.) l.objSyms[symIdx] = objSym{l.extReader.objidx, uint32(pi)} l.extReader.syms = append(l.extReader.syms, symIdx) + + // Some attributes were encoded in the object file. Copy them over. + l.SetAttrDuplicateOK(symIdx, r.Sym(li).Dupok()) + l.SetAttrShared(symIdx, r.Shared()) } // Copy the payload of symbol src to dst. Both src and dst must be external @@ -2361,31 +2365,6 @@ func (l *Loader) CopySym(src, dst Sym) { // TODO: other attributes? } -// CopyAttributes copies over all of the attributes of symbol 'src' to -// symbol 'dst'. -func (l *Loader) CopyAttributes(src Sym, dst Sym) { - l.SetAttrReachable(dst, l.AttrReachable(src)) - l.SetAttrOnList(dst, l.AttrOnList(src)) - l.SetAttrLocal(dst, l.AttrLocal(src)) - l.SetAttrNotInSymbolTable(dst, l.AttrNotInSymbolTable(src)) - if l.IsExternal(dst) { - l.SetAttrVisibilityHidden(dst, l.AttrVisibilityHidden(src)) - l.SetAttrDuplicateOK(dst, l.AttrDuplicateOK(src)) - l.SetAttrShared(dst, l.AttrShared(src)) - l.SetAttrExternal(dst, l.AttrExternal(src)) - } else { - // Some attributes are modifiable only for external symbols. - // In such cases, don't try to transfer over the attribute - // from the source even if there is a clash. This comes up - // when copying attributes from a dupOK ABI wrapper symbol to - // the real target symbol (which may not be marked dupOK). - } - l.SetAttrSpecial(dst, l.AttrSpecial(src)) - l.SetAttrCgoExportDynamic(dst, l.AttrCgoExportDynamic(src)) - l.SetAttrCgoExportStatic(dst, l.AttrCgoExportStatic(src)) - l.SetAttrReadOnly(dst, l.AttrReadOnly(src)) -} - // CreateExtSym creates a new external symbol with the specified name // without adding it to any lookup tables, returning a Sym index for it. func (l *Loader) CreateExtSym(name string, ver int) Sym { From e3982660a73b04a87c08215cb5aaa16d816ea573 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Fri, 15 Apr 2022 13:46:00 -0700 Subject: [PATCH 115/137] runtime: don't block preemption signal in new M's or ensureSigM No test because we already have a test in the syscall package. The issue reports 1 failure per 100,000 iterations, which is rare enough that our builders won't catch the problem. Fixes #52226 Change-Id: I17633ff6cf676b6d575356186dce42cdacad0746 Reviewed-on: https://go-review.googlesource.com/c/go/+/400315 Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Michael Knyszek Reviewed-by: Ian Lance Taylor --- src/runtime/signal_unix.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/runtime/signal_unix.go b/src/runtime/signal_unix.go index 8bde739c64978..3db789396d629 100644 --- a/src/runtime/signal_unix.go +++ b/src/runtime/signal_unix.go @@ -1247,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 @@ -1257,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 } From e704ef2b8529119a11694b4cb15215d3dd6b0a9f Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 15 Apr 2022 22:02:21 +0000 Subject: [PATCH 116/137] go/doc: fix incorrect identifier parsing in comments This code was trying to iterate codepoints, but didn't reslice the string, so it was reading the first codepoint over and over, if the string length was not a multiple of the first codepoint length, this would cause to overshoot past the end of the string. This was a latent bug introduced in CL 384265 but was revealed to Ngolo-fuzzing in OSS-Fuzz in CL 397277. Fixes #52353 Change-Id: I13f0352e6ad13a42878927f3b1c18c58360dd40c GitHub-Last-Rev: 424f6cfad1bc7d66314911e6b4b4ce6751330435 GitHub-Pull-Request: golang/go#52356 Reviewed-on: https://go-review.googlesource.com/c/go/+/400240 Reviewed-by: Dmitri Shuralyov Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor --- src/go/doc/comment/parse.go | 2 +- src/go/doc/comment/parse_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/go/doc/comment/parse_test.go diff --git a/src/go/doc/comment/parse.go b/src/go/doc/comment/parse.go index a8cba90ec0c06..83b37c32c53b8 100644 --- a/src/go/doc/comment/parse.go +++ b/src/go/doc/comment/parse.go @@ -1063,7 +1063,7 @@ func ident(s string) (id string, ok bool) { } break } - r, nr := utf8.DecodeRuneInString(s) + r, nr := utf8.DecodeRuneInString(s[n:]) if unicode.IsLetter(r) { n += nr continue diff --git a/src/go/doc/comment/parse_test.go b/src/go/doc/comment/parse_test.go new file mode 100644 index 0000000000000..bce733eaae54f --- /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("𫕐ﯯ") +} From 5e4543c29ff930084f79cd982d6eee0f13f52565 Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Fri, 15 Apr 2022 11:36:32 +0000 Subject: [PATCH 117/137] bytes: explode checks for n too large As is already done in strings package. Change-Id: Ia45e6443ddf6beac5e70a1cc493119030e173139 GitHub-Last-Rev: 1174c250350f31eced1513169d62a8a3e679dcf6 GitHub-Pull-Request: golang/go#52348 Reviewed-on: https://go-review.googlesource.com/c/go/+/400239 Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Auto-Submit: Ian Lance Taylor Reviewed-by: Dmitri Shuralyov --- src/bytes/bytes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bytes/bytes.go b/src/bytes/bytes.go index 2a00ce33546b1..659a82bcc8c95 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) From e948c3394ee137bae45e85cfa7c8ec0bb0e16dc8 Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Fri, 15 Apr 2022 18:09:48 -0700 Subject: [PATCH 118/137] reflect: make Value.Type inlineable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the result of Type to be computed much faster. Performance: old new delta 1.76ns 0.66ns -62.27% Change-Id: Ie007fd175aaa41b2f67c71fa2a34ab8d292dd0e0 Reviewed-on: https://go-review.googlesource.com/c/go/+/400335 Reviewed-by: Brad Fitzpatrick Run-TryBot: Brad Fitzpatrick TryBot-Result: Gopher Robot Reviewed-by: Dmitri Shuralyov Reviewed-by: Daniel Martí Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor --- src/reflect/value.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 2496cbe463667..06f0469edefc9 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -2465,12 +2465,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 } From 740a490f71d026bb7d2d13cb8fa2d6d6e0572b70 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Fri, 15 Apr 2022 17:42:17 +0000 Subject: [PATCH 119/137] net/http: correctly show error types in transfer test actualReader and tc.expectedReader are reflect.Type instances, not the actual objects. Change-Id: I7c9cfa489e3297b94c603b62bad1ed84bd207057 GitHub-Last-Rev: d581402375aea0c911fef663ec7e89a24c4e5524 GitHub-Pull-Request: golang/go#52339 Reviewed-on: https://go-review.googlesource.com/c/go/+/400235 Reviewed-by: Damien Neil Run-TryBot: Damien Neil TryBot-Result: Gopher Robot Reviewed-by: Dmitri Shuralyov Auto-Submit: Ian Lance Taylor --- src/net/http/transfer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/net/http/transfer_test.go b/src/net/http/transfer_test.go index f0c28b26299a8..5e0df896d80c8 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) } } From 91b9915d3f6f8cd2e9e9fda63f67772803adfa03 Mon Sep 17 00:00:00 2001 From: Lynn Boger Date: Tue, 12 Apr 2022 09:37:31 -0500 Subject: [PATCH 120/137] runtime: improve memclr on ppc64x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This improves performance for memclr for sizes >= 64 and < 512 by unrolling the loop to clear 64 bytes at a time, whereas before it was doing 32 bytes. On a power9, the improvement is: Memclr/64 6.07ns ± 0% 5.17ns ± 0% -14.86% (p=1.000 n=1+1) Memclr/256 11.8ns ± 0% 8.3ns ± 0% -30.10% (p=1.000 n=1+1) GoMemclr/64 5.58ns ± 0% 5.02ns ± 0% -10.04% (p=1.000 n=1+1) GoMemclr/256 12.0ns ± 0% 8.8ns ± 0% -26.62% (p=1.000 n=1+1) Change-Id: I929389ae9e50128cba81e0c412e7ba431da7facc Reviewed-on: https://go-review.googlesource.com/c/go/+/399895 Reviewed-by: Cherry Mui Run-TryBot: Lynn Boger TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/runtime/memclr_ppc64x.s | 83 ++++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/src/runtime/memclr_ppc64x.s b/src/runtime/memclr_ppc64x.s index ad84ea9600e1c..354325585d663 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 + SRDCC $3, R6, R7 // 64 byte chunks? + XXLXOR VS32, VS32, VS32 // clear VS32 (V0) + BEQ lt64gt8 - // Prepare to clear 32 bytes at a time. + // Prepare to clear 64 bytes at a time. -zero32setup: +zero64setup: DCBTST (R3) // prepare data cache - XXLXOR VS32, VS32, VS32 // clear VS32 (V0) - MOVD R7, CTR // number of 32 byte chunks + MOVD R7, CTR // number of 64 byte chunks MOVD $16, R8 + MOVD $32, R16 + MOVD $48, R17 -zero32: +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 From 3df9df8d6a1a140239e4cba0d0595bdab2ba9c60 Mon Sep 17 00:00:00 2001 From: hopehook Date: Fri, 8 Apr 2022 17:59:05 +0800 Subject: [PATCH 121/137] cmd/compile: fix missing source information in ssa view Endlineno is lost when we call "genericSubst" to create the new instantiation of the generic function. This will cause "readFuncLines" to fail to read the target function. To fix this issue, as @mdempsky pointed out, add the line in cmd/compile/internal/noder/stencil.go: newf.Endlineno = gf.Endlineno Fixes #51988 Change-Id: Ib408e4ed0ceb68df8dedda4fb551309e8385aada Reviewed-on: https://go-review.googlesource.com/c/go/+/399057 Reviewed-by: Matthew Dempsky Run-TryBot: Matthew Dempsky Auto-Submit: Matthew Dempsky TryBot-Result: Gopher Robot Reviewed-by: Austin Clements Reviewed-by: Than McIntosh --- src/cmd/compile/internal/noder/stencil.go | 1 + src/runtime/callers_test.go | 30 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index eeac8d8de7d7d..41435a7afe886 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/runtime/callers_test.go b/src/runtime/callers_test.go index 3cf3fbe5acf6f..d245cbd2d2f1c 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 +} From f49e802892a225c7fd14a3a8bb8c0e83875d888d Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Mon, 18 Apr 2022 12:55:30 -0400 Subject: [PATCH 122/137] net/http: eliminate arbitrary timeouts in TestServerRequestContextCancel_ConnClose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These timeouts are empirically sometimes (but rarely) too short on slower builders, and at any rate if this test fails “for real” we'll want a goroutine dump in order to debug it anyway. A goroutine dump is exactly what we get if we let the test time out on its own. Fixes #52414. Change-Id: Id2dd3839977bd8a41f296d67d1cccbf068fd73f4 Reviewed-on: https://go-review.googlesource.com/c/go/+/400816 Run-TryBot: Bryan Mills Auto-Submit: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/net/http/serve_test.go | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index 435f828871a3a..1c85a66599784 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) { From c5edd5f616b4ee4bbaefdb1579c6078e7ed7e84e Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Sat, 16 Apr 2022 20:23:28 -0700 Subject: [PATCH 123/137] reflect: make Value.MapRange inlineable This allows the caller to decide whether MapIter should be stack allocated or heap allocated based on whether it escapes. In most cases, it does not escape and thus removes the utility of MapIter.Reset (#46293). In fact, use of sync.Pool with MapIter and calling MapIter.Reset is likely to be slower. Change-Id: Ic93e7d39e5dd4c83e7fca9e0bdfbbcd70777f0e1 Reviewed-on: https://go-review.googlesource.com/c/go/+/400675 Reviewed-by: Keith Randall Reviewed-by: Keith Randall Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor TryBot-Result: Gopher Robot --- src/cmd/compile/internal/test/inl_test.go | 1 + src/reflect/all_test.go | 8 +++++--- src/reflect/value.go | 12 +++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/cmd/compile/internal/test/inl_test.go b/src/cmd/compile/internal/test/inl_test.go index b10d37a17cfd0..211068e1dc351 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/reflect/all_test.go b/src/reflect/all_test.go index 5a35d98b51f01..f7adf2fa1a6db 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -370,9 +370,11 @@ 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. + if got != 0 { + t.Errorf("wanted 0 alloc, got %d", got) } } diff --git a/src/reflect/value.go b/src/reflect/value.go index 06f0469edefc9..6fe3cee01717f 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1833,10 +1833,20 @@ func (iter *MapIter) Reset(v 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 { From a11a885cb567b3797e33733e883c2ba3bdc0e898 Mon Sep 17 00:00:00 2001 From: Roland Shoemaker Date: Fri, 4 Feb 2022 09:24:23 -0800 Subject: [PATCH 124/137] crypto/x509: reject duplicate extensions When parsing certificates and CSRs, reject duplicate extensions (and additionally duplicate requested extensions in CSRs.) Fixes #50988 Change-Id: I531e932cfcdde78f64c106e747a68270bd4f1d80 Reviewed-on: https://go-review.googlesource.com/c/go/+/383215 Reviewed-by: Damien Neil Run-TryBot: Roland Shoemaker Auto-Submit: Roland Shoemaker Reviewed-by: Roland Shoemaker TryBot-Result: Gopher Robot --- src/crypto/x509/parser.go | 6 +++++ src/crypto/x509/x509.go | 14 +++++++++++ src/crypto/x509/x509_test.go | 46 ++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/src/crypto/x509/parser.go b/src/crypto/x509/parser.go index 9dd7c2564e12b..e0e8f6125fdca 100644 --- a/src/crypto/x509/parser.go +++ b/src/crypto/x509/parser.go @@ -930,6 +930,7 @@ func parseCertificate(der []byte) (*Certificate, error) { return nil, errors.New("x509: malformed extensions") } if present { + seenExts := make(map[string]bool) if !extensions.ReadASN1(&extensions, cryptobyte_asn1.SEQUENCE) { return nil, errors.New("x509: malformed extensions") } @@ -942,6 +943,11 @@ func parseCertificate(der []byte) (*Certificate, error) { if err != nil { return nil, err } + oidStr := ext.Id.String() + if seenExts[oidStr] { + return nil, errors.New("x509: certificate contains duplicate extensions") + } + seenExts[oidStr] = true cert.Extensions = append(cert.Extensions, ext) } err = processExtensions(cert) diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 8823ff8a26298..085408a0f80b5 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -1825,12 +1825,18 @@ func parseCSRExtensions(rawAttributes []asn1.RawValue) ([]pkix.Extension, error) } var ret []pkix.Extension + seenExts := make(map[string]bool) for _, rawAttr := range rawAttributes { var attr pkcs10Attribute if rest, err := asn1.Unmarshal(rawAttr.FullBytes, &attr); err != nil || len(rest) != 0 || len(attr.Values) == 0 { // Ignore attributes that don't parse. continue } + oidStr := attr.Id.String() + if seenExts[oidStr] { + return nil, errors.New("x509: certificate request contains duplicate extensions") + } + seenExts[oidStr] = true if !attr.Id.Equal(oidExtensionRequest) { continue @@ -1840,6 +1846,14 @@ func parseCSRExtensions(rawAttributes []asn1.RawValue) ([]pkix.Extension, error) if _, err := asn1.Unmarshal(attr.Values[0].FullBytes, &extensions); err != nil { return nil, err } + requestedExts := make(map[string]bool) + for _, ext := range extensions { + oidStr := ext.Id.String() + if requestedExts[oidStr] { + return nil, errors.New("x509: certificate request contains duplicate requested extensions") + } + requestedExts[oidStr] = true + } ret = append(ret, extensions...) } diff --git a/src/crypto/x509/x509_test.go b/src/crypto/x509/x509_test.go index 4806ef3493efd..486d6bf3d238b 100644 --- a/src/crypto/x509/x509_test.go +++ b/src/crypto/x509/x509_test.go @@ -3661,3 +3661,49 @@ func TestCreateNegativeSerial(t *testing.T) { t.Errorf("CreateCertificate returned unexpected error: want %q, got %q", expectedErr, err) } } + +const dupExtCert = `-----BEGIN CERTIFICATE----- +MIIBrjCCARegAwIBAgIBATANBgkqhkiG9w0BAQsFADAPMQ0wCwYDVQQDEwR0ZXN0 +MCIYDzAwMDEwMTAxMDAwMDAwWhgPMDAwMTAxMDEwMDAwMDBaMA8xDTALBgNVBAMT +BHRlc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMiFchnHms9l9NninAIz +SkY9acwl9Bk2AtmJrNCenFpiA17AcOO5q8DJYwdXi6WPKlVgcyH+ysW8XMWkq+CP +yhtF/+LMzl9odaUF2iUy3vgTC5gxGLWH5URVssx21Und2Pm2f4xyou5IVxbS9dxy +jLvV9PEY9BIb0H+zFthjhihDAgMBAAGjFjAUMAgGAioDBAIFADAIBgIqAwQCBQAw +DQYJKoZIhvcNAQELBQADgYEAlhQ4TQQKIQ8GUyzGiN/75TCtQtjhMGemxc0cNgre +d9rmm4DjydH0t7/sMCB56lQrfhJNplguzsbjFW4l245KbNKHfLiqwEGUgZjBNKur +ot6qX/skahLtt0CNOaFIge75HVKe/69OrWQGdp18dkay/KS4Glu8YMKIjOhfrUi1 +NZA= +-----END CERTIFICATE-----` + +func TestDuplicateExtensionsCert(t *testing.T) { + b, _ := pem.Decode([]byte(dupExtCert)) + if b == nil { + t.Fatalf("couldn't decode test certificate") + } + _, err := ParseCertificate(b.Bytes) + if err == nil { + t.Fatal("ParseCertificate should fail when parsing certificate with duplicate extensions") + } +} + +const dupExtCSR = `-----BEGIN CERTIFICATE REQUEST----- +MIIBczCB3QIBADAPMQ0wCwYDVQQDEwR0ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQC5PbxMGVJ8aLF9lq/EvGObXTRMB7ieiZL9N+DJZg1n/ECCnZLIvYrr +ZmmDV7YZsClgxKGfjJB0RQFFyZElFM9EfHEs8NJdidDKCRdIhDXQWRyhXKevHvdm +CQNKzUeoxvdHpU/uscSkw6BgUzPyLyTx9A6ye2ix94z8Y9hGOBO2DQIDAQABoCUw +IwYJKoZIhvcNAQkOMRYwFDAIBgIqAwQCBQAwCAYCKgMEAgUAMA0GCSqGSIb3DQEB +CwUAA4GBAHROEsE7URk1knXmBnQtIHwoq663vlMcX3Hes58pUy020rWP8QkocA+X +VF18/phg3p5ILlS4fcbbP2bEeV0pePo2k00FDPsJEKCBAX2LKxbU7Vp2OuV2HM2+ +VLOVx0i+/Q7fikp3hbN1JwuMTU0v2KL/IKoUcZc02+5xiYrnOIt5 +-----END CERTIFICATE REQUEST-----` + +func TestDuplicateExtensionsCSR(t *testing.T) { + b, _ := pem.Decode([]byte(dupExtCSR)) + if b == nil { + t.Fatalf("couldn't decode test certificate") + } + _, err := ParseCertificateRequest(b.Bytes) + if err == nil { + t.Fatal("ParseCertificate should fail when parsing certificate with duplicate extensions") + } +} From e8d9fd707f4914bdd298f5dcdd14d3b6035e278b Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 30 Mar 2022 09:53:58 -0400 Subject: [PATCH 125/137] crypto/x509: move sha1 removal to unspecified future release Updates #41682 Change-Id: I3a2d6eedf4030cdc7308001aef549eb20eeb11c1 Reviewed-on: https://go-review.googlesource.com/c/go/+/396774 Reviewed-by: Russ Cox Reviewed-by: Filippo Valsorda Run-TryBot: Filippo Valsorda Auto-Submit: Filippo Valsorda TryBot-Result: Gopher Robot --- src/crypto/x509/x509.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/x509/x509.go b/src/crypto/x509/x509.go index 085408a0f80b5..ceb04ae20e92f 100644 --- a/src/crypto/x509/x509.go +++ b/src/crypto/x509/x509.go @@ -733,7 +733,7 @@ var debugAllowSHA1 = godebug.Get("x509sha1") == "1" // // To temporarily restore support for SHA-1 signatures, include the value // "x509sha1=1" in the GODEBUG environment variable. Note that this option will -// be removed in Go 1.19. +// be removed in a future release. type InsecureAlgorithmError SignatureAlgorithm func (e InsecureAlgorithmError) Error() string { From caa46312eeca1275ce22ecf8985ca31ef8de7883 Mon Sep 17 00:00:00 2001 From: "Paul E. Murphy" Date: Mon, 3 May 2021 11:00:54 -0500 Subject: [PATCH 126/137] cmd/link: use TOC-relative trampolines on PPC64 when needed When linking a PIE binary with the internal linker, TOC relative relocations need to be generated. Update trampolines to indirect call using R12 to more closely match the AIX/ELFv2 regardless of buildmode, and work with position-indepdent code. Likewise, update the check for offseting R_CALLPOWER relocs to make a local call. It should be checking ldr.AttrExternal, not ldr.IsExternal. This offset should not be adjusted for external (non-go) object files, it is handled when ELF reloc are translated into go relocs. And, update trampoline tests to verify these are generated correctly and produce a working binary using -buildmode=pie on ppc64le. Fixes #52337 Change-Id: I8a2dea06c3237bdf0e87888b56a17b6c4c99a7de Reviewed-on: https://go-review.googlesource.com/c/go/+/400234 Reviewed-by: Than McIntosh Reviewed-by: Cherry Mui Run-TryBot: Paul Murphy TryBot-Result: Gopher Robot --- src/cmd/link/internal/ppc64/asm.go | 51 +++++++--------- src/cmd/link/link_test.go | 98 +++++++++++++++++------------- 2 files changed, 78 insertions(+), 71 deletions(-) diff --git a/src/cmd/link/internal/ppc64/asm.go b/src/cmd/link/internal/ppc64/asm.go index d2b140b45dee9..73c2718a3369f 100644 --- a/src/cmd/link/internal/ppc64/asm.go +++ b/src/cmd/link/internal/ppc64/asm.go @@ -766,7 +766,7 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { // For external linking, the linker can insert a call stub to handle a long call, but depends on having the TOC address in // r2. For those build modes with external linking where the TOC address is not maintained in r2, trampolines must be created. if ctxt.IsExternal() && r2Valid(ctxt) { - // No trampolines needed since r2 contains the TOC + // The TOC pointer is valid. The external linker will insert trampolines. return } @@ -819,14 +819,9 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { } } if ldr.SymType(tramp) == 0 { - if r2Valid(ctxt) { - // Should have returned for above cases - ctxt.Errorf(s, "unexpected trampoline for shared or dynamic linking") - } else { - trampb := ldr.MakeSymbolUpdater(tramp) - ctxt.AddTramp(trampb) - gentramp(ctxt, ldr, trampb, rs, r.Add()) - } + trampb := ldr.MakeSymbolUpdater(tramp) + ctxt.AddTramp(trampb) + gentramp(ctxt, ldr, trampb, rs, r.Add()) } sb := ldr.MakeSymbolUpdater(s) relocs := sb.Relocs() @@ -842,7 +837,6 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) { func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, target loader.Sym, offset int64) { tramp.SetSize(16) // 4 instructions P := make([]byte, tramp.Size()) - t := ldr.SymValue(target) + offset var o1, o2 uint32 if ctxt.IsAIX() { @@ -851,8 +845,8 @@ func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, ta // However, all text symbols are accessed with a TOC symbol as // text relocations aren't supposed to be possible. // So, keep using the external linking way to be more AIX friendly. - o1 = uint32(0x3fe20000) // lis r2, toctargetaddr hi - o2 = uint32(0xebff0000) // ld r31, toctargetaddr lo + o1 = uint32(0x3c000000) | 12<<21 | 2<<16 // addis r12, r2, toctargetaddr hi + o2 = uint32(0xe8000000) | 12<<21 | 12<<16 // ld r12, r12, toctargetaddr lo toctramp := ldr.CreateSymForUpdate("TOC."+ldr.SymName(tramp.Sym()), 0) toctramp.SetType(sym.SXCOFFTOC) @@ -866,31 +860,32 @@ func gentramp(ctxt *ld.Link, ldr *loader.Loader, tramp *loader.SymbolBuilder, ta // Used for default build mode for an executable // Address of the call target is generated using // relocation and doesn't depend on r2 (TOC). - o1 = uint32(0x3fe00000) // lis r31,targetaddr hi - o2 = uint32(0x3bff0000) // addi r31,targetaddr lo + o1 = uint32(0x3c000000) | 12<<21 // lis r12,targetaddr hi + o2 = uint32(0x38000000) | 12<<21 | 12<<16 // addi r12,r12,targetaddr lo - // With external linking, the target address must be - // relocated using LO and HA - if ctxt.IsExternal() || ldr.SymValue(target) == 0 { + t := ldr.SymValue(target) + if t == 0 || r2Valid(ctxt) || ctxt.IsExternal() { + // Target address is unknown, generate relocations r, _ := tramp.AddRel(objabi.R_ADDRPOWER) + if r2Valid(ctxt) { + // Use a TOC relative address if R2 holds the TOC pointer + o1 |= uint32(2 << 16) // Transform lis r31,ha into addis r31,r2,ha + r.SetType(objabi.R_ADDRPOWER_TOCREL) + } r.SetOff(0) r.SetSiz(8) // generates 2 relocations: HA + LO r.SetSym(target) r.SetAdd(offset) } else { - // adjustment needed if lo has sign bit set - // when using addi to compute address - val := uint32((t & 0xffff0000) >> 16) - if t&0x8000 != 0 { - val += 1 - } - o1 |= val // hi part of addr - o2 |= uint32(t & 0xffff) // lo part of addr + // The target address is known, resolve it + t += offset + o1 |= (uint32(t) + 0x8000) >> 16 // HA + o2 |= uint32(t) & 0xFFFF // LO } } - o3 := uint32(0x7fe903a6) // mtctr r31 - o4 := uint32(0x4e800420) // bctr + o3 := uint32(0x7c0903a6) | 12<<21 // mtctr r12 + o4 := uint32(0x4e800420) // bctr ctxt.Arch.ByteOrder.PutUint32(P, o1) ctxt.Arch.ByteOrder.PutUint32(P[4:], o2) ctxt.Arch.ByteOrder.PutUint32(P[8:], o3) @@ -962,7 +957,7 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade // If we are linking PIE or shared code, all golang generated object files have an extra 2 instruction prologue // to regenerate the TOC pointer from R12. The exception are two special case functions tested below. Note, // local call offsets for externally generated objects are accounted for when converting into golang relocs. - if !ldr.IsExternal(rs) && ldr.AttrShared(rs) && tgtName != "runtime.duffzero" && tgtName != "runtime.duffcopy" { + if !ldr.AttrExternal(rs) && ldr.AttrShared(rs) && tgtName != "runtime.duffzero" && tgtName != "runtime.duffcopy" { // Furthermore, only apply the offset if the target looks like the start of a function call. if r.Add() == 0 && ldr.SymType(rs) == sym.STEXT { t += 8 diff --git a/src/cmd/link/link_test.go b/src/cmd/link/link_test.go index 8df31d7fd490e..ac68008d8ddc6 100644 --- a/src/cmd/link/link_test.go +++ b/src/cmd/link/link_test.go @@ -642,8 +642,12 @@ func TestTrampoline(t *testing.T) { // For stress test, we set -debugtramp=2 flag, which sets a very low // threshold for trampoline generation, and essentially all cross-package // calls will use trampolines. + buildmodes := []string{"default"} switch runtime.GOARCH { - case "arm", "arm64", "ppc64", "ppc64le": + case "arm", "arm64", "ppc64": + case "ppc64le": + // Trampolines are generated differently when internal linking PIE, test them too. + buildmodes = append(buildmodes, "pie") default: t.Skipf("trampoline insertion is not implemented on %s", runtime.GOARCH) } @@ -661,18 +665,20 @@ func TestTrampoline(t *testing.T) { } exe := filepath.Join(tmpdir, "hello.exe") - cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2", "-o", exe, src) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("build failed: %v\n%s", err, out) - } - cmd = exec.Command(exe) - out, err = cmd.CombinedOutput() - if err != nil { - t.Errorf("executable failed to run: %v\n%s", err, out) - } - if string(out) != "hello\n" { - t.Errorf("unexpected output:\n%s", out) + for _, mode := range buildmodes { + cmd := exec.Command(testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) + } + cmd = exec.Command(exe) + out, err = cmd.CombinedOutput() + if err != nil { + t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out) + } + if string(out) != "hello\n" { + t.Errorf("unexpected output (%s):\n%s", mode, out) + } } } @@ -693,8 +699,12 @@ func TestTrampolineCgo(t *testing.T) { // For stress test, we set -debugtramp=2 flag, which sets a very low // threshold for trampoline generation, and essentially all cross-package // calls will use trampolines. + buildmodes := []string{"default"} switch runtime.GOARCH { - case "arm", "arm64", "ppc64", "ppc64le": + case "arm", "arm64", "ppc64": + case "ppc64le": + // Trampolines are generated differently when internal linking PIE, test them too. + buildmodes = append(buildmodes, "pie") default: t.Skipf("trampoline insertion is not implemented on %s", runtime.GOARCH) } @@ -713,37 +723,39 @@ func TestTrampolineCgo(t *testing.T) { } exe := filepath.Join(tmpdir, "hello.exe") - cmd := exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2", "-o", exe, src) - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("build failed: %v\n%s", err, out) - } - cmd = exec.Command(exe) - out, err = cmd.CombinedOutput() - if err != nil { - t.Errorf("executable failed to run: %v\n%s", err, out) - } - if string(out) != "hello\n" && string(out) != "hello\r\n" { - t.Errorf("unexpected output:\n%s", out) - } + for _, mode := range buildmodes { + cmd := exec.Command(testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2", "-o", exe, src) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) + } + cmd = exec.Command(exe) + out, err = cmd.CombinedOutput() + if err != nil { + t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out) + } + if string(out) != "hello\n" && string(out) != "hello\r\n" { + t.Errorf("unexpected output (%s):\n%s", mode, out) + } - // Test internal linking mode. + // Test internal linking mode. - if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || (runtime.GOARCH == "arm64" && runtime.GOOS == "windows") || !testenv.CanInternalLink() { - return // internal linking cgo is not supported - } - cmd = exec.Command(testenv.GoToolPath(t), "build", "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src) - out, err = cmd.CombinedOutput() - if err != nil { - t.Fatalf("build failed: %v\n%s", err, out) - } - cmd = exec.Command(exe) - out, err = cmd.CombinedOutput() - if err != nil { - t.Errorf("executable failed to run: %v\n%s", err, out) - } - if string(out) != "hello\n" && string(out) != "hello\r\n" { - t.Errorf("unexpected output:\n%s", out) + if runtime.GOARCH == "ppc64" || (runtime.GOARCH == "arm64" && runtime.GOOS == "windows") || !testenv.CanInternalLink() { + return // internal linking cgo is not supported + } + cmd = exec.Command(testenv.GoToolPath(t), "build", "-buildmode="+mode, "-ldflags=-debugtramp=2 -linkmode=internal", "-o", exe, src) + out, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("build (%s) failed: %v\n%s", mode, err, out) + } + cmd = exec.Command(exe) + out, err = cmd.CombinedOutput() + if err != nil { + t.Errorf("executable failed to run (%s): %v\n%s", mode, err, out) + } + if string(out) != "hello\n" && string(out) != "hello\r\n" { + t.Errorf("unexpected output (%s):\n%s", mode, out) + } } } From 8af7fac5a1d2b3881f8f435d77adfcef6a07bedc Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 18 Apr 2022 13:39:52 -0400 Subject: [PATCH 127/137] internal/sys: add LR and fixed frame size to sys.Arch Storing this information in the Arch eliminates some code duplication between the compiler and linker. This information is entirely determined by the Arch, so the current approach of attaching it to an entire Ctxt is a little silly. This will also make it easier to use this information from tests. The next CL will be a rote refactoring to eliminate the Ctxt.FixedFrameSize methods. Change-Id: I315c524fa66a0ea99f63ae5a2a6fdc367d843bad Reviewed-on: https://go-review.googlesource.com/c/go/+/400818 Run-TryBot: Austin Clements Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot --- src/cmd/internal/obj/link.go | 11 +- src/cmd/internal/sys/arch.go | 252 +++++++++++++++++------------- src/cmd/link/internal/ld/dwarf.go | 2 +- src/cmd/link/internal/ld/lib.go | 8 +- src/cmd/link/internal/ld/link.go | 12 +- 5 files changed, 152 insertions(+), 133 deletions(-) diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index dc06a3aa11b78..12a4c94e2496a 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -989,16 +989,7 @@ func (fi *FuncInfo) UnspillRegisterArgs(last *Prog, pa ProgAlloc) *Prog { // on the stack in the function prologue and so always have a pointer between // the hardware stack pointer and the local variable area. func (ctxt *Link) FixedFrameSize() int64 { - switch ctxt.Arch.Family { - case sys.AMD64, sys.I386, sys.Wasm: - return 0 - case sys.PPC64: - // PIC code on ppc64le requires 32 bytes of stack, and it's easier to - // just use that much stack always on ppc64x. - return int64(4 * ctxt.Arch.PtrSize) - default: - return int64(ctxt.Arch.PtrSize) - } + return ctxt.Arch.FixedFrameSize } // LinkArch is the definition of a single architecture. diff --git a/src/cmd/internal/sys/arch.go b/src/cmd/internal/sys/arch.go index 84ed35ba8dd5c..5886b42e5155d 100644 --- a/src/cmd/internal/sys/arch.go +++ b/src/cmd/internal/sys/arch.go @@ -56,6 +56,18 @@ type Arch struct { // CanJumpTable reports whether the backend can handle // compiling a jump table. CanJumpTable bool + + // HasLR indicates that this architecture uses a link register + // for calls. + HasLR bool + + // FixedFrameSize is the smallest possible offset from the + // hardware stack pointer to a local variable on the stack. + // Architectures that use a link register save its value on + // the stack in the function prologue and so always have a + // pointer between the hardware stack pointer and the local + // variable area. + FixedFrameSize int64 } // InFamily reports whether a is a member of any of the specified @@ -70,103 +82,121 @@ func (a *Arch) InFamily(xs ...ArchFamily) bool { } var Arch386 = &Arch{ - Name: "386", - Family: I386, - ByteOrder: binary.LittleEndian, - PtrSize: 4, - RegSize: 4, - MinLC: 1, - Alignment: 1, - CanMergeLoads: true, + Name: "386", + Family: I386, + ByteOrder: binary.LittleEndian, + PtrSize: 4, + RegSize: 4, + MinLC: 1, + Alignment: 1, + CanMergeLoads: true, + HasLR: false, + FixedFrameSize: 0, } var ArchAMD64 = &Arch{ - Name: "amd64", - Family: AMD64, - ByteOrder: binary.LittleEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 1, - Alignment: 1, - CanMergeLoads: true, - CanJumpTable: true, + Name: "amd64", + Family: AMD64, + ByteOrder: binary.LittleEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 1, + Alignment: 1, + CanMergeLoads: true, + CanJumpTable: true, + HasLR: false, + FixedFrameSize: 0, } var ArchARM = &Arch{ - Name: "arm", - Family: ARM, - ByteOrder: binary.LittleEndian, - PtrSize: 4, - RegSize: 4, - MinLC: 4, - Alignment: 4, // TODO: just for arm5? - CanMergeLoads: false, + Name: "arm", + Family: ARM, + ByteOrder: binary.LittleEndian, + PtrSize: 4, + RegSize: 4, + MinLC: 4, + Alignment: 4, // TODO: just for arm5? + CanMergeLoads: false, + HasLR: true, + FixedFrameSize: 4, // LR } var ArchARM64 = &Arch{ - Name: "arm64", - Family: ARM64, - ByteOrder: binary.LittleEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 4, - Alignment: 1, - CanMergeLoads: true, + Name: "arm64", + Family: ARM64, + ByteOrder: binary.LittleEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 4, + Alignment: 1, + CanMergeLoads: true, + HasLR: true, + FixedFrameSize: 8, // LR } var ArchLoong64 = &Arch{ - Name: "loong64", - Family: Loong64, - ByteOrder: binary.LittleEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 4, - Alignment: 8, // Unaligned accesses are not guaranteed to be fast - CanMergeLoads: false, + Name: "loong64", + Family: Loong64, + ByteOrder: binary.LittleEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 4, + Alignment: 8, // Unaligned accesses are not guaranteed to be fast + CanMergeLoads: false, + HasLR: true, + FixedFrameSize: 8, // LR } var ArchMIPS = &Arch{ - Name: "mips", - Family: MIPS, - ByteOrder: binary.BigEndian, - PtrSize: 4, - RegSize: 4, - MinLC: 4, - Alignment: 4, - CanMergeLoads: false, + Name: "mips", + Family: MIPS, + ByteOrder: binary.BigEndian, + PtrSize: 4, + RegSize: 4, + MinLC: 4, + Alignment: 4, + CanMergeLoads: false, + HasLR: true, + FixedFrameSize: 4, // LR } var ArchMIPSLE = &Arch{ - Name: "mipsle", - Family: MIPS, - ByteOrder: binary.LittleEndian, - PtrSize: 4, - RegSize: 4, - MinLC: 4, - Alignment: 4, - CanMergeLoads: false, + Name: "mipsle", + Family: MIPS, + ByteOrder: binary.LittleEndian, + PtrSize: 4, + RegSize: 4, + MinLC: 4, + Alignment: 4, + CanMergeLoads: false, + HasLR: true, + FixedFrameSize: 4, // LR } var ArchMIPS64 = &Arch{ - Name: "mips64", - Family: MIPS64, - ByteOrder: binary.BigEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 4, - Alignment: 8, - CanMergeLoads: false, + Name: "mips64", + Family: MIPS64, + ByteOrder: binary.BigEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 4, + Alignment: 8, + CanMergeLoads: false, + HasLR: true, + FixedFrameSize: 8, // LR } var ArchMIPS64LE = &Arch{ - Name: "mips64le", - Family: MIPS64, - ByteOrder: binary.LittleEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 4, - Alignment: 8, - CanMergeLoads: false, + Name: "mips64le", + Family: MIPS64, + ByteOrder: binary.LittleEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 4, + Alignment: 8, + CanMergeLoads: false, + HasLR: true, + FixedFrameSize: 8, // LR } var ArchPPC64 = &Arch{ @@ -178,50 +208,62 @@ var ArchPPC64 = &Arch{ MinLC: 4, Alignment: 1, CanMergeLoads: false, + HasLR: true, + // PIC code on ppc64le requires 32 bytes of stack, and it's + // easier to just use that much stack always. + FixedFrameSize: 4 * 8, } var ArchPPC64LE = &Arch{ - Name: "ppc64le", - Family: PPC64, - ByteOrder: binary.LittleEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 4, - Alignment: 1, - CanMergeLoads: true, + Name: "ppc64le", + Family: PPC64, + ByteOrder: binary.LittleEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 4, + Alignment: 1, + CanMergeLoads: true, + HasLR: true, + FixedFrameSize: 4 * 8, } var ArchRISCV64 = &Arch{ - Name: "riscv64", - Family: RISCV64, - ByteOrder: binary.LittleEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 4, - Alignment: 8, // riscv unaligned loads work, but are really slow (trap + simulated by OS) - CanMergeLoads: false, + Name: "riscv64", + Family: RISCV64, + ByteOrder: binary.LittleEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 4, + Alignment: 8, // riscv unaligned loads work, but are really slow (trap + simulated by OS) + CanMergeLoads: false, + HasLR: true, + FixedFrameSize: 8, // LR } var ArchS390X = &Arch{ - Name: "s390x", - Family: S390X, - ByteOrder: binary.BigEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 2, - Alignment: 1, - CanMergeLoads: true, + Name: "s390x", + Family: S390X, + ByteOrder: binary.BigEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 2, + Alignment: 1, + CanMergeLoads: true, + HasLR: true, + FixedFrameSize: 8, // LR } var ArchWasm = &Arch{ - Name: "wasm", - Family: Wasm, - ByteOrder: binary.LittleEndian, - PtrSize: 8, - RegSize: 8, - MinLC: 1, - Alignment: 1, - CanMergeLoads: false, + Name: "wasm", + Family: Wasm, + ByteOrder: binary.LittleEndian, + PtrSize: 8, + RegSize: 8, + MinLC: 1, + Alignment: 1, + CanMergeLoads: false, + HasLR: false, + FixedFrameSize: 0, } var Archs = [...]*Arch{ diff --git a/src/cmd/link/internal/ld/dwarf.go b/src/cmd/link/internal/ld/dwarf.go index 2e209d0c6b7df..6ed9697aec43e 100644 --- a/src/cmd/link/internal/ld/dwarf.go +++ b/src/cmd/link/internal/ld/dwarf.go @@ -1360,7 +1360,7 @@ func (d *dwctxt) writeframes(fs loader.Sym) dwarfSecInfo { fsu := d.ldr.MakeSymbolUpdater(fs) fsu.SetType(sym.SDWARFSECT) isdw64 := isDwarf64(d.linkctxt) - haslr := haslinkregister(d.linkctxt) + haslr := d.linkctxt.Arch.HasLR // Length field is 4 bytes on Dwarf32 and 12 bytes on Dwarf64 lengthFieldSize := int64(4) diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 4295bb8656893..7104a3c8b6b12 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -2349,12 +2349,8 @@ type chain struct { limit int // limit on entry to sym } -func haslinkregister(ctxt *Link) bool { - return ctxt.FixedFrameSize() != 0 -} - func callsize(ctxt *Link) int { - if haslinkregister(ctxt) { + if ctxt.Arch.HasLR { return 0 } return ctxt.Arch.RegSize @@ -2554,7 +2550,7 @@ func (sc *stkChk) print(ch *chain, limit int) { } } else { sc.print(ch.up, ch.limit+callsize(ctxt)) - if !haslinkregister(ctxt) { + if !ctxt.Arch.HasLR { fmt.Printf("\t%d\ton entry to %s\n", ch.limit, name) } } diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index 64d18bd62c075..f1b5f4d223b93 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -33,7 +33,6 @@ package ld import ( "bufio" "cmd/internal/objabi" - "cmd/internal/sys" "cmd/link/internal/loader" "cmd/link/internal/sym" "debug/elf" @@ -108,16 +107,7 @@ type cgodata struct { // on the stack in the function prologue and so always have a pointer between // the hardware stack pointer and the local variable area. func (ctxt *Link) FixedFrameSize() int64 { - switch ctxt.Arch.Family { - case sys.AMD64, sys.I386: - return 0 - case sys.PPC64: - // PIC code on ppc64le requires 32 bytes of stack, and it's easier to - // just use that much stack always on ppc64x. - return int64(4 * ctxt.Arch.PtrSize) - default: - return int64(ctxt.Arch.PtrSize) - } + return ctxt.Arch.FixedFrameSize } func (ctxt *Link) Logf(format string, args ...interface{}) { From 5f625de4d09843ba1c996019abaddd1f85840f56 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 18 Apr 2022 13:41:08 -0400 Subject: [PATCH 128/137] cmd/compile,cmd/internal/obj: replace Ctxt.FixedFrameSize method with Arch field And delete now-unused FixedFrameSize methods. Change-Id: Id257e1647dbeb4eb4ab866c53744010c4efeb953 Reviewed-on: https://go-review.googlesource.com/c/go/+/400819 Run-TryBot: Austin Clements Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot --- src/cmd/compile/internal/abi/abiutils.go | 2 +- src/cmd/compile/internal/amd64/ssa.go | 2 +- src/cmd/compile/internal/arm/ssa.go | 2 +- src/cmd/compile/internal/arm64/ssa.go | 4 ++-- src/cmd/compile/internal/dwarfgen/dwarf.go | 4 ++-- src/cmd/compile/internal/escape/desugar.go | 4 ++-- src/cmd/compile/internal/mips/ggen.go | 4 ++-- src/cmd/compile/internal/mips/ssa.go | 2 +- src/cmd/compile/internal/mips64/ssa.go | 2 +- src/cmd/compile/internal/ppc64/ggen.go | 6 +++--- src/cmd/compile/internal/ppc64/ssa.go | 4 ++-- src/cmd/compile/internal/riscv64/ggen.go | 2 +- src/cmd/compile/internal/riscv64/ssa.go | 4 ++-- src/cmd/compile/internal/s390x/ggen.go | 2 +- src/cmd/compile/internal/s390x/ssa.go | 2 +- src/cmd/compile/internal/ssa/config.go | 4 ++-- src/cmd/compile/internal/ssa/expand_calls.go | 4 ++-- src/cmd/compile/internal/ssa/writebarrier.go | 6 +++--- src/cmd/compile/internal/ssagen/pgen.go | 4 ++-- src/cmd/compile/internal/ssagen/ssa.go | 6 +++--- src/cmd/compile/internal/x86/ssa.go | 2 +- src/cmd/internal/obj/link.go | 8 -------- src/cmd/internal/obj/mips/asm0.go | 6 +++--- src/cmd/internal/obj/mips/obj0.go | 10 +++++----- src/cmd/internal/obj/ppc64/asm9.go | 4 ++-- src/cmd/internal/obj/ppc64/obj9.go | 8 ++++---- src/cmd/internal/obj/riscv/obj.go | 4 ++-- src/cmd/internal/obj/s390x/asmz.go | 4 ++-- src/cmd/internal/obj/s390x/objz.go | 6 +++--- src/cmd/link/internal/ld/lib.go | 2 +- src/cmd/link/internal/ld/link.go | 8 -------- 31 files changed, 58 insertions(+), 74 deletions(-) diff --git a/src/cmd/compile/internal/abi/abiutils.go b/src/cmd/compile/internal/abi/abiutils.go index 07ece87c411f1..aa5063f741d61 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 7049d4e163bef..c9667bd04ae7c 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -1100,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() diff --git a/src/cmd/compile/internal/arm/ssa.go b/src/cmd/compile/internal/arm/ssa.go index 063fb65b33ab9..a53f51bd1373b 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 48eb2190b2572..3b6e6f672309d 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/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go index ba73976504e16..f84368ece3421 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: diff --git a/src/cmd/compile/internal/escape/desugar.go b/src/cmd/compile/internal/escape/desugar.go index 8b3cc25cf9a4a..6c21981acad09 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/mips/ggen.go b/src/cmd/compile/internal/mips/ggen.go index 1a5125207dd0e..a18440e7b3c40 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 6326f966bf215..0411756c8d71c 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 6e12c6cb942d6..f3e372c3bcb8f 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/ppc64/ggen.go b/src/cmd/compile/internal/ppc64/ggen.go index 7877be3336b43..4c935cfc71f8d 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 da74cacd95f27..8689bd8b27ea3 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/riscv64/ggen.go b/src/cmd/compile/internal/riscv64/ggen.go index 0f37f65fcf306..44488e43276bc 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 b6e6dc1a03d3b..5f74fd876cf6c 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 488a080c46888..70e403122481e 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 8f9c157d9a96e..7d9b31de4c289 100644 --- a/src/cmd/compile/internal/s390x/ssa.go +++ b/src/cmd/compile/internal/s390x/ssa.go @@ -550,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/config.go b/src/cmd/compile/internal/ssa/config.go index ddf2190e527be..931ef454fca35 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -332,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/expand_calls.go b/src/cmd/compile/internal/ssa/expand_calls.go index b774ea78b1a3a..90ea2d50403fd 100644 --- a/src/cmd/compile/internal/ssa/expand_calls.go +++ b/src/cmd/compile/internal/ssa/expand_calls.go @@ -1108,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 } } @@ -1129,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/writebarrier.go b/src/cmd/compile/internal/ssa/writebarrier.go index 21eee12c85325..65ff960c84721 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 86d40e239d67a..825b32aa80824 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 7b6b69ffc572d..adb95445c4396 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -5102,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). @@ -5606,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 @@ -5633,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") } diff --git a/src/cmd/compile/internal/x86/ssa.go b/src/cmd/compile/internal/x86/ssa.go index 12d9d0f365e1c..378100b1627d9 100644 --- a/src/cmd/compile/internal/x86/ssa.go +++ b/src/cmd/compile/internal/x86/ssa.go @@ -727,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/internal/obj/link.go b/src/cmd/internal/obj/link.go index 12a4c94e2496a..5f6c135f8c400 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -984,14 +984,6 @@ func (fi *FuncInfo) UnspillRegisterArgs(last *Prog, pa ProgAlloc) *Prog { return last } -// The smallest possible offset from the hardware stack pointer to a local -// variable on the stack. Architectures that use a link register save its value -// on the stack in the function prologue and so always have a pointer between -// the hardware stack pointer and the local variable area. -func (ctxt *Link) FixedFrameSize() int64 { - return ctxt.Arch.FixedFrameSize -} - // LinkArch is the definition of a single architecture. type LinkArch struct { *sys.Arch diff --git a/src/cmd/internal/obj/mips/asm0.go b/src/cmd/internal/obj/mips/asm0.go index e475ffdc146ae..ab8d37b1bee07 100644 --- a/src/cmd/internal/obj/mips/asm0.go +++ b/src/cmd/internal/obj/mips/asm0.go @@ -415,7 +415,7 @@ func span0(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { return } - c := ctxt0{ctxt: ctxt, newprog: newprog, cursym: cursym, autosize: int32(p.To.Offset + ctxt.FixedFrameSize())} + c := ctxt0{ctxt: ctxt, newprog: newprog, cursym: cursym, autosize: int32(p.To.Offset + ctxt.Arch.FixedFrameSize)} if oprange[AOR&obj.AMask] == nil { c.ctxt.Diag("mips ops not initialized, call mips.buildop first") @@ -627,7 +627,7 @@ func (c *ctxt0) aclass(a *obj.Addr) int { // a.Offset is still relative to pseudo-FP. a.Reg = obj.REG_NONE } - c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() + c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.Arch.FixedFrameSize if c.instoffset >= -BIG && c.instoffset < BIG { return C_SAUTO } @@ -695,7 +695,7 @@ func (c *ctxt0) aclass(a *obj.Addr) int { // a.Offset is still relative to pseudo-FP. a.Reg = obj.REG_NONE } - c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() + c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.Arch.FixedFrameSize if c.instoffset >= -BIG && c.instoffset < BIG { return C_SACON } diff --git a/src/cmd/internal/obj/mips/obj0.go b/src/cmd/internal/obj/mips/obj0.go index b96a28a94423b..2a2c8ecb75cee 100644 --- a/src/cmd/internal/obj/mips/obj0.go +++ b/src/cmd/internal/obj/mips/obj0.go @@ -140,7 +140,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { p := c.cursym.Func().Text textstksiz := p.To.Offset - if textstksiz == -ctxt.FixedFrameSize() { + if textstksiz == -ctxt.Arch.FixedFrameSize { // Historical way to mark NOFRAME. p.From.Sym.Set(obj.AttrNoFrame, true) textstksiz = 0 @@ -282,7 +282,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if !p.From.Sym.NoFrame() { // If there is a stack frame at all, it includes // space to save the LR. - autosize += int32(c.ctxt.FixedFrameSize()) + autosize += int32(c.ctxt.Arch.FixedFrameSize) } if autosize&4 != 0 && c.ctxt.Arch.Family == sys.MIPS64 { @@ -299,7 +299,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { } } - p.To.Offset = int64(autosize) - ctxt.FixedFrameSize() + p.To.Offset = int64(autosize) - ctxt.Arch.FixedFrameSize if c.cursym.Func().Text.Mark&LEAF != 0 { c.cursym.Set(obj.AttrLeaf, true) @@ -392,7 +392,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q = obj.Appendp(q, newprog) q.As = add q.From.Type = obj.TYPE_CONST - q.From.Offset = int64(autosize) + ctxt.FixedFrameSize() + q.From.Offset = int64(autosize) + ctxt.Arch.FixedFrameSize q.Reg = REGSP q.To.Type = obj.TYPE_REG q.To.Reg = REG_R3 @@ -409,7 +409,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q = obj.Appendp(q, newprog) q.As = add q.From.Type = obj.TYPE_CONST - q.From.Offset = ctxt.FixedFrameSize() + q.From.Offset = ctxt.Arch.FixedFrameSize q.Reg = REGSP q.To.Type = obj.TYPE_REG q.To.Reg = REG_R2 diff --git a/src/cmd/internal/obj/ppc64/asm9.go b/src/cmd/internal/obj/ppc64/asm9.go index 50c9b37f02fa2..9ee469c6f226e 100644 --- a/src/cmd/internal/obj/ppc64/asm9.go +++ b/src/cmd/internal/obj/ppc64/asm9.go @@ -917,7 +917,7 @@ func (c *ctxt9) aclass(a *obj.Addr) int { return C_LOREG case obj.NAME_PARAM: - c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() + c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.Arch.FixedFrameSize if c.instoffset >= -BIG && c.instoffset < BIG { return C_SOREG } @@ -983,7 +983,7 @@ func (c *ctxt9) aclass(a *obj.Addr) int { return C_LACON case obj.NAME_PARAM: - c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() + c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.Arch.FixedFrameSize if c.instoffset >= -BIG && c.instoffset < BIG { return C_SACON } diff --git a/src/cmd/internal/obj/ppc64/obj9.go b/src/cmd/internal/obj/ppc64/obj9.go index 0a938d7a13783..098f1cd7fece4 100644 --- a/src/cmd/internal/obj/ppc64/obj9.go +++ b/src/cmd/internal/obj/ppc64/obj9.go @@ -614,7 +614,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if !p.From.Sym.NoFrame() { // If there is a stack frame at all, it includes // space to save the LR. - autosize += int32(c.ctxt.FixedFrameSize()) + autosize += int32(c.ctxt.Arch.FixedFrameSize) } if p.Mark&LEAF != 0 && autosize < objabi.StackSmall { @@ -811,7 +811,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q = obj.Appendp(q, c.newprog) q.As = AADD q.From.Type = obj.TYPE_CONST - q.From.Offset = int64(autosize) + c.ctxt.FixedFrameSize() + q.From.Offset = int64(autosize) + c.ctxt.Arch.FixedFrameSize q.Reg = REGSP q.To.Type = obj.TYPE_REG q.To.Reg = REG_R24 @@ -831,7 +831,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q = obj.Appendp(q, c.newprog) q.As = AADD q.From.Type = obj.TYPE_CONST - q.From.Offset = c.ctxt.FixedFrameSize() + q.From.Offset = c.ctxt.Arch.FixedFrameSize q.Reg = REGSP q.To.Type = obj.TYPE_REG q.To.Reg = REG_R25 @@ -1066,7 +1066,7 @@ func (c *ctxt9) stacksplit(p *obj.Prog, framesize int32) *obj.Prog { p = c.cursym.Func().SpillRegisterArgs(p, c.newprog) // Save LR and REGCTXT - frameSize := 8 + c.ctxt.FixedFrameSize() + frameSize := 8 + c.ctxt.Arch.FixedFrameSize // MOVD LR, REGTMP p = obj.Appendp(p, c.newprog) diff --git a/src/cmd/internal/obj/riscv/obj.go b/src/cmd/internal/obj/riscv/obj.go index 0f52f6677944b..b30958cb38550 100644 --- a/src/cmd/internal/obj/riscv/obj.go +++ b/src/cmd/internal/obj/riscv/obj.go @@ -380,7 +380,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { // Save LR unless there is no frame. if !text.From.Sym.NoFrame() { - stacksize += ctxt.FixedFrameSize() + stacksize += ctxt.Arch.FixedFrameSize } cursym.Func().Args = text.To.Val.(int32) @@ -461,7 +461,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { calcargp := obj.Appendp(getargp, newprog) calcargp.As = AADDI - calcargp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: stacksize + ctxt.FixedFrameSize()} + calcargp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: stacksize + ctxt.Arch.FixedFrameSize} calcargp.Reg = REG_SP calcargp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_X7} diff --git a/src/cmd/internal/obj/s390x/asmz.go b/src/cmd/internal/obj/s390x/asmz.go index 06921085c9584..5760847bcd4db 100644 --- a/src/cmd/internal/obj/s390x/asmz.go +++ b/src/cmd/internal/obj/s390x/asmz.go @@ -586,7 +586,7 @@ func (c *ctxtz) aclass(a *obj.Addr) int { // a.Offset is still relative to pseudo-FP. a.Reg = obj.REG_NONE } - c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() + c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.Arch.FixedFrameSize if c.instoffset >= -BIG && c.instoffset < BIG { return C_SAUTO } @@ -657,7 +657,7 @@ func (c *ctxtz) aclass(a *obj.Addr) int { // a.Offset is still relative to pseudo-FP. a.Reg = obj.REG_NONE } - c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.FixedFrameSize() + c.instoffset = int64(c.autosize) + a.Offset + c.ctxt.Arch.FixedFrameSize if c.instoffset >= -BIG && c.instoffset < BIG { return C_SACON } diff --git a/src/cmd/internal/obj/s390x/objz.go b/src/cmd/internal/obj/s390x/objz.go index aebbf8dbc55f1..fed770375920e 100644 --- a/src/cmd/internal/obj/s390x/objz.go +++ b/src/cmd/internal/obj/s390x/objz.go @@ -310,7 +310,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { if !p.From.Sym.NoFrame() { // If there is a stack frame at all, it includes // space to save the LR. - autosize += int32(c.ctxt.FixedFrameSize()) + autosize += int32(c.ctxt.Arch.FixedFrameSize) } if p.Mark&LEAF != 0 && autosize < objabi.StackSmall { @@ -420,7 +420,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q = obj.Appendp(q, c.newprog) q.As = AADD q.From.Type = obj.TYPE_CONST - q.From.Offset = int64(autosize) + c.ctxt.FixedFrameSize() + q.From.Offset = int64(autosize) + c.ctxt.Arch.FixedFrameSize q.Reg = REGSP q.To.Type = obj.TYPE_REG q.To.Reg = REG_R5 @@ -440,7 +440,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) { q = obj.Appendp(q, c.newprog) q.As = AADD q.From.Type = obj.TYPE_CONST - q.From.Offset = c.ctxt.FixedFrameSize() + q.From.Offset = c.ctxt.Arch.FixedFrameSize q.Reg = REGSP q.To.Type = obj.TYPE_REG q.To.Reg = REG_R6 diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index 7104a3c8b6b12..e26b1e56ddd28 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -2471,7 +2471,7 @@ func (sc *stkChk) check(up *chain, depth int) int { } // Raise limit to allow frame. locals := info.Locals() - limit = objabi.StackLimit + int(locals) + int(ctxt.FixedFrameSize()) + limit = objabi.StackLimit + int(locals) + int(ctxt.Arch.FixedFrameSize) } // Walk through sp adjustments in function, consuming relocs. diff --git a/src/cmd/link/internal/ld/link.go b/src/cmd/link/internal/ld/link.go index f1b5f4d223b93..34221dfa8a764 100644 --- a/src/cmd/link/internal/ld/link.go +++ b/src/cmd/link/internal/ld/link.go @@ -102,14 +102,6 @@ type cgodata struct { directives [][]string } -// The smallest possible offset from the hardware stack pointer to a local -// variable on the stack. Architectures that use a link register save its value -// on the stack in the function prologue and so always have a pointer between -// the hardware stack pointer and the local variable area. -func (ctxt *Link) FixedFrameSize() int64 { - return ctxt.Arch.FixedFrameSize -} - func (ctxt *Link) Logf(format string, args ...interface{}) { fmt.Fprintf(ctxt.Bso, format, args...) ctxt.Bso.Flush() From 120f445495a573f80b1f0c0acc326c281c01b13e Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 4 Apr 2022 14:51:39 -0400 Subject: [PATCH 129/137] test/nosplit: apply stack limit adjustment in the right place The nosplit test was originally written when the stack limit was a mere 128 bytes. Now it's much larger, but rather than rewriting all of the tests, we apply a hack to just add the extra space into the stack frames of the existing tests. Unfortunately, we add it in the wrong place. The extra space should be added just once per chain of nosplit functions, but instead we add it to every frame that appears first on a line in the test's little script language. This means that for tests like start 0 call f1 f1 16 nosplit call f2 f2 16 nosplit call f3 f3 16 nosplit call f4 f4 16 nosplit call f5 f5 16 nosplit call f6 f6 16 nosplit call f7 f7 16 nosplit call f8 f8 16 nosplit call end end 1000 REJECT we add 672 bytes to *every* frame, meaning that we wind up way over the stack limit by the end of the stanza, rather than just a little as originally intended. Fix this by instead adding the extra space to the first nosplit function in a stanza. This isn't perfect either, since we could have a nosplit -> split -> nosplit chain, but it's the best we can do without a graph analysis. Change-Id: Ibf156c68fe3eb1b64a438115f4a17f1a6c7e2bd1 Reviewed-on: https://go-review.googlesource.com/c/go/+/398174 Run-TryBot: Austin Clements Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot --- test/nosplit.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/test/nosplit.go b/test/nosplit.go index 7c7e1bfd99e01..1d94f6eb915ee 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -292,12 +292,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 +312,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 +336,6 @@ TestCases: } } - if size%ptrSize == 4 { - continue TestCases - } - nosplit := m[3] - body := m[4] - if nosplit != "" { nosplit = ",7" } else { From 7a062432059e0a7f6217424733d3a01030897741 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 4 Apr 2022 15:41:08 -0400 Subject: [PATCH 130/137] test/nosplit: add more complicated recursion tests Change-Id: I301ed8bcc93f31147d247e60a7aab8ed42421bbd Reviewed-on: https://go-review.googlesource.com/c/go/+/398175 Run-TryBot: Austin Clements Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot --- test/nosplit.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/nosplit.go b/test/nosplit.go index 1d94f6eb915ee..7e0fd4e7916e8 100644 --- a/test/nosplit.go +++ b/test/nosplit.go @@ -70,6 +70,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 +117,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. From e25f46e59621caf02f8ac1acc8d1dbe028859e47 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Fri, 1 Apr 2022 15:51:12 -0400 Subject: [PATCH 131/137] cmd/link: faster algorithm for nosplit stack checking, better errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The linker performs a global analysis of all nosplit call chains to check they fit in the stack space ensured by splittable functions. That analysis has two problems right now: 1. It's inefficient. It performs a top-down analysis, starting with every nosplit function and the nosplit stack limit and walking *down* the call graph to compute how much stack remains at every call. As a result, it visits the same functions over and over, often with different remaining stack depths. This approach is historical: this check was originally written in C and this approach avoided the need for any interesting data structures. 2. If some call chain is over the limit, it only reports a single call chain. As a result, if the check does fail, you often wind up playing whack-a-mole by guessing where the problem is in the one chain, trying to reduce the stack size, and then seeing if the link works or reports a different path. This CL completely rewrites the nosplit stack check. It now uses a bottom-up analysis, computing the maximum stack height required by every function's call tree. This visits every function exactly once, making it much more efficient. It uses slightly more heap space for intermediate storage, but still very little in the scheme of the overall link. For example, when linking cmd/go, the new algorithm virtually eliminates the time spent in this pass, and reduces overall link time: │ before │ after │ │ sec/op │ sec/op vs base │ Dostkcheck 7.926m ± 4% 1.831m ± 6% -76.90% (p=0.000 n=20) TotalTime 301.3m ± 1% 296.4m ± 3% -1.62% (p=0.040 n=20) │ before │ after │ │ B/op │ B/op vs base │ Dostkcheck 40.00Ki ± 0% 212.15Ki ± 0% +430.37% (p=0.000 n=20) Most of this time is spent analyzing the runtime, so for larger binaries, the total time saved is roughly the same, and proportionally less of the overall link. If the new implementation finds an error, it redoes the analysis, switching to preferring quality of error reporting over performance. For error reporting, it computes stack depths top-down (like the old algorithm), and reports *all* paths that are over the stack limit, presented as a tree for compactness. For example, this is the output from a simple test case from test/nosplit with two over-limit paths from f1: main.f1: nosplit stack overflow main.f1 grows 768 bytes, calls main.f2 grows 56 bytes, calls main.f4 grows 48 bytes 80 bytes over limit grows 768 bytes, calls main.f3 grows 104 bytes 80 bytes over limit While we're here, we do a few nice cleanups: - We add a debug output flag, which will be useful for understanding what our nosplit chains look like and which ones are close to running over. - We move the implementation out of the fog of lib.go to its own file. - The implementation is generally more Go-like and less C-like. Change-Id: If1ab31197f5215475559b93695c44a01bd16e276 Reviewed-on: https://go-review.googlesource.com/c/go/+/398176 Run-TryBot: Austin Clements Reviewed-by: Than McIntosh Reviewed-by: Cherry Mui TryBot-Result: Gopher Robot --- src/cmd/link/internal/ld/lib.go | 218 --------- src/cmd/link/internal/ld/main.go | 5 +- src/cmd/link/internal/ld/stackcheck.go | 421 ++++++++++++++++++ src/cmd/link/internal/ld/stackcheck_test.go | 89 ++++ .../internal/ld/testdata/stackcheck/main.go | 20 + .../internal/ld/testdata/stackcheck/main.s | 40 ++ test/nosplit.go | 3 +- 7 files changed, 575 insertions(+), 221 deletions(-) create mode 100644 src/cmd/link/internal/ld/stackcheck.go create mode 100644 src/cmd/link/internal/ld/stackcheck_test.go create mode 100644 src/cmd/link/internal/ld/testdata/stackcheck/main.go create mode 100644 src/cmd/link/internal/ld/testdata/stackcheck/main.s diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go index e26b1e56ddd28..d995f7676bc75 100644 --- a/src/cmd/link/internal/ld/lib.go +++ b/src/cmd/link/internal/ld/lib.go @@ -34,7 +34,6 @@ import ( "bytes" "cmd/internal/bio" "cmd/internal/goobj" - "cmd/internal/obj" "cmd/internal/objabi" "cmd/internal/sys" "cmd/link/internal/loadelf" @@ -2343,223 +2342,6 @@ func addsection(ldr *loader.Loader, arch *sys.Arch, seg *sym.Segment, name strin return sect } -type chain struct { - sym loader.Sym - up *chain - limit int // limit on entry to sym -} - -func callsize(ctxt *Link) int { - if ctxt.Arch.HasLR { - return 0 - } - return ctxt.Arch.RegSize -} - -type stkChk struct { - ldr *loader.Loader - ctxt *Link - morestack loader.Sym - done loader.Bitmap -} - -// Walk the call tree and check that there is always enough stack space -// for the call frames, especially for a chain of nosplit functions. -func (ctxt *Link) dostkcheck() { - ldr := ctxt.loader - sc := stkChk{ - ldr: ldr, - ctxt: ctxt, - morestack: ldr.Lookup("runtime.morestack", 0), - done: loader.MakeBitmap(ldr.NSym()), - } - - // Every splitting function ensures that there are at least StackLimit - // bytes available below SP when the splitting prologue finishes. - // If the splitting function calls F, then F begins execution with - // at least StackLimit - callsize() bytes available. - // Check that every function behaves correctly with this amount - // of stack, following direct calls in order to piece together chains - // of non-splitting functions. - var ch chain - ch.limit = objabi.StackLimit - callsize(ctxt) - if buildcfg.GOARCH == "arm64" { - // need extra 8 bytes below SP to save FP - ch.limit -= 8 - } - - // Check every function, but do the nosplit functions in a first pass, - // to make the printed failure chains as short as possible. - for _, s := range ctxt.Textp { - if ldr.IsNoSplit(s) { - ch.sym = s - sc.check(&ch, 0) - } - } - - for _, s := range ctxt.Textp { - if !ldr.IsNoSplit(s) { - ch.sym = s - sc.check(&ch, 0) - } - } -} - -func (sc *stkChk) check(up *chain, depth int) int { - limit := up.limit - s := up.sym - ldr := sc.ldr - ctxt := sc.ctxt - - // Don't duplicate work: only need to consider each - // function at top of safe zone once. - top := limit == objabi.StackLimit-callsize(ctxt) - if top { - if sc.done.Has(s) { - return 0 - } - sc.done.Set(s) - } - - if depth > 500 { - sc.ctxt.Errorf(s, "nosplit stack check too deep") - sc.broke(up, 0) - return -1 - } - - if ldr.AttrExternal(s) { - // external function. - // should never be called directly. - // onlyctxt.Diagnose the direct caller. - // TODO(mwhudson): actually think about this. - // TODO(khr): disabled for now. Calls to external functions can only happen on the g0 stack. - // See the trampolines in src/runtime/sys_darwin_$ARCH.go. - //if depth == 1 && ldr.SymType(s) != sym.SXREF && !ctxt.DynlinkingGo() && - // ctxt.BuildMode != BuildModeCArchive && ctxt.BuildMode != BuildModePIE && ctxt.BuildMode != BuildModeCShared && ctxt.BuildMode != BuildModePlugin { - // Errorf(s, "call to external function") - //} - return -1 - } - info := ldr.FuncInfo(s) - if !info.Valid() { // external function. see above. - return -1 - } - - if limit < 0 { - sc.broke(up, limit) - return -1 - } - - // morestack looks like it calls functions, - // but it switches the stack pointer first. - if s == sc.morestack { - return 0 - } - - var ch chain - ch.up = up - - if !ldr.IsNoSplit(s) { - // Ensure we have enough stack to call morestack. - ch.limit = limit - callsize(ctxt) - ch.sym = sc.morestack - if sc.check(&ch, depth+1) < 0 { - return -1 - } - if !top { - return 0 - } - // Raise limit to allow frame. - locals := info.Locals() - limit = objabi.StackLimit + int(locals) + int(ctxt.Arch.FixedFrameSize) - } - - // Walk through sp adjustments in function, consuming relocs. - relocs := ldr.Relocs(s) - var ch1 chain - pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) - ri := 0 - for pcsp.Init(ldr.Data(ldr.Pcsp(s))); !pcsp.Done; pcsp.Next() { - // pcsp.value is in effect for [pcsp.pc, pcsp.nextpc). - - // Check stack size in effect for this span. - if int32(limit)-pcsp.Value < 0 { - sc.broke(up, int(int32(limit)-pcsp.Value)) - return -1 - } - - // Process calls in this span. - for ; ri < relocs.Count(); ri++ { - r := relocs.At(ri) - if uint32(r.Off()) >= pcsp.NextPC { - break - } - t := r.Type() - switch { - case t.IsDirectCall(): - ch.limit = int(int32(limit) - pcsp.Value - int32(callsize(ctxt))) - ch.sym = r.Sym() - if sc.check(&ch, depth+1) < 0 { - return -1 - } - - // Indirect call. Assume it is a call to a splitting function, - // so we have to make sure it can call morestack. - // Arrange the data structures to report both calls, so that - // if there is an error, stkprint shows all the steps involved. - case t == objabi.R_CALLIND: - ch.limit = int(int32(limit) - pcsp.Value - int32(callsize(ctxt))) - ch.sym = 0 - ch1.limit = ch.limit - callsize(ctxt) // for morestack in called prologue - ch1.up = &ch - ch1.sym = sc.morestack - if sc.check(&ch1, depth+2) < 0 { - return -1 - } - } - } - } - - return 0 -} - -func (sc *stkChk) broke(ch *chain, limit int) { - sc.ctxt.Errorf(ch.sym, "nosplit stack overflow") - sc.print(ch, limit) -} - -func (sc *stkChk) print(ch *chain, limit int) { - ldr := sc.ldr - ctxt := sc.ctxt - var name string - if ch.sym != 0 { - name = fmt.Sprintf("%s<%d>", ldr.SymName(ch.sym), ldr.SymVersion(ch.sym)) - if ldr.IsNoSplit(ch.sym) { - name += " (nosplit)" - } - } else { - name = "function pointer" - } - - if ch.up == nil { - // top of chain. ch.sym != 0. - if ldr.IsNoSplit(ch.sym) { - fmt.Printf("\t%d\tassumed on entry to %s\n", ch.limit, name) - } else { - fmt.Printf("\t%d\tguaranteed after split check in %s\n", ch.limit, name) - } - } else { - sc.print(ch.up, ch.limit+callsize(ctxt)) - if !ctxt.Arch.HasLR { - fmt.Printf("\t%d\ton entry to %s\n", ch.limit, name) - } - } - - if ch.limit != limit { - fmt.Printf("\t%d\tafter %s uses %d\n", limit, name, ch.limit-limit) - } -} - func usage() { fmt.Fprintf(os.Stderr, "usage: link [options] main.o\n") objabi.Flagprint(os.Stderr) diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index fa95a7acf23d5..c52e6e909d847 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -93,6 +93,7 @@ var ( flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker") FlagDebugTramp = flag.Int("debugtramp", 0, "debug trampolines") FlagDebugTextSize = flag.Int("debugtextsize", 0, "debug text section max size") + flagDebugNosplit = flag.Bool("debugnosplit", false, "dump nosplit call graph") FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).") FlagRound = flag.Int("R", -1, "set address rounding `quantum`") FlagTextAddr = flag.Int64("T", -1, "set text segment `address`") @@ -283,8 +284,8 @@ func Main(arch *sys.Arch, theArch Arch) { bench.Start("callgraph") ctxt.callgraph() - bench.Start("dostkcheck") - ctxt.dostkcheck() + bench.Start("doStackCheck") + ctxt.doStackCheck() bench.Start("mangleTypeSym") ctxt.mangleTypeSym() diff --git a/src/cmd/link/internal/ld/stackcheck.go b/src/cmd/link/internal/ld/stackcheck.go new file mode 100644 index 0000000000000..520e4d67b5d19 --- /dev/null +++ b/src/cmd/link/internal/ld/stackcheck.go @@ -0,0 +1,421 @@ +// 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 ld + +import ( + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/link/internal/loader" + "fmt" + "internal/buildcfg" + "sort" + "strings" +) + +type stackCheck struct { + ctxt *Link + ldr *loader.Loader + morestack loader.Sym + callSize int // The number of bytes added by a CALL + + // height records the maximum number of bytes a function and + // its callees can add to the stack without a split check. + height map[loader.Sym]int16 + + // graph records the out-edges from each symbol. This is only + // populated on a second pass if the first pass reveals an + // over-limit function. + graph map[loader.Sym][]stackCheckEdge +} + +type stackCheckEdge struct { + growth int // Stack growth in bytes at call to target + target loader.Sym // 0 for stack growth without a call +} + +// stackCheckCycle is a sentinel stored in the height map to detect if +// we've found a cycle. This is effectively an "infinite" stack +// height, so we use the closest value to infinity that we can. +const stackCheckCycle int16 = 1<<15 - 1 + +// stackCheckIndirect is a sentinel Sym value used to represent the +// target of an indirect/closure call. +const stackCheckIndirect loader.Sym = -1 + +// doStackCheck walks the call tree to check that there is always +// enough stack space for call frames, especially for a chain of +// nosplit functions. +// +// It walks all functions to accumulate the number of bytes they can +// grow the stack by without a split check and checks this against the +// limit. +func (ctxt *Link) doStackCheck() { + sc := newStackCheck(ctxt, false) + + // limit is number of bytes a splittable function ensures are + // available on the stack. If any call chain exceeds this + // depth, the stack check test fails. + // + // The call to morestack in every splittable function ensures + // that there are at least StackLimit bytes available below SP + // when morestack returns. + limit := objabi.StackLimit - sc.callSize + if buildcfg.GOARCH == "arm64" { + // Need an extra 8 bytes below SP to save FP. + limit -= 8 + } + + // Compute stack heights without any back-tracking information. + // This will almost certainly succeed and we can simply + // return. If it fails, we do a second pass with back-tracking + // to produce a good error message. + // + // This accumulates stack heights bottom-up so it only has to + // visit every function once. + var failed []loader.Sym + for _, s := range ctxt.Textp { + if sc.check(s) > limit { + failed = append(failed, s) + } + } + + if len(failed) > 0 { + // Something was over-limit, so now we do the more + // expensive work to report a good error. First, for + // the over-limit functions, redo the stack check but + // record the graph this time. + sc = newStackCheck(ctxt, true) + for _, s := range failed { + sc.check(s) + } + + // Find the roots of the graph (functions that are not + // called by any other function). + roots := sc.findRoots() + + // Find and report all paths that go over the limit. + // This accumulates stack depths top-down. This is + // much less efficient because we may have to visit + // the same function multiple times at different + // depths, but lets us find all paths. + for _, root := range roots { + ctxt.Errorf(root, "nosplit stack overflow") + chain := []stackCheckChain{{stackCheckEdge{0, root}, false}} + sc.report(root, limit, &chain) + } + } +} + +func newStackCheck(ctxt *Link, graph bool) *stackCheck { + sc := &stackCheck{ + ctxt: ctxt, + ldr: ctxt.loader, + morestack: ctxt.loader.Lookup("runtime.morestack", 0), + height: make(map[loader.Sym]int16, len(ctxt.Textp)), + } + // Compute stack effect of a CALL operation. 0 on LR machines. + // 1 register pushed on non-LR machines. + if !ctxt.Arch.HasLR { + sc.callSize = ctxt.Arch.RegSize + } + + if graph { + // We're going to record the call graph. + sc.graph = make(map[loader.Sym][]stackCheckEdge) + } + + return sc +} + +func (sc *stackCheck) symName(sym loader.Sym) string { + switch sym { + case stackCheckIndirect: + return "indirect" + case 0: + return "leaf" + } + return fmt.Sprintf("%s<%d>", sc.ldr.SymName(sym), sc.ldr.SymVersion(sym)) +} + +// check returns the stack height of sym. It populates sc.height and +// sc.graph for sym and every function in its call tree. +func (sc *stackCheck) check(sym loader.Sym) int { + if h, ok := sc.height[sym]; ok { + // We've already visited this symbol or we're in a cycle. + return int(h) + } + // Store the sentinel so we can detect cycles. + sc.height[sym] = stackCheckCycle + // Compute and record the height and optionally edges. + h, edges := sc.computeHeight(sym, *flagDebugNosplit || sc.graph != nil) + if h > int(stackCheckCycle) { // Prevent integer overflow + h = int(stackCheckCycle) + } + sc.height[sym] = int16(h) + if sc.graph != nil { + sc.graph[sym] = edges + } + + if *flagDebugNosplit { + for _, edge := range edges { + fmt.Printf("nosplit: %s +%d", sc.symName(sym), edge.growth) + if edge.target == 0 { + // Local stack growth or leaf function. + fmt.Printf("\n") + } else { + fmt.Printf(" -> %s\n", sc.symName(edge.target)) + } + } + } + + return h +} + +// computeHeight returns the stack height of sym. If graph is true, it +// also returns the out-edges of sym. +// +// Caching is applied to this in check. Call check instead of calling +// this directly. +func (sc *stackCheck) computeHeight(sym loader.Sym, graph bool) (int, []stackCheckEdge) { + ldr := sc.ldr + + // Check special cases. + if sym == sc.morestack { + // morestack looks like it calls functions, but they + // either happen only when already on the system stack + // (where there is ~infinite space), or after + // switching to the system stack. Hence, its stack + // height on the user stack is 0. + return 0, nil + } + if sym == stackCheckIndirect { + // Assume that indirect/closure calls are always to + // splittable functions, so they just need enough room + // to call morestack. + return sc.callSize, []stackCheckEdge{{sc.callSize, sc.morestack}} + } + + // Ignore calls to external functions. Assume that these calls + // are only ever happening on the system stack, where there's + // plenty of room. + if ldr.AttrExternal(sym) { + return 0, nil + } + if info := ldr.FuncInfo(sym); !info.Valid() { // also external + return 0, nil + } + + // Track the maximum height of this function and, if we're + // recording the graph, its out-edges. + var edges []stackCheckEdge + maxHeight := 0 + ctxt := sc.ctxt + // addEdge adds a stack growth out of this function to + // function "target" or, if target == 0, a local stack growth + // within the function. + addEdge := func(growth int, target loader.Sym) { + if graph { + edges = append(edges, stackCheckEdge{growth, target}) + } + height := growth + if target != 0 { // Don't walk into the leaf "edge" + height += sc.check(target) + } + if height > maxHeight { + maxHeight = height + } + } + + if !ldr.IsNoSplit(sym) { + // Splittable functions start with a call to + // morestack, after which their height is 0. Account + // for the height of the call to morestack. + addEdge(sc.callSize, sc.morestack) + return maxHeight, edges + } + + // This function is nosplit, so it adjusts SP without a split + // check. + // + // Walk through SP adjustments in function, consuming relocs + // and following calls. + maxLocalHeight := 0 + relocs, ri := ldr.Relocs(sym), 0 + pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC)) + for pcsp.Init(ldr.Data(ldr.Pcsp(sym))); !pcsp.Done; pcsp.Next() { + // pcsp.value is in effect for [pcsp.pc, pcsp.nextpc). + height := int(pcsp.Value) + if height > maxLocalHeight { + maxLocalHeight = height + } + + // Process calls in this span. + for ; ri < relocs.Count(); ri++ { + r := relocs.At(ri) + if uint32(r.Off()) >= pcsp.NextPC { + break + } + t := r.Type() + if t.IsDirectCall() || t == objabi.R_CALLIND { + growth := height + sc.callSize + var target loader.Sym + if t == objabi.R_CALLIND { + target = stackCheckIndirect + } else { + target = r.Sym() + } + addEdge(growth, target) + } + } + } + if maxLocalHeight > maxHeight { + // This is either a leaf function, or the function + // grew its stack to larger than the maximum call + // height between calls. Either way, record that local + // stack growth. + addEdge(maxLocalHeight, 0) + } + + return maxHeight, edges +} + +func (sc *stackCheck) findRoots() []loader.Sym { + // Collect all nodes. + nodes := make(map[loader.Sym]struct{}) + for k := range sc.graph { + nodes[k] = struct{}{} + } + + // Start a DFS from each node and delete all reachable + // children. If we encounter an unrooted cycle, this will + // delete everything in that cycle, so we detect this case and + // track the lowest-numbered node encountered in the cycle and + // put that node back as a root. + var walk func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym) + walk = func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym) { + if _, ok := nodes[sym]; !ok { + // We already deleted this node. + return false, 0 + } + delete(nodes, sym) + + if origin == sym { + // We found an unrooted cycle. We already + // deleted all children of this node. Walk + // back up, tracking the lowest numbered + // symbol in this cycle. + return true, sym + } + + // Delete children of this node. + for _, out := range sc.graph[sym] { + if c, l := walk(origin, out.target); c { + cycle = true + if lowest == 0 { + // On first cycle detection, + // add sym to the set of + // lowest-numbered candidates. + lowest = sym + } + if l < lowest { + lowest = l + } + } + } + return + } + for k := range nodes { + // Delete all children of k. + for _, out := range sc.graph[k] { + if cycle, lowest := walk(k, out.target); cycle { + // This is an unrooted cycle so we + // just deleted everything. Put back + // the lowest-numbered symbol. + nodes[lowest] = struct{}{} + } + } + } + + // Sort roots by height. This makes the result deterministic + // and also improves the error reporting. + var roots []loader.Sym + for k := range nodes { + roots = append(roots, k) + } + sort.Slice(roots, func(i, j int) bool { + h1, h2 := sc.height[roots[i]], sc.height[roots[j]] + if h1 != h2 { + return h1 > h2 + } + // Secondary sort by Sym. + return roots[i] < roots[j] + }) + return roots +} + +type stackCheckChain struct { + stackCheckEdge + printed bool +} + +func (sc *stackCheck) report(sym loader.Sym, depth int, chain *[]stackCheckChain) { + // Walk the out-edges of sym. We temporarily pull the edges + // out of the graph to detect cycles and prevent infinite + // recursion. + edges, ok := sc.graph[sym] + isCycle := !(ok || sym == 0) + delete(sc.graph, sym) + for _, out := range edges { + *chain = append(*chain, stackCheckChain{out, false}) + sc.report(out.target, depth-out.growth, chain) + *chain = (*chain)[:len(*chain)-1] + } + sc.graph[sym] = edges + + // If we've reached the end of a chain and it went over the + // stack limit or was a cycle that would eventually go over, + // print the whole chain. + // + // We should either be in morestack (which has no out-edges) + // or the sentinel 0 Sym "called" from a leaf function (which + // has no out-edges), or we came back around a cycle (possibly + // to ourselves) and edges was temporarily nil'd. + if len(edges) == 0 && (depth < 0 || isCycle) { + var indent string + for i := range *chain { + ent := &(*chain)[i] + if ent.printed { + // Already printed on an earlier part + // of this call tree. + continue + } + ent.printed = true + + if i == 0 { + // chain[0] is just the root function, + // not a stack growth. + fmt.Printf("%s\n", sc.symName(ent.target)) + continue + } + + indent = strings.Repeat(" ", i) + fmt.Print(indent) + // Grows the stack X bytes and (maybe) calls Y. + fmt.Printf("grows %d bytes", ent.growth) + if ent.target == 0 { + // Not a call, just a leaf. Print nothing. + } else { + fmt.Printf(", calls %s", sc.symName(ent.target)) + } + fmt.Printf("\n") + } + // Print how far over this chain went. + if isCycle { + fmt.Printf("%sinfinite cycle\n", indent) + } else { + fmt.Printf("%s%d bytes over limit\n", indent, -depth) + } + } +} diff --git a/src/cmd/link/internal/ld/stackcheck_test.go b/src/cmd/link/internal/ld/stackcheck_test.go new file mode 100644 index 0000000000000..21dbf2b3fd451 --- /dev/null +++ b/src/cmd/link/internal/ld/stackcheck_test.go @@ -0,0 +1,89 @@ +// 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 ld + +import ( + "cmd/internal/objabi" + "cmd/internal/sys" + "fmt" + "internal/testenv" + "os" + "os/exec" + "regexp" + "testing" +) + +// See also $GOROOT/test/nosplit.go for multi-platform edge case tests. + +func TestStackCheckOutput(t *testing.T) { + testenv.MustHaveGoBuild(t) + t.Parallel() + + cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", os.DevNull, "./testdata/stackcheck") + // The rules for computing frame sizes on all of the + // architectures are complicated, so just do this on amd64. + cmd.Env = append(os.Environ(), "GOARCH=amd64") + outB, err := cmd.CombinedOutput() + + if err == nil { + t.Fatalf("expected link to fail") + } + out := string(outB) + + t.Logf("linker output:\n%s", out) + + // Construct expected stanzas + arch := sys.ArchAMD64 + call := 0 + if !arch.HasLR { + call = arch.RegSize + } + limit := objabi.StackLimit - call + + wantMap := map[string]string{ + "main.startSelf": fmt.Sprintf( + `main.startSelf<0> + grows 1008 bytes + %d bytes over limit +`, 1008-limit), + "main.startChain": fmt.Sprintf( + `main.startChain<0> + grows 32 bytes, calls main.chain0<0> + grows 48 bytes, calls main.chainEnd<0> + grows 1008 bytes + %d bytes over limit + grows 32 bytes, calls main.chain2<0> + grows 80 bytes, calls main.chainEnd<0> + grows 1008 bytes + %d bytes over limit +`, 32+48+1008-limit, 32+80+1008-limit), + "main.startRec": `main.startRec<0> + grows 8 bytes, calls main.startRec0<0> + grows 8 bytes, calls main.startRec<0> + infinite cycle +`, + } + + // Parse stanzas + stanza := regexp.MustCompile(`^(.*): nosplit stack overflow\n(.*\n(?: .*\n)*)`) + // Strip comments from cmd/go + out = regexp.MustCompile(`(?m)^#.*\n`).ReplaceAllString(out, "") + for len(out) > 0 { + m := stanza.FindStringSubmatch(out) + if m == nil { + t.Fatalf("unexpected output:\n%s", out) + } + out = out[len(m[0]):] + fn := m[1] + got := m[2] + + want, ok := wantMap[fn] + if !ok { + t.Errorf("unexpected function: %s", fn) + } else if want != got { + t.Errorf("want:\n%sgot:\n%s", want, got) + } + } +} diff --git a/src/cmd/link/internal/ld/testdata/stackcheck/main.go b/src/cmd/link/internal/ld/testdata/stackcheck/main.go new file mode 100644 index 0000000000000..b708cc5e704d7 --- /dev/null +++ b/src/cmd/link/internal/ld/testdata/stackcheck/main.go @@ -0,0 +1,20 @@ +// 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() { asmMain() } + +func asmMain() + +func startSelf() + +func startChain() +func chain0() +func chain1() +func chain2() +func chainEnd() + +func startRec() +func startRec0() diff --git a/src/cmd/link/internal/ld/testdata/stackcheck/main.s b/src/cmd/link/internal/ld/testdata/stackcheck/main.s new file mode 100644 index 0000000000000..10f6a3f4c2f49 --- /dev/null +++ b/src/cmd/link/internal/ld/testdata/stackcheck/main.s @@ -0,0 +1,40 @@ +// 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. + +#define NOSPLIT 7 + +TEXT ·asmMain(SB),0,$0-0 + CALL ·startSelf(SB) + CALL ·startChain(SB) + CALL ·startRec(SB) + RET + +// Test reporting of basic over-the-limit +TEXT ·startSelf(SB),NOSPLIT,$1000-0 + RET + +// Test reporting of multiple over-the-limit chains +TEXT ·startChain(SB),NOSPLIT,$16-0 + CALL ·chain0(SB) + CALL ·chain1(SB) + CALL ·chain2(SB) + RET +TEXT ·chain0(SB),NOSPLIT,$32-0 + CALL ·chainEnd(SB) + RET +TEXT ·chain1(SB),NOSPLIT,$48-0 // Doesn't go over + RET +TEXT ·chain2(SB),NOSPLIT,$64-0 + CALL ·chainEnd(SB) + RET +TEXT ·chainEnd(SB),NOSPLIT,$1000-0 // Should be reported twice + RET + +// Test reporting of rootless recursion +TEXT ·startRec(SB),NOSPLIT,$0-0 + CALL ·startRec0(SB) + RET +TEXT ·startRec0(SB),NOSPLIT,$0-0 + CALL ·startRec(SB) + RET diff --git a/test/nosplit.go b/test/nosplit.go index 7e0fd4e7916e8..9cedb93ec3604 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. From e04280838f34669458d55d6aab812a8c304c40a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Mart=C3=AD?= Date: Tue, 12 Apr 2022 07:11:28 +0100 Subject: [PATCH 132/137] io/ioutil: provide an equivalent for the deprecated ReadDir All APIs in the now-deprecated io/ioutil package have a direct replacement in either the io or os package with the same signature, with the notable exception of ioutil.ReadDir, as os.ReadDir has a slightly different signature with fs.DirEntry rather than fs.FileInfo. New code can easily make use of []fs.DirEntry directly, but existing code may need to continue using []fs.FileInfo for backwards compatibility reasons. For instance, I had a bit of code that exposed the slice as a public API, like: return ioutil.ReadDir(name) It took me a couple of minutes to figure out what the exact equivalent in terms of os.ReadDir would be, and a code sample would have helped. Add one for future reference. For #42026. For #51927. Change-Id: I76d46cd7d68fc609c873821755fdcfc299ffd56c Reviewed-on: https://go-review.googlesource.com/c/go/+/399854 Reviewed-by: Bryan Mills Run-TryBot: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor --- src/io/ioutil/ioutil.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/io/ioutil/ioutil.go b/src/io/ioutil/ioutil.go index 9921c2ae50866..6a1d69172ce62 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 { From 0670afa1b3aaed740816c5ffc9825d154c47d188 Mon Sep 17 00:00:00 2001 From: "Bryan C. Mills" Date: Mon, 18 Apr 2022 10:56:19 -0400 Subject: [PATCH 133/137] doc/go1.19: move the description of the runtime.GOROOT change from 'cmd/go' to 'runtime' Even though the change in the behavior of 'runtime.GOROOT' was not actually due to a change in the runtime package proper, I suspect that users who notice it will look for the release note in that section, not the 'cmd/go' section. Fixes #51461. Change-Id: I271752968d4152a7fdf3e170537e3072bf87ce86 Reviewed-on: https://go-review.googlesource.com/c/go/+/400814 Reviewed-by: Ian Lance Taylor --- doc/go1.19.html | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/doc/go1.19.html b/doc/go1.19.html index 21781c3e3329a..a813d59cb8219 100644 --- a/doc/go1.19.html +++ b/doc/go1.19.html @@ -36,11 +36,6 @@

Go command

-

- Passing the -trimpath flag to go commands now - causes runtime.GOROOT() in the resulting binary to return the - empty string instead of the string "go". -

The -trimpath flag, if set, is now included in the build settings stamped into Go binaries by go build, and can be @@ -137,6 +132,17 @@

Minor changes to the library

+
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

From 689dc17793991746eb27ba4520d45af19a661f98 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 19 Apr 2022 08:55:04 -0700 Subject: [PATCH 134/137] reflect: adjust MapRange allocation test for noopt builder Change-Id: I55899ff0ed2c3c01f24ab1ccf133ce4236049e39 Reviewed-on: https://go-review.googlesource.com/c/go/+/401074 Run-TryBot: Keith Randall Reviewed-by: Joseph Tsai Reviewed-by: Bryan Mills TryBot-Result: Gopher Robot Reviewed-by: Keith Randall --- src/reflect/all_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index f7adf2fa1a6db..f18df4e6c508d 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" @@ -373,8 +374,12 @@ func TestMapIterSet(t *testing.T) { // 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. - if got != 0 { - t.Errorf("wanted 0 alloc, got %d", got) + want := 0 + if strings.HasSuffix(testenv.Builder(), "-noopt") { + want = 1 // no inlining with the noopt builder + } + if got != want { + t.Errorf("wanted %d alloc, got %d", want, got) } } From aa8262d800f0cba2e4d4472a7e344eb60481b0ff Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 19 Apr 2022 10:33:46 -0700 Subject: [PATCH 135/137] reflect: adjust MapRange allocation test for noopt builder, take 2 Change-Id: If2887f84b3d14fac3c059fc5bad4186ec9d69d0f Reviewed-on: https://go-review.googlesource.com/c/go/+/401077 Reviewed-by: Joseph Tsai Run-TryBot: Keith Randall Reviewed-by: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Ian Lance Taylor --- src/reflect/all_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index f18df4e6c508d..a886f9f64abbc 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -364,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() { @@ -375,9 +379,6 @@ func TestMapIterSet(t *testing.T) { // The function is inlineable, so if the local usage does not escape // the *MapIter, it can remain stack allocated. want := 0 - if strings.HasSuffix(testenv.Builder(), "-noopt") { - want = 1 // no inlining with the noopt builder - } if got != want { t.Errorf("wanted %d alloc, got %d", want, got) } From e858c14f916d424b33ad7e2450895b3117a5f727 Mon Sep 17 00:00:00 2001 From: Jorropo Date: Mon, 18 Apr 2022 17:46:43 +0000 Subject: [PATCH 136/137] cmd/compile: more negation related generic SSA rewrite rules The x + (-y) => x - y rule is hitted 75 times while building stage 3 and tools and make the linux-amd64 go binary 0.007% smaller. It transform: NEG AX ADD BX, AX Into: SUB BX, AX Which is 2X faster (assuming this assembly in a vacum). The x ^ (-1) => ^x rule is not hitted in the toolchain. It transforms: XOR $-1, AX Into: NOT AX Which is more compact as it doesn't encode the immediate. Cache usage aside, this does not affect performance (assuming this assembly in a vacum). On my ryzen 3600, with some surrouding code, this randomly might be 2X faster, I guess this has to do with loading the immediate into a temporary register. Combined to an other rule that already exists it also rewrite manual two's complement negation from: XOR $-1, AX INC AX Into: NEG AX Which is 2X faster. The other rules just eliminates similar trivial cases and help constants folding. This should generalise to other architectures. Change-Id: Ia1e51b340622e7ed88e5d856f3b1aa424aa039de GitHub-Last-Rev: ce35ff2efdd8911f1e812806ec41a41e8cabc4c7 GitHub-Pull-Request: golang/go#52395 Reviewed-on: https://go-review.googlesource.com/c/go/+/400714 Reviewed-by: Keith Randall Auto-Submit: Keith Randall Reviewed-by: Keith Randall Run-TryBot: Keith Randall TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- .../compile/internal/ssa/gen/generic.rules | 14 +- .../compile/internal/ssa/rewritegeneric.go | 500 ++++++++++++++++++ 2 files changed, 512 insertions(+), 2 deletions(-) diff --git a/src/cmd/compile/internal/ssa/gen/generic.rules b/src/cmd/compile/internal/ssa/gen/generic.rules index 6dbe9b47d011f..d5cc107fab7b5 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/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index fbf227562a92f..f61b6ca3ececa 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 { From d68a8d0f27bb3599b49cacd119d7ac3202248050 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Fri, 10 Dec 2021 17:23:08 +0100 Subject: [PATCH 137/137] crypto/rand: batch and buffer calls to getrandom/getentropy We're using bufio to batch reads of /dev/urandom to 4k, but we weren't doing the same on newer platforms with getrandom/getentropy. Since the overhead is the same for these -- one syscall -- we should batch reads of these into the same 4k buffer. While we're at it, we can simplify a lot of the constant dispersal. This also adds a new test case to make sure the buffering works as desired. Change-Id: I7297d4aa795c00712e6484b841cef8650c2be4ef Reviewed-on: https://go-review.googlesource.com/c/go/+/370894 Reviewed-by: Filippo Valsorda Run-TryBot: Jason Donenfeld Auto-Submit: Jason Donenfeld TryBot-Result: Gopher Robot Reviewed-by: Ian Lance Taylor --- src/crypto/rand/rand_batched_test.go | 50 +++++++++++++--- src/crypto/rand/rand_dragonfly.go | 9 --- src/crypto/rand/rand_freebsd.go | 9 --- src/crypto/rand/rand_getentropy.go | 20 +------ .../{rand_batched.go => rand_getrandom.go} | 40 +++++++------ src/crypto/rand/rand_linux.go | 14 ----- src/crypto/rand/rand_solaris.go | 10 ---- src/crypto/rand/rand_unix.go | 57 +++++++++++++++++-- 8 files changed, 120 insertions(+), 89 deletions(-) delete mode 100644 src/crypto/rand/rand_dragonfly.go delete mode 100644 src/crypto/rand/rand_freebsd.go rename src/crypto/rand/{rand_batched.go => rand_getrandom.go} (56%) delete mode 100644 src/crypto/rand/rand_linux.go delete mode 100644 src/crypto/rand/rand_solaris.go diff --git a/src/crypto/rand/rand_batched_test.go b/src/crypto/rand/rand_batched_test.go index 28e45aa689ec1..7a981e7892f1d 100644 --- a/src/crypto/rand/rand_batched_test.go +++ b/src/crypto/rand/rand_batched_test.go @@ -2,21 +2,24 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux || freebsd || dragonfly || solaris +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris package rand import ( "bytes" + "encoding/binary" + "errors" + prand "math/rand" "testing" ) func TestBatched(t *testing.T) { - fillBatched := batched(func(p []byte) bool { + fillBatched := batched(func(p []byte) error { for i := range p { p[i] = byte(i) } - return true + return nil }, 5) p := make([]byte, 13) @@ -29,16 +32,49 @@ func TestBatched(t *testing.T) { } } +func TestBatchedBuffering(t *testing.T) { + var prandSeed [8]byte + Read(prandSeed[:]) + prand.Seed(int64(binary.LittleEndian.Uint64(prandSeed[:]))) + + backingStore := make([]byte, 1<<23) + prand.Read(backingStore) + backingMarker := backingStore[:] + output := make([]byte, len(backingStore)) + outputMarker := output[:] + + fillBatched := batched(func(p []byte) error { + n := copy(p, backingMarker) + backingMarker = backingMarker[n:] + return nil + }, 731) + + for len(outputMarker) > 0 { + max := 9200 + if max > len(outputMarker) { + max = len(outputMarker) + } + howMuch := prand.Intn(max + 1) + if !fillBatched(outputMarker[:howMuch]) { + t.Fatal("batched function returned false") + } + outputMarker = outputMarker[howMuch:] + } + if !bytes.Equal(backingStore, output) { + t.Error("incorrect batch result") + } +} + func TestBatchedError(t *testing.T) { - b := batched(func(p []byte) bool { return false }, 5) + b := batched(func(p []byte) error { return errors.New("failure") }, 5) if b(make([]byte, 13)) { - t.Fatal("batched function should have returned false") + t.Fatal("batched function should have returned an error") } } func TestBatchedEmpty(t *testing.T) { - b := batched(func(p []byte) bool { return false }, 5) + b := batched(func(p []byte) error { return errors.New("failure") }, 5) if !b(make([]byte, 0)) { - t.Fatal("empty slice should always return true") + t.Fatal("empty slice should always return successful") } } diff --git a/src/crypto/rand/rand_dragonfly.go b/src/crypto/rand/rand_dragonfly.go deleted file mode 100644 index 8a36fea6cd363..0000000000000 --- a/src/crypto/rand/rand_dragonfly.go +++ /dev/null @@ -1,9 +0,0 @@ -// 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 rand - -// maxGetRandomRead is the maximum number of bytes to ask for in one call to the -// getrandom() syscall. In DragonFlyBSD at most 256 bytes will be returned per call. -const maxGetRandomRead = 1 << 8 diff --git a/src/crypto/rand/rand_freebsd.go b/src/crypto/rand/rand_freebsd.go deleted file mode 100644 index 75f683c386d50..0000000000000 --- a/src/crypto/rand/rand_freebsd.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package rand - -// maxGetRandomRead is the maximum number of bytes to ask for in one call to the -// getrandom() syscall. In FreeBSD at most 256 bytes will be returned per call. -const maxGetRandomRead = 1 << 8 diff --git a/src/crypto/rand/rand_getentropy.go b/src/crypto/rand/rand_getentropy.go index 2bf2f52032443..68f921b0fc3fb 100644 --- a/src/crypto/rand/rand_getentropy.go +++ b/src/crypto/rand/rand_getentropy.go @@ -6,25 +6,9 @@ package rand -import ( - "internal/syscall/unix" -) +import "internal/syscall/unix" func init() { - altGetRandom = getEntropy -} - -func getEntropy(p []byte) (ok bool) { // getentropy(2) returns a maximum of 256 bytes per call - for i := 0; i < len(p); i += 256 { - end := i + 256 - if len(p) < end { - end = len(p) - } - err := unix.GetEntropy(p[i:end]) - if err != nil { - return false - } - } - return true + altGetRandom = batched(unix.GetEntropy, 256) } diff --git a/src/crypto/rand/rand_batched.go b/src/crypto/rand/rand_getrandom.go similarity index 56% rename from src/crypto/rand/rand_batched.go rename to src/crypto/rand/rand_getrandom.go index 3e8e620382920..cb31a5687a526 100644 --- a/src/crypto/rand/rand_batched.go +++ b/src/crypto/rand/rand_getrandom.go @@ -8,25 +8,25 @@ package rand import ( "internal/syscall/unix" + "runtime" + "syscall" ) -// maxGetRandomRead is platform dependent. func init() { - altGetRandom = batched(getRandomBatch, maxGetRandomRead) -} - -// batched returns a function that calls f to populate a []byte by chunking it -// into subslices of, at most, readMax bytes. -func batched(f func([]byte) bool, readMax int) func([]byte) bool { - return func(buf []byte) bool { - for len(buf) > readMax { - if !f(buf[:readMax]) { - return false - } - buf = buf[readMax:] - } - return len(buf) == 0 || f(buf) + var maxGetRandomRead int + switch runtime.GOOS { + case "linux", "android": + // Per the manpage: + // When reading from the urandom source, a maximum of 33554431 bytes + // is returned by a single call to getrandom() on systems where int + // has a size of 32 bits. + maxGetRandomRead = (1 << 25) - 1 + case "freebsd", "dragonfly", "solaris": + maxGetRandomRead = 1 << 8 + default: + panic("no maximum specified for GetRandom") } + altGetRandom = batched(getRandom, maxGetRandomRead) } // If the kernel is too old to support the getrandom syscall(), @@ -36,7 +36,13 @@ func batched(f func([]byte) bool, readMax int) func([]byte) bool { // If the kernel supports the getrandom() syscall, unix.GetRandom will block // until the kernel has sufficient randomness (as we don't use GRND_NONBLOCK). // In this case, unix.GetRandom will not return an error. -func getRandomBatch(p []byte) (ok bool) { +func getRandom(p []byte) error { n, err := unix.GetRandom(p, 0) - return n == len(p) && err == nil + if err != nil { + return err + } + if n != len(p) { + return syscall.EIO + } + return nil } diff --git a/src/crypto/rand/rand_linux.go b/src/crypto/rand/rand_linux.go deleted file mode 100644 index 29809f62c8108..0000000000000 --- a/src/crypto/rand/rand_linux.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2014 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 rand - -// maxGetRandomRead is the maximum number of bytes to ask for in one call to the -// getrandom() syscall. In linux at most 2^25-1 bytes will be returned per call. -// From the manpage -// -// - When reading from the urandom source, a maximum of 33554431 bytes -// is returned by a single call to getrandom() on systems where int -// has a size of 32 bits. -const maxGetRandomRead = (1 << 25) - 1 diff --git a/src/crypto/rand/rand_solaris.go b/src/crypto/rand/rand_solaris.go deleted file mode 100644 index bbad0fe557ec8..0000000000000 --- a/src/crypto/rand/rand_solaris.go +++ /dev/null @@ -1,10 +0,0 @@ -// 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 rand - -// maxGetRandomRead is the maximum number of bytes to ask for in one call to the -// getrandom() syscall. Across all the Solaris platforms, 256 bytes is the -// lowest number of bytes returned atomically per call. -const maxGetRandomRead = 1 << 8 diff --git a/src/crypto/rand/rand_unix.go b/src/crypto/rand/rand_unix.go index 58c97649c47eb..2dd4158888ffa 100644 --- a/src/crypto/rand/rand_unix.go +++ b/src/crypto/rand/rand_unix.go @@ -15,7 +15,6 @@ import ( "io" "os" "sync" - "sync/atomic" "syscall" "time" ) @@ -30,19 +29,69 @@ func init() { type reader struct { f io.Reader mu sync.Mutex - used int32 // atomic; whether this reader has been used + used bool // whether this reader has been used } // altGetRandom if non-nil specifies an OS-specific function to get // urandom-style randomness. var altGetRandom func([]byte) (ok bool) +// batched returns a function that calls f to populate a []byte by chunking it +// into subslices of, at most, readMax bytes, buffering min(readMax, 4096) +// bytes at a time. +func batched(f func([]byte) error, readMax int) func([]byte) bool { + bufferSize := 4096 + if bufferSize > readMax { + bufferSize = readMax + } + fullBuffer := make([]byte, bufferSize) + var buf []byte + return func(out []byte) bool { + // First we copy any amount remaining in the buffer. + n := copy(out, buf) + out, buf = out[n:], buf[n:] + + // Then, if we're requesting more than the buffer size, + // generate directly into the output, chunked by readMax. + for len(out) >= len(fullBuffer) { + read := len(out) - (len(out) % len(fullBuffer)) + if read > readMax { + read = readMax + } + if f(out[:read]) != nil { + return false + } + out = out[read:] + } + + // If there's a partial block left over, fill the buffer, + // and copy in the remainder. + if len(out) > 0 { + if f(fullBuffer[:]) != nil { + return false + } + buf = fullBuffer[:] + n = copy(out, buf) + out, buf = out[n:], buf[n:] + } + + if len(out) > 0 { + panic("crypto/rand batching failed to fill buffer") + } + + return true + } +} + func warnBlocked() { println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel") } func (r *reader) Read(b []byte) (n int, err error) { - if atomic.CompareAndSwapInt32(&r.used, 0, 1) { + r.mu.Lock() + defer r.mu.Unlock() + if !r.used { + r.used = true // First use of randomness. Start timer to warn about // being blocked on entropy not being available. t := time.AfterFunc(time.Minute, warnBlocked) @@ -51,8 +100,6 @@ func (r *reader) Read(b []byte) (n int, err error) { if altGetRandom != nil && altGetRandom(b) { return len(b), nil } - r.mu.Lock() - defer r.mu.Unlock() if r.f == nil { f, err := os.Open(urandomDevice) if err != nil {