diff --git a/api/go1.25.txt b/api/go1.25.txt index cb3900bf46..d50d19545f 100644 --- a/api/go1.25.txt +++ b/api/go1.25.txt @@ -101,6 +101,7 @@ pkg testing, method (*F) Output() io.Writer #59928 pkg testing, method (*T) Attr(string, string) #43936 pkg testing, method (*T) Output() io.Writer #59928 pkg testing, type TB interface, Attr(string, string) #43936 +pkg testing, type TB interface, Output() io.Writer #59928 pkg testing/fstest, method (MapFS) Lstat(string) (fs.FileInfo, error) #49580 pkg testing/fstest, method (MapFS) ReadLink(string) (string, error) #49580 pkg testing/synctest, func Test(*testing.T, func(*testing.T)) #67434 diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index 8bba604214..459c2498fc 100644 --- a/src/cmd/compile/internal/inline/inl.go +++ b/src/cmd/compile/internal/inline/inl.go @@ -785,7 +785,7 @@ func inlineCallCheck(callerfn *ir.Func, call *ir.CallExpr) (bool, bool) { if call.Op() != ir.OCALLFUNC { return false, false } - if call.GoDefer { + if call.GoDefer || call.NoInline { return false, false } diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go index a884c1bc73..954cc306fc 100644 --- a/src/cmd/compile/internal/inline/interleaved/interleaved.go +++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go @@ -279,7 +279,12 @@ func (s *inlClosureState) mark(n ir.Node) ir.Node { ok := match(n) - ir.EditChildren(n, s.mark) + // can't wrap TailCall's child into ParenExpr + if t, ok := n.(*ir.TailCallStmt); ok { + ir.EditChildren(t.Call, s.mark) + } else { + ir.EditChildren(n, s.mark) + } if ok { if p == nil { @@ -317,23 +322,6 @@ func (s *inlClosureState) unparenthesize() { n = paren.X } ir.EditChildren(n, unparen) - // special case for tail calls: if the tail call was inlined, transform - // the tail call to a return stmt if the inlined function was not void, - // otherwise replace it with the inlined expression followed by a return. - if tail, ok := n.(*ir.TailCallStmt); ok { - if inl, done := tail.Call.(*ir.InlinedCallExpr); done { - if len(inl.ReturnVars) != 0 { - ret := ir.NewReturnStmt(tail.Pos(), []ir.Node{inl}) - if len(inl.ReturnVars) > 1 { - typecheck.RewriteMultiValueCall(ret, inl) - } - n = ret - } else { - ret := ir.NewReturnStmt(tail.Pos(), nil) - n = ir.NewBlockStmt(tail.Pos(), []ir.Node{inl, ret}) - } - } - } return n } ir.EditChildren(s.fn, unparen) @@ -370,9 +358,11 @@ func (s *inlClosureState) fixpoint() bool { } func match(n ir.Node) bool { - switch n.(type) { + switch n := n.(type) { case *ir.CallExpr: return true + case *ir.TailCallStmt: + n.Call.NoInline = true // can't inline yet } return false } diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 8f7df4b458..702adfdd84 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -191,6 +191,7 @@ type CallExpr struct { KeepAlive []*Name // vars to be kept alive until call returns IsDDD bool GoDefer bool // whether this call is part of a go or defer statement + NoInline bool // whether this call must not be inlined } func NewCallExpr(pos src.XPos, op Op, fun Node, args []Node) *CallExpr { diff --git a/src/cmd/compile/internal/ir/node_gen.go b/src/cmd/compile/internal/ir/node_gen.go index e67b5ba0bc..026acbf9dd 100644 --- a/src/cmd/compile/internal/ir/node_gen.go +++ b/src/cmd/compile/internal/ir/node_gen.go @@ -2202,13 +2202,13 @@ func (n *TailCallStmt) doChildrenWithHidden(do func(Node) bool) bool { func (n *TailCallStmt) editChildren(edit func(Node) Node) { editNodes(n.init, edit) if n.Call != nil { - n.Call = edit(n.Call) + n.Call = edit(n.Call).(*CallExpr) } } func (n *TailCallStmt) editChildrenWithHidden(edit func(Node) Node) { editNodes(n.init, edit) if n.Call != nil { - n.Call = edit(n.Call) + n.Call = edit(n.Call).(*CallExpr) } } diff --git a/src/cmd/compile/internal/ir/stmt.go b/src/cmd/compile/internal/ir/stmt.go index ae7fb2080b..0801ecdd9e 100644 --- a/src/cmd/compile/internal/ir/stmt.go +++ b/src/cmd/compile/internal/ir/stmt.go @@ -479,7 +479,7 @@ func NewSwitchStmt(pos src.XPos, tag Node, cases []*CaseClause) *SwitchStmt { // code generation to jump directly to another function entirely. type TailCallStmt struct { miniStmt - Call Node // the underlying call + Call *CallExpr // the underlying call } func NewTailCallStmt(pos src.XPos, call *CallExpr) *TailCallStmt { diff --git a/src/cmd/compile/internal/noder/doc.go b/src/cmd/compile/internal/noder/doc.go index baf7c67463..a5d5533168 100644 --- a/src/cmd/compile/internal/noder/doc.go +++ b/src/cmd/compile/internal/noder/doc.go @@ -25,9 +25,9 @@ determines its index in the series. SectionMeta SectionPosBase SectionPkg - SectionName // TODO(markfreeman) Define. - SectionType // TODO(markfreeman) Define. - SectionObj // TODO(markfreeman) Define. + SectionName + SectionType + SectionObj SectionObjExt // TODO(markfreeman) Define. SectionObjDict // TODO(markfreeman) Define. SectionBody // TODO(markfreeman) Define. @@ -35,9 +35,11 @@ determines its index in the series. # Sections A section is a series of elements of a type determined by the section's -kind. Go constructs are mapped onto (potentially multiple) elements. -Elements are accessed using an index relative to the start of the -section. +kind. Go constructs are mapped onto one or more elements with possibly +different types; in that case, the elements are in different sections. + +Elements are accessed using an element index relative to the start of +the section. RelElemIdx = Uint64 . @@ -47,6 +49,9 @@ outside the string section access string values by reference. SectionString = { String } . +Note that despite being an element, a string does not begin with a +reference table. + ## Meta Section The meta section provides fundamental information for a package. It contains exactly two elements — a public root and a private root. @@ -135,6 +140,97 @@ Note, a PkgRef is *not* equivalent to Ref[Pkg] due to an extra marker. Ref[Pkg] . +## Type Section +The type section is a series of type definition elements. + + SectionType = { TypeDef } . + +A type definition can be in one of several formats, which are identified +by their TypeSpec code. + + TypeDef = RefTable + [ Sync ] + [ Sync ] + Uint64 // denotes which TypeSpec to use + TypeSpec + . + + TypeSpec = TypeSpecBasic // TODO(markfreeman): Define. + | TypeSpecNamed // TODO(markfreeman): Define. + | TypeSpecPointer // TODO(markfreeman): Define. + | TypeSpecSlice // TODO(markfreeman): Define. + | TypeSpecArray // TODO(markfreeman): Define. + | TypeSpecChan // TODO(markfreeman): Define. + | TypeSpecMap // TODO(markfreeman): Define. + | TypeSpecSignature // TODO(markfreeman): Define. + | TypeSpecStruct // TODO(markfreeman): Define. + | TypeSpecInterface // TODO(markfreeman): Define. + | TypeSpecUnion // TODO(markfreeman): Define. + | TypeSpecTypeParam // TODO(markfreeman): Define. + . + +// TODO(markfreeman): Document the reader dictionary once we understand it more. +To use a type elsewhere, a TypeUse is encoded. + + TypeUse = [ Sync ] + Bool // whether it is a derived type + [ Uint64 ] // if derived, an index into the reader dictionary + [ Ref[TypeDef] ] // else, a reference to the type + . + +## Object Sections +Information about an object (e.g. variable, function, type name, etc.) +is split into multiple elements in different sections. Those elements +have the same section-relative element index. + +### Name Section +The name section holds a series of names. + + SectionName = { Name } . + +Names are elements holding qualified identifiers and type information +for objects. + + Name = RefTable + [ Sync ] + [ Sync ] + PkgRef // the object's package + StringRef // the object's package-local name + [ Sync ] + Uint64 // the object's type (e.g. Var, Func, etc.) + . + +### Definition Section +The definition section holds definitions for objects defined by the target +package; it does not contain definitions for imported objects. + + SectionObj = { ObjectDef } . + +Object definitions can be in one of several formats. To determine the correct +format, the name section must be referenced; it contains a code indicating +the object's type. + + ObjectDef = RefTable + [ Sync ] + ObjectSpec + . + + ObjectSpec = ObjectSpecConst // TODO(markfreeman) Define. + | ObjectSpecFunc // TODO(markfreeman) Define. + | ObjectSpecAlias // TODO(markfreeman) Define. + | ObjectSpecNamedType // TODO(markfreeman) Define. + | ObjectSpecVar // TODO(markfreeman) Define. + . + +To use an object definition elsewhere, an ObjectUse is encoded. + + ObjectUse = [ Sync ] + [ Bool ] + Ref[ObjectDef] + Uint64 // the number of type arguments + { TypeUse } // references to the type arguments + . + # References A reference table precedes every element. Each entry in the table contains a (section, index) pair denoting the location of the @@ -152,10 +248,15 @@ referenced element. Elements encode references to other elements as an index in the reference table — not the location of the referenced element directly. - // TODO(markfreeman): Rename to RefUse. - UseReloc = [ Sync ] - RelElemIdx - . + RefTableIdx = Uint64 . + +To do this, the Ref[T] primitive is used as below; note that this is +the same shape as provided by package pkgbits, just with new +interpretation applied. + + Ref[T] = [ Sync ] + RefTableIdx // the Uint64 + . # Primitives Primitive encoding is handled separately by the pkgbits package. Check diff --git a/src/cmd/compile/internal/noder/noder.go b/src/cmd/compile/internal/noder/noder.go index 77daf9eda5..79a9078333 100644 --- a/src/cmd/compile/internal/noder/noder.go +++ b/src/cmd/compile/internal/noder/noder.go @@ -458,7 +458,7 @@ func Renameinit() *types.Sym { func checkEmbed(decl *syntax.VarDecl, haveEmbed, withinFunc bool) error { switch { case !haveEmbed: - return errors.New("go:embed only allowed in Go files that import \"embed\"") + return errors.New("go:embed requires import \"embed\" (or import _ \"embed\", if package is not used)") case len(decl.NameList) > 1: return errors.New("go:embed cannot apply to multiple vars") case decl.Values != nil: diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index c854619897..2c3f7161a8 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -3996,11 +3996,12 @@ func addTailCall(pos src.XPos, fn *ir.Func, recv ir.Node, method *types.Field) { if recv.Type() != nil && recv.Type().IsPtr() && method.Type.Recv().Type.IsPtr() && method.Embedded != 0 && !types.IsInterfaceMethod(method.Type) && + !unifiedHaveInlineBody(ir.MethodExprName(dot).Func) && !(base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink) { if base.Debug.TailCall != 0 { base.WarnfAt(fn.Nname.Type().Recv().Type.Elem().Pos(), "tail call emitted for the method %v wrapper", method.Nname) } - // Prefer OTAILCALL to reduce code size (the called method can be inlined). + // Prefer OTAILCALL to reduce code size (except the case when the called method can be inlined). fn.Body.Append(ir.NewTailCallStmt(pos, call)) return } diff --git a/src/cmd/compile/internal/ssagen/ssa.go b/src/cmd/compile/internal/ssagen/ssa.go index 542ad823ab..e241e9b9bc 100644 --- a/src/cmd/compile/internal/ssagen/ssa.go +++ b/src/cmd/compile/internal/ssagen/ssa.go @@ -1921,7 +1921,7 @@ func (s *state) stmt(n ir.Node) { case ir.OTAILCALL: n := n.(*ir.TailCallStmt) - s.callResult(n.Call.(*ir.CallExpr), callTail) + s.callResult(n.Call, callTail) call := s.mem() b := s.endBlock() b.Kind = ssa.BlockRetJmp // could use BlockExit. BlockRetJmp is mostly for clarity. diff --git a/src/cmd/compile/internal/typecheck/stmt.go b/src/cmd/compile/internal/typecheck/stmt.go index bb3a29dd13..8d792485d8 100644 --- a/src/cmd/compile/internal/typecheck/stmt.go +++ b/src/cmd/compile/internal/typecheck/stmt.go @@ -137,7 +137,7 @@ assignOK: if cr > len(rhs) { stmt := stmt.(*ir.AssignListStmt) stmt.SetOp(ir.OAS2FUNC) - r := rhs[0] + r := rhs[0].(*ir.CallExpr) rtyp := r.Type() mismatched := false diff --git a/src/cmd/compile/internal/types2/stdlib_test.go b/src/cmd/compile/internal/types2/stdlib_test.go index 4de698baaf..35e15d814d 100644 --- a/src/cmd/compile/internal/types2/stdlib_test.go +++ b/src/cmd/compile/internal/types2/stdlib_test.go @@ -333,6 +333,7 @@ func TestStdFixed(t *testing.T) { "issue56103.go", // anonymous interface cycles; will be a type checker error in 1.22 "issue52697.go", // types2 does not have constraints on stack size "issue73309.go", // this test requires GODEBUG=gotypesalias=1 + "issue73309b.go", // this test requires GODEBUG=gotypesalias=1 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms. // However, types2 does not know about build constraints. diff --git a/src/cmd/compile/internal/walk/stmt.go b/src/cmd/compile/internal/walk/stmt.go index 2e5ca3180f..b2a226e078 100644 --- a/src/cmd/compile/internal/walk/stmt.go +++ b/src/cmd/compile/internal/walk/stmt.go @@ -139,8 +139,7 @@ func walkStmt(n ir.Node) ir.Node { n := n.(*ir.TailCallStmt) var init ir.Nodes - call := n.Call.(*ir.CallExpr) - call.Fun = walkExpr(call.Fun, &init) + n.Call.Fun = walkExpr(n.Call.Fun, &init) if len(init) > 0 { init.Append(n) diff --git a/src/cmd/dist/build.go b/src/cmd/dist/build.go index 23deece6fb..832aa3c244 100644 --- a/src/cmd/dist/build.go +++ b/src/cmd/dist/build.go @@ -1516,7 +1516,7 @@ func cmdbootstrap() { } // To recap, so far we have built the new toolchain - // (cmd/asm, cmd/cgo, cmd/compile, cmd/link) + // (cmd/asm, cmd/cgo, cmd/compile, cmd/link, cmd/preprofile) // using the Go bootstrap toolchain and go command. // Then we built the new go command (as go_bootstrap) // using the new toolchain and our own build logic (above). @@ -1589,6 +1589,18 @@ func cmdbootstrap() { os.Setenv("GOCACHE", oldgocache) } + // Keep in sync with binExes in cmd/distpack/pack.go. + binExesIncludedInDistpack := []string{"cmd/go", "cmd/gofmt"} + + // Keep in sync with the filter in cmd/distpack/pack.go. + toolsIncludedInDistpack := []string{"cmd/asm", "cmd/cgo", "cmd/compile", "cmd/cover", "cmd/link", "cmd/preprofile", "cmd/vet"} + + // We could install all tools in "cmd", but is unnecessary because we will + // remove them in distpack, so instead install the tools that will actually + // be included in distpack, which is a superset of toolchain. Not installing + // the tools will help us test what happens when the tools aren't present. + toolsToInstall := slices.Concat(binExesIncludedInDistpack, toolsIncludedInDistpack) + if goos == oldgoos && goarch == oldgoarch { // Common case - not setting up for cross-compilation. timelog("build", "toolchain") @@ -1605,9 +1617,9 @@ func cmdbootstrap() { xprintf("\n") } xprintf("Building commands for host, %s/%s.\n", goos, goarch) - goInstall(toolenv(), goBootstrap, "cmd") - checkNotStale(toolenv(), goBootstrap, "cmd") - checkNotStale(toolenv(), gorootBinGo, "cmd") + goInstall(toolenv(), goBootstrap, toolsToInstall...) + checkNotStale(toolenv(), goBootstrap, toolsToInstall...) + checkNotStale(toolenv(), gorootBinGo, toolsToInstall...) timelog("build", "target toolchain") if vflag > 0 { @@ -1621,12 +1633,12 @@ func cmdbootstrap() { xprintf("Building packages and commands for target, %s/%s.\n", goos, goarch) } goInstall(nil, goBootstrap, "std") - goInstall(toolenv(), goBootstrap, "cmd") + goInstall(toolenv(), goBootstrap, toolsToInstall...) checkNotStale(toolenv(), goBootstrap, toolchain...) checkNotStale(nil, goBootstrap, "std") - checkNotStale(toolenv(), goBootstrap, "cmd") + checkNotStale(toolenv(), goBootstrap, toolsToInstall...) checkNotStale(nil, gorootBinGo, "std") - checkNotStale(toolenv(), gorootBinGo, "cmd") + checkNotStale(toolenv(), gorootBinGo, toolsToInstall...) if debug { run("", ShowOutput|CheckExit, pathf("%s/compile", tooldir), "-V=full") checkNotStale(toolenv(), goBootstrap, toolchain...) @@ -1677,7 +1689,7 @@ func cmdbootstrap() { if distpack { xprintf("Packaging archives for %s/%s.\n", goos, goarch) - run("", ShowOutput|CheckExit, pathf("%s/distpack", tooldir)) + run("", ShowOutput|CheckExit, gorootBinGo, "tool", "distpack") } // Print trailing banner unless instructed otherwise. diff --git a/src/cmd/distpack/pack.go b/src/cmd/distpack/pack.go index 4f14210e5f..27f73e593c 100644 --- a/src/cmd/distpack/pack.go +++ b/src/cmd/distpack/pack.go @@ -171,6 +171,7 @@ func main() { switch strings.TrimSuffix(path.Base(name), ".exe") { default: return false + // Keep in sync with toolsIncludedInDistpack in cmd/dist/build.go. case "asm", "cgo", "compile", "cover", "link", "preprofile", "vet": } } @@ -179,6 +180,7 @@ func main() { // Add go and gofmt to bin, using cross-compiled binaries // if this is a cross-compiled distribution. + // Keep in sync with binExesIncludedInDistpack in cmd/dist/build.go. binExes := []string{ "go", "gofmt", diff --git a/src/cmd/doc/doc.go b/src/cmd/doc/doc.go new file mode 100644 index 0000000000..ac15ad9c7d --- /dev/null +++ b/src/cmd/doc/doc.go @@ -0,0 +1,55 @@ +// Copyright 2015 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. + +// 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 +// first argument must be a full package path. This is similar to the +// command-line usage for the godoc command. +// +// For commands, unless the -cmd flag is present "go doc command" +// shows only the package-level docs for the package. +// +// The -src flag causes doc to print the full source code for the symbol, such +// as the body of a struct, function or method. +// +// The -all flag causes doc to print all documentation for the package and +// all its visible symbols. The argument must identify a package. +// +// For complete documentation, run "go help doc". +package main + +import ( + "cmd/internal/doc" + "cmd/internal/telemetry/counter" + "os" +) + +func main() { + counter.Open() + counter.Inc("doc/invocations") + doc.Main(os.Args[1:]) +} diff --git a/src/cmd/go/go_test.go b/src/cmd/go/go_test.go index 83323aeaad..3e691abe41 100644 --- a/src/cmd/go/go_test.go +++ b/src/cmd/go/go_test.go @@ -1093,10 +1093,10 @@ func TestGoListTest(t *testing.T) { tg.grepStdoutNot(`^testing \[bytes.test\]$`, "unexpected test copy of testing") tg.grepStdoutNot(`^testing$`, "unexpected real copy of testing") - tg.run("list", "-test", "cmd/buildid", "cmd/doc") + tg.run("list", "-test", "cmd/buildid", "cmd/gofmt") tg.grepStdout(`^cmd/buildid$`, "missing cmd/buildid") - tg.grepStdout(`^cmd/doc$`, "missing cmd/doc") - tg.grepStdout(`^cmd/doc\.test$`, "missing cmd/doc test") + tg.grepStdout(`^cmd/gofmt$`, "missing cmd/gofmt") + tg.grepStdout(`^cmd/gofmt\.test$`, "missing cmd/gofmt test") tg.grepStdoutNot(`^cmd/buildid\.test$`, "unexpected cmd/buildid test") tg.grepStdoutNot(`^testing`, "unexpected testing") diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go index d583447cf6..a4edd854f1 100644 --- a/src/cmd/go/internal/cfg/cfg.go +++ b/src/cmd/go/internal/cfg/cfg.go @@ -227,6 +227,8 @@ func ForceHost() { // Recompute the build context using Goos and Goarch to // set the correct value for ctx.CgoEnabled. BuildContext = defaultContext() + // Call SetGOROOT to properly set the GOROOT on the new context. + SetGOROOT(Getenv("GOROOT"), false) // Recompute experiments: the settings determined depend on GOOS and GOARCH. // This will also update the BuildContext's tool tags to include the new // experiment tags. diff --git a/src/cmd/go/internal/doc/doc.go b/src/cmd/go/internal/doc/doc.go index 7dfa652e15..131da81495 100644 --- a/src/cmd/go/internal/doc/doc.go +++ b/src/cmd/go/internal/doc/doc.go @@ -2,17 +2,15 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !cmd_go_bootstrap + // Package doc implements the “go doc” command. package doc import ( "cmd/go/internal/base" - "cmd/go/internal/cfg" + "cmd/internal/doc" "context" - "errors" - "os" - "os/exec" - "path/filepath" ) var CmdDoc = &base.Command{ @@ -134,13 +132,5 @@ Flags: } func runDoc(ctx context.Context, cmd *base.Command, args []string) { - base.StartSigHandlers() - err := base.RunErr(cfg.BuildToolexec, filepath.Join(cfg.GOROOTbin, "go"), "tool", "doc", args) - if err != nil { - var ee *exec.ExitError - if errors.As(err, &ee) { - os.Exit(ee.ExitCode()) - } - base.Error(err) - } + doc.Main(args) } diff --git a/src/cmd/go/internal/doc/doc_bootstrap.go b/src/cmd/go/internal/doc/doc_bootstrap.go new file mode 100644 index 0000000000..8be95dc9a6 --- /dev/null +++ b/src/cmd/go/internal/doc/doc_bootstrap.go @@ -0,0 +1,13 @@ +// Copyright 2025 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 cmd_go_bootstrap + +// Don't build cmd/doc into go_bootstrap because it depends on net. + +package doc + +import "cmd/go/internal/base" + +var CmdDoc = &base.Command{} diff --git a/src/cmd/doc/dirs.go b/src/cmd/internal/doc/dirs.go similarity index 99% rename from src/cmd/doc/dirs.go rename to src/cmd/internal/doc/dirs.go index 60ad6d30e6..8b1670f61c 100644 --- a/src/cmd/doc/dirs.go +++ b/src/cmd/internal/doc/dirs.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 main +package doc import ( "bytes" diff --git a/src/cmd/doc/doc_test.go b/src/cmd/internal/doc/doc_test.go similarity index 99% rename from src/cmd/doc/doc_test.go rename to src/cmd/internal/doc/doc_test.go index 3b383bdd78..bccace40c0 100644 --- a/src/cmd/doc/doc_test.go +++ b/src/cmd/internal/doc/doc_test.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 main +package doc import ( "bytes" @@ -90,7 +90,7 @@ type test struct { no []string // Regular expressions that should not match. } -const p = "cmd/doc/testdata" +const p = "cmd/internal/doc/testdata" var tests = []test{ // Sanity check. @@ -105,7 +105,7 @@ var tests = []test{ { "package clause", []string{p}, - []string{`package pkg.*cmd/doc/testdata`}, + []string{`package pkg.*cmd/internal/doc/testdata`}, nil, }, diff --git a/src/cmd/doc/main.go b/src/cmd/internal/doc/main.go similarity index 89% rename from src/cmd/doc/main.go rename to src/cmd/internal/doc/main.go index 490337a0b4..a19f36e1bd 100644 --- a/src/cmd/doc/main.go +++ b/src/cmd/internal/doc/main.go @@ -2,45 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// 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 -// first argument must be a full package path. This is similar to the -// command-line usage for the godoc command. -// -// For commands, unless the -cmd flag is present "go doc command" -// shows only the package-level docs for the package. -// -// The -src flag causes doc to print the full source code for the symbol, such -// as the body of a struct, function or method. -// -// The -all flag causes doc to print all documentation for the package and -// all its visible symbols. The argument must identify a package. -// -// For complete documentation, run "go help doc". -package main +// Package doc provides the implementation of the "go doc" subcommand and cmd/doc. +package doc import ( "bytes" @@ -74,7 +37,7 @@ var ( ) // usage is a replacement usage function for the flags package. -func usage() { +func usage(flagSet *flag.FlagSet) { fmt.Fprintf(os.Stderr, "Usage of [go] doc:\n") fmt.Fprintf(os.Stderr, "\tgo doc\n") fmt.Fprintf(os.Stderr, "\tgo doc \n") @@ -85,16 +48,17 @@ func usage() { fmt.Fprintf(os.Stderr, "For more information run\n") fmt.Fprintf(os.Stderr, "\tgo help doc\n\n") fmt.Fprintf(os.Stderr, "Flags:\n") - flag.PrintDefaults() + flagSet.PrintDefaults() os.Exit(2) } -func main() { +// Main is the entry point, invoked both by go doc and cmd/doc. +func Main(args []string) { log.SetFlags(0) log.SetPrefix("doc: ") - counter.Open() dirsInit() - err := do(os.Stdout, flag.CommandLine, os.Args[1:]) + var flagSet flag.FlagSet + err := do(os.Stdout, &flagSet, args) if err != nil { log.Fatal(err) } @@ -102,7 +66,7 @@ func main() { // do is the workhorse, broken out of main to make testing easier. func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { - flagSet.Usage = usage + flagSet.Usage = func() { usage(flagSet) } unexported = false matchCase = false flagSet.StringVar(&chdir, "C", "", "change to `dir` before running command") @@ -114,7 +78,6 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { flagSet.BoolVar(&short, "short", false, "one-line representation for each symbol") flagSet.BoolVar(&serveHTTP, "http", false, "serve HTML docs over HTTP") flagSet.Parse(args) - counter.Inc("doc/invocations") counter.CountFlags("doc/flag:", *flag.CommandLine) if chdir != "" { if err := os.Chdir(chdir); err != nil { @@ -151,7 +114,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { // Loop until something is printed. dirs.Reset() for i := 0; ; i++ { - buildPackage, userPath, sym, more := parseArgs(flagSet.Args()) + buildPackage, userPath, sym, more := parseArgs(flagSet, flagSet.Args()) if i > 0 && !more { // Ignore the "more" bit on the first iteration. return failMessage(paths, symbol, method) } @@ -165,7 +128,7 @@ func do(writer io.Writer, flagSet *flag.FlagSet, args []string) (err error) { unexported = true } - symbol, method = parseSymbol(sym) + symbol, method = parseSymbol(flagSet, sym) pkg := parsePackage(writer, buildPackage, userPath) paths = append(paths, pkg.prettyPath()) @@ -338,7 +301,7 @@ func failMessage(paths []string, symbol, method string) error { // and there may be more matches. For example, if the argument // is rand.Float64, we must scan both crypto/rand and math/rand // to find the symbol, and the first call will return crypto/rand, true. -func parseArgs(args []string) (pkg *build.Package, path, symbol string, more bool) { +func parseArgs(flagSet *flag.FlagSet, args []string) (pkg *build.Package, path, symbol string, more bool) { wd, err := os.Getwd() if err != nil { log.Fatal(err) @@ -356,7 +319,7 @@ func parseArgs(args []string) (pkg *build.Package, path, symbol string, more boo } switch len(args) { default: - usage() + usage(flagSet) case 1: // Done below. case 2: @@ -499,7 +462,7 @@ func importDir(dir string) *build.Package { // parseSymbol breaks str apart into a symbol and method. // Both may be missing or the method may be missing. // If present, each must be a valid Go identifier. -func parseSymbol(str string) (symbol, method string) { +func parseSymbol(flagSet *flag.FlagSet, str string) (symbol, method string) { if str == "" { return } @@ -510,7 +473,7 @@ func parseSymbol(str string) (symbol, method string) { method = elem[1] default: log.Printf("too many periods in symbol specification") - usage() + usage(flagSet) } symbol = elem[0] return diff --git a/src/cmd/doc/pkg.go b/src/cmd/internal/doc/pkg.go similarity index 99% rename from src/cmd/doc/pkg.go rename to src/cmd/internal/doc/pkg.go index a21d8a4688..953b0d9a28 100644 --- a/src/cmd/doc/pkg.go +++ b/src/cmd/internal/doc/pkg.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 main +package doc import ( "bufio" diff --git a/src/cmd/doc/signal_notunix.go b/src/cmd/internal/doc/signal_notunix.go similarity index 95% rename from src/cmd/doc/signal_notunix.go rename to src/cmd/internal/doc/signal_notunix.go index 3b8fa9e080..b91a67eb5f 100644 --- a/src/cmd/doc/signal_notunix.go +++ b/src/cmd/internal/doc/signal_notunix.go @@ -4,7 +4,7 @@ //go:build plan9 || windows -package main +package doc import ( "os" diff --git a/src/cmd/doc/signal_unix.go b/src/cmd/internal/doc/signal_unix.go similarity index 95% rename from src/cmd/doc/signal_unix.go rename to src/cmd/internal/doc/signal_unix.go index 52431c221b..f30612ce9d 100644 --- a/src/cmd/doc/signal_unix.go +++ b/src/cmd/internal/doc/signal_unix.go @@ -4,7 +4,7 @@ //go:build unix || js || wasip1 -package main +package doc import ( "os" diff --git a/src/cmd/doc/testdata/merge/aa.go b/src/cmd/internal/doc/testdata/merge/aa.go similarity index 100% rename from src/cmd/doc/testdata/merge/aa.go rename to src/cmd/internal/doc/testdata/merge/aa.go diff --git a/src/cmd/doc/testdata/merge/bb.go b/src/cmd/internal/doc/testdata/merge/bb.go similarity index 100% rename from src/cmd/doc/testdata/merge/bb.go rename to src/cmd/internal/doc/testdata/merge/bb.go diff --git a/src/cmd/doc/testdata/nested/empty/empty.go b/src/cmd/internal/doc/testdata/nested/empty/empty.go similarity index 100% rename from src/cmd/doc/testdata/nested/empty/empty.go rename to src/cmd/internal/doc/testdata/nested/empty/empty.go diff --git a/src/cmd/doc/testdata/nested/ignore.go b/src/cmd/internal/doc/testdata/nested/ignore.go similarity index 100% rename from src/cmd/doc/testdata/nested/ignore.go rename to src/cmd/internal/doc/testdata/nested/ignore.go diff --git a/src/cmd/doc/testdata/nested/nested/real.go b/src/cmd/internal/doc/testdata/nested/nested/real.go similarity index 100% rename from src/cmd/doc/testdata/nested/nested/real.go rename to src/cmd/internal/doc/testdata/nested/nested/real.go diff --git a/src/cmd/doc/testdata/pkg.go b/src/cmd/internal/doc/testdata/pkg.go similarity index 100% rename from src/cmd/doc/testdata/pkg.go rename to src/cmd/internal/doc/testdata/pkg.go diff --git a/src/cmd/trace/gen.go b/src/cmd/trace/gen.go index 6e4d82799e..4455f83046 100644 --- a/src/cmd/trace/gen.go +++ b/src/cmd/trace/gen.go @@ -215,12 +215,12 @@ func (g *stackSampleGenerator[R]) StackSample(ctx *traceContext, ev *trace.Event // to trace.ResourceNone (the global scope). type globalRangeGenerator struct { ranges map[string]activeRange - seenSync bool + seenSync int } // Sync notifies the generator of an EventSync event. func (g *globalRangeGenerator) Sync() { - g.seenSync = true + g.seenSync++ } // GlobalRange implements a handler for EventRange* events whose Scope.Kind is ResourceNone. @@ -234,8 +234,9 @@ func (g *globalRangeGenerator) GlobalRange(ctx *traceContext, ev *trace.Event) { case trace.EventRangeBegin: g.ranges[r.Name] = activeRange{ev.Time(), ev.Stack()} case trace.EventRangeActive: - // If we've seen a Sync event, then Active events are always redundant. - if !g.seenSync { + // If we've seen at least 2 Sync events (indicating that we're in at least the second + // generation), then Active events are always redundant. + if g.seenSync < 2 { // Otherwise, they extend back to the start of the trace. g.ranges[r.Name] = activeRange{ctx.startTime, ev.Stack()} } @@ -294,12 +295,12 @@ func (g *globalMetricGenerator) GlobalMetric(ctx *traceContext, ev *trace.Event) // ResourceProc. type procRangeGenerator struct { ranges map[trace.Range]activeRange - seenSync bool + seenSync int } // Sync notifies the generator of an EventSync event. func (g *procRangeGenerator) Sync() { - g.seenSync = true + g.seenSync++ } // ProcRange implements a handler for EventRange* events whose Scope.Kind is ResourceProc. @@ -313,8 +314,9 @@ func (g *procRangeGenerator) ProcRange(ctx *traceContext, ev *trace.Event) { case trace.EventRangeBegin: g.ranges[r] = activeRange{ev.Time(), ev.Stack()} case trace.EventRangeActive: - // If we've seen a Sync event, then Active events are always redundant. - if !g.seenSync { + // If we've seen at least 2 Sync events (indicating that we're in at least the second + // generation), then Active events are always redundant. + if g.seenSync < 2 { // Otherwise, they extend back to the start of the trace. g.ranges[r] = activeRange{ctx.startTime, ev.Stack()} } diff --git a/src/go/ast/ast.go b/src/go/ast/ast.go index afe0a750fe..a3dc0c3220 100644 --- a/src/go/ast/ast.go +++ b/src/go/ast/ast.go @@ -4,6 +4,10 @@ // Package ast declares the types used to represent syntax trees for Go // packages. +// +// Syntax trees may be constructed directly, but they are typically +// produced from Go source code by the parser; see the ParseFile +// function in package [go/parser]. package ast import ( diff --git a/src/go/parser/parser.go b/src/go/parser/parser.go index 38ee0de3bb..8a2f95976f 100644 --- a/src/go/parser/parser.go +++ b/src/go/parser/parser.go @@ -2,10 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package parser implements a parser for Go source files. Input may be -// provided in a variety of forms (see the various Parse* functions); the -// output is an abstract syntax tree (AST) representing the Go source. The -// parser is invoked through one of the Parse* functions. +// Package parser implements a parser for Go source files. +// +// The [ParseFile] function reads file input from a string, []byte, or +// io.Reader, and produces an [ast.File] representing the complete +// abstract syntax tree of the file. +// +// The [ParseExprFrom] function reads a single source-level expression and +// produces an [ast.Expr], the syntax tree of the expression. // // The parser accepts a larger language than is syntactically permitted by // the Go spec, for simplicity, and for improved robustness in the presence @@ -13,6 +17,11 @@ // treated like an ordinary parameter list and thus may contain multiple // entries where the spec permits exactly one. Consequently, the corresponding // field in the AST (ast.FuncDecl.Recv) field is not restricted to one entry. +// +// Applications that need to parse one or more complete packages of Go +// source code may find it more convenient not to interact directly +// with the parser but instead to use the Load function in package +// [golang.org/x/tools/go/packages]. package parser import ( diff --git a/src/go/token/position.go b/src/go/token/position.go index f5a43aecef..e9f1f5561b 100644 --- a/src/go/token/position.go +++ b/src/go/token/position.go @@ -429,7 +429,7 @@ func (f *File) Position(p Pos) (pos Position) { type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file - files []*File // list of files in the order added to the set + tree tree // tree of files in ascending base order last atomic.Pointer[File] // cache of last file looked up } @@ -487,7 +487,7 @@ func (s *FileSet) AddFile(filename string, base, size int) *File { } // add the file to the file set s.base = base - s.files = append(s.files, f) + s.tree.add(f) s.last.Store(f) return f } @@ -518,40 +518,9 @@ func (s *FileSet) AddExistingFiles(files ...*File) { s.mutex.Lock() defer s.mutex.Unlock() - // Merge and sort. - newFiles := append(s.files, files...) - slices.SortFunc(newFiles, func(x, y *File) int { - return cmp.Compare(x.Base(), y.Base()) - }) - - // Reject overlapping files. - // Discard adjacent identical files. - out := newFiles[:0] - for i, file := range newFiles { - if i > 0 { - prev := newFiles[i-1] - if file == prev { - continue - } - if prev.Base()+prev.Size()+1 > file.Base() { - panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)", - prev.Name(), prev.Base(), prev.Base()+prev.Size(), - file.Name(), file.Base(), file.Base()+file.Size())) - } - } - out = append(out, file) - } - newFiles = out - - s.files = newFiles - - // Advance base. - if len(newFiles) > 0 { - last := newFiles[len(newFiles)-1] - newBase := last.Base() + last.Size() + 1 - if s.base < newBase { - s.base = newBase - } + for _, f := range files { + s.tree.add(f) + s.base = max(s.base, f.Base()+f.Size()+1) } } @@ -567,39 +536,26 @@ func (s *FileSet) RemoveFile(file *File) { s.mutex.Lock() defer s.mutex.Unlock() - if i := searchFiles(s.files, file.base); i >= 0 && s.files[i] == file { - last := &s.files[len(s.files)-1] - s.files = slices.Delete(s.files, i, i+1) - *last = nil // don't prolong lifetime when popping last element + pn, _ := s.tree.locate(file.key()) + if *pn != nil && (*pn).file == file { + s.tree.delete(pn) } } -// Iterate calls f for the files in the file set in the order they were added -// until f returns false. -func (s *FileSet) Iterate(f func(*File) bool) { - for i := 0; ; i++ { - var file *File - s.mutex.RLock() - if i < len(s.files) { - file = s.files[i] - } +// Iterate calls yield for the files in the file set in ascending Base +// order until yield returns false. +func (s *FileSet) Iterate(yield func(*File) bool) { + s.mutex.RLock() + defer s.mutex.RUnlock() + + // Unlock around user code. + // The iterator is robust to modification by yield. + // Avoid range here, so we can use defer. + s.tree.all()(func(f *File) bool { s.mutex.RUnlock() - if file == nil || !f(file) { - break - } - } -} - -func searchFiles(a []*File, x int) int { - i, found := slices.BinarySearchFunc(a, x, func(a *File, x int) int { - return cmp.Compare(a.base, x) + defer s.mutex.RLock() + return yield(f) }) - if !found { - // We want the File containing x, but if we didn't - // find x then i is the next one. - i-- - } - return i } func (s *FileSet) file(p Pos) *File { @@ -611,16 +567,12 @@ func (s *FileSet) file(p Pos) *File { s.mutex.RLock() defer s.mutex.RUnlock() - // p is not in last file - search all files - if i := searchFiles(s.files, int(p)); i >= 0 { - f := s.files[i] - // f.base <= int(p) by definition of searchFiles - if int(p) <= f.base+f.size { - // Update cache of last file. A race is ok, - // but an exclusive lock causes heavy contention. - s.last.Store(f) - return f - } + pn, _ := s.tree.locate(key{int(p), int(p)}) + if n := *pn; n != nil { + // Update cache of last file. A race is ok, + // but an exclusive lock causes heavy contention. + s.last.Store(n.file) + return n.file } return nil } diff --git a/src/go/token/position_bench_test.go b/src/go/token/position_bench_test.go index 7bb9de8946..add0783832 100644 --- a/src/go/token/position_bench_test.go +++ b/src/go/token/position_bench_test.go @@ -84,15 +84,15 @@ func BenchmarkFileSet_Position(b *testing.B) { } func BenchmarkFileSet_AddExistingFiles(b *testing.B) { + rng := rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64())) + // Create the "universe" of files. fset := token.NewFileSet() var files []*token.File for range 25000 { files = append(files, fset.AddFile("", -1, 10000)) } - rand.Shuffle(len(files), func(i, j int) { - files[i], files[j] = files[j], files[i] - }) + token.Shuffle(rng, files) // choose returns n random files. choose := func(n int) []*token.File { diff --git a/src/go/token/serialize.go b/src/go/token/serialize.go index 04a48d90f8..2c06f8c25c 100644 --- a/src/go/token/serialize.go +++ b/src/go/token/serialize.go @@ -4,6 +4,8 @@ package token +import "slices" + type serializedFile struct { // fields correspond 1:1 to fields with same (lower-case) name in File Name string @@ -27,18 +29,15 @@ func (s *FileSet) Read(decode func(any) error) error { s.mutex.Lock() s.base = ss.Base - files := make([]*File, len(ss.Files)) - for i := 0; i < len(ss.Files); i++ { - f := &ss.Files[i] - files[i] = &File{ + for _, f := range ss.Files { + s.tree.add(&File{ name: f.Name, base: f.Base, size: f.Size, lines: f.Lines, infos: f.Infos, - } + }) } - s.files = files s.last.Store(nil) s.mutex.Unlock() @@ -51,16 +50,16 @@ func (s *FileSet) Write(encode func(any) error) error { s.mutex.Lock() ss.Base = s.base - files := make([]serializedFile, len(s.files)) - for i, f := range s.files { + var files []serializedFile + for f := range s.tree.all() { f.mutex.Lock() - files[i] = serializedFile{ + files = append(files, serializedFile{ Name: f.name, Base: f.base, Size: f.size, - Lines: append([]int(nil), f.lines...), - Infos: append([]lineInfo(nil), f.infos...), - } + Lines: slices.Clone(f.lines), + Infos: slices.Clone(f.infos), + }) f.mutex.Unlock() } ss.Files = files diff --git a/src/go/token/serialize_test.go b/src/go/token/serialize_test.go index 8d9799547a..5b64c58a82 100644 --- a/src/go/token/serialize_test.go +++ b/src/go/token/serialize_test.go @@ -8,6 +8,7 @@ import ( "bytes" "encoding/gob" "fmt" + "slices" "testing" ) @@ -29,12 +30,14 @@ func equal(p, q *FileSet) error { return fmt.Errorf("different bases: %d != %d", p.base, q.base) } - if len(p.files) != len(q.files) { - return fmt.Errorf("different number of files: %d != %d", len(p.files), len(q.files)) + pfiles := slices.Collect(p.tree.all()) + qfiles := slices.Collect(q.tree.all()) + if len(pfiles) != len(qfiles) { + return fmt.Errorf("different number of files: %d != %d", len(pfiles), len(qfiles)) } - for i, f := range p.files { - g := q.files[i] + for i, f := range pfiles { + g := qfiles[i] if f.name != g.name { return fmt.Errorf("different filenames: %q != %q", f.name, g.name) } @@ -88,7 +91,7 @@ func TestSerialization(t *testing.T) { p := NewFileSet() checkSerialize(t, p) // add some files - for i := 0; i < 10; i++ { + for i := range 10 { f := p.AddFile(fmt.Sprintf("file%d", i), p.Base()+i, i*100) checkSerialize(t, p) // add some lines and alternative file infos diff --git a/src/go/token/tree.go b/src/go/token/tree.go new file mode 100644 index 0000000000..2354ad0f33 --- /dev/null +++ b/src/go/token/tree.go @@ -0,0 +1,410 @@ +// Copyright 2025 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 token + +// tree is a self-balancing AVL tree; see +// Lewis & Denenberg, Data Structures and Their Algorithms. +// +// An AVL tree is a binary tree in which the difference between the +// heights of a node's two subtrees--the node's "balance factor"--is +// at most one. It is more strictly balanced than a red/black tree, +// and thus favors lookups at the expense of updates, which is the +// appropriate trade-off for FileSet. +// +// Insertion at a node may cause its ancestors' balance factors to +// temporarily reach ±2, requiring rebalancing of each such ancestor +// by a rotation. +// +// Each key is the pos-end range of a single File. +// All Files in the tree must have disjoint ranges. +// +// The implementation is simplified from Russ Cox's github.com/rsc/omap. + +import ( + "fmt" + "iter" +) + +// A tree is a tree-based ordered map: +// each value is a *File, keyed by its Pos range. +// All map entries cover disjoint ranges. +// +// The zero value of tree is an empty map ready to use. +type tree struct { + root *node +} + +type node struct { + // We use the notation (parent left right) in many comments. + parent *node + left *node + right *node + file *File + key key // = file.key(), but improves locality (25% faster) + balance int32 // at most ±2 + height int32 +} + +// A key represents the Pos range of a File. +type key struct{ start, end int } + +func (f *File) key() key { + return key{f.base, f.base + f.size} +} + +// compareKey reports whether x is before y (-1), +// after y (+1), or overlapping y (0). +// This is a total order so long as all +// files in the tree have disjoint ranges. +// +// All files are separated by at least one unit. +// This allows us to use strict < comparisons. +// Use key{p, p} to search for a zero-width position +// even at the start or end of a file. +func compareKey(x, y key) int { + switch { + case x.end < y.start: + return -1 + case y.end < x.start: + return +1 + } + return 0 +} + +// check asserts that each node's height, subtree, and parent link is +// correct. +func (n *node) check(parent *node) { + const debugging = false + if debugging { + if n == nil { + return + } + if n.parent != parent { + panic("bad parent") + } + n.left.check(n) + n.right.check(n) + n.checkBalance() + } +} + +func (n *node) checkBalance() { + lheight, rheight := n.left.safeHeight(), n.right.safeHeight() + balance := rheight - lheight + if balance != n.balance { + panic("bad node.balance") + } + if !(-2 <= balance && balance <= +2) { + panic(fmt.Sprintf("node.balance out of range: %d", balance)) + } + h := 1 + max(lheight, rheight) + if h != n.height { + panic("bad node.height") + } +} + +// locate returns a pointer to the variable that holds the node +// identified by k, along with its parent, if any. If the key is not +// present, it returns a pointer to the node where the key should be +// inserted by a subsequent call to [tree.set]. +func (t *tree) locate(k key) (pos **node, parent *node) { + pos, x := &t.root, t.root + for x != nil { + sign := compareKey(k, x.key) + if sign < 0 { + pos, x, parent = &x.left, x.left, x + } else if sign > 0 { + pos, x, parent = &x.right, x.right, x + } else { + break + } + } + return pos, parent +} + +// all returns an iterator over the tree t. +// If t is modified during the iteration, +// some files may not be visited. +// No file will be visited multiple times. +func (t *tree) all() iter.Seq[*File] { + return func(yield func(*File) bool) { + if t == nil { + return + } + x := t.root + if x != nil { + for x.left != nil { + x = x.left + } + } + for x != nil && yield(x.file) { + if x.height >= 0 { + // still in tree + x = x.next() + } else { + // deleted + x = t.nextAfter(t.locate(x.key)) + } + } + } +} + +// nextAfter returns the node in the key sequence following +// (pos, parent), a result pair from [tree.locate]. +func (t *tree) nextAfter(pos **node, parent *node) *node { + switch { + case *pos != nil: + return (*pos).next() + case parent == nil: + return nil + case pos == &parent.left: + return parent + default: + return parent.next() + } +} + +func (x *node) next() *node { + if x.right == nil { + for x.parent != nil && x.parent.right == x { + x = x.parent + } + return x.parent + } + x = x.right + for x.left != nil { + x = x.left + } + return x +} + +func (t *tree) setRoot(x *node) { + t.root = x + if x != nil { + x.parent = nil + } +} + +func (x *node) setLeft(y *node) { + x.left = y + if y != nil { + y.parent = x + } +} + +func (x *node) setRight(y *node) { + x.right = y + if y != nil { + y.parent = x + } +} + +func (n *node) safeHeight() int32 { + if n == nil { + return -1 + } + return n.height +} + +func (n *node) update() { + lheight, rheight := n.left.safeHeight(), n.right.safeHeight() + n.height = max(lheight, rheight) + 1 + n.balance = rheight - lheight +} + +func (t *tree) replaceChild(parent, old, new *node) { + switch { + case parent == nil: + if t.root != old { + panic("corrupt tree") + } + t.setRoot(new) + case parent.left == old: + parent.setLeft(new) + case parent.right == old: + parent.setRight(new) + default: + panic("corrupt tree") + } +} + +// rebalanceUp visits each excessively unbalanced ancestor +// of x, restoring balance by rotating it. +// +// x is a node that has just been mutated, and so the height and +// balance of x and its ancestors may be stale, but the children of x +// must be in a valid state. +func (t *tree) rebalanceUp(x *node) { + for x != nil { + h := x.height + x.update() + switch x.balance { + case -2: + if x.left.balance == 1 { + t.rotateLeft(x.left) + } + x = t.rotateRight(x) + + case +2: + if x.right.balance == -1 { + t.rotateRight(x.right) + } + x = t.rotateLeft(x) + } + if x.height == h { + // x's height has not changed, so the height + // and balance of its ancestors have not changed; + // no further rebalancing is required. + return + } + x = x.parent + } +} + +// rotateRight rotates the subtree rooted at node y. +// turning (y (x a b) c) into (x a (y b c)). +func (t *tree) rotateRight(y *node) *node { + // p -> (y (x a b) c) + p := y.parent + x := y.left + b := x.right + + x.checkBalance() + y.checkBalance() + + x.setRight(y) + y.setLeft(b) + t.replaceChild(p, y, x) + + y.update() + x.update() + return x +} + +// rotateLeft rotates the subtree rooted at node x. +// turning (x a (y b c)) into (y (x a b) c). +func (t *tree) rotateLeft(x *node) *node { + // p -> (x a (y b c)) + p := x.parent + y := x.right + b := y.left + + x.checkBalance() + y.checkBalance() + + y.setLeft(x) + x.setRight(b) + t.replaceChild(p, x, y) + + x.update() + y.update() + return y +} + +// add inserts file into the tree, if not present. +// It panics if file overlaps with another. +func (t *tree) add(file *File) { + pos, parent := t.locate(file.key()) + if *pos == nil { + t.set(file, pos, parent) // missing; insert + return + } + if prev := (*pos).file; prev != file { + panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)", + prev.Name(), prev.Base(), prev.Base()+prev.Size(), + file.Name(), file.Base(), file.Base()+file.Size())) + } +} + +// set updates the existing node at (pos, parent) if present, or +// inserts a new node if not, so that it refers to file. +func (t *tree) set(file *File, pos **node, parent *node) { + if x := *pos; x != nil { + // This code path isn't currently needed + // because FileSet never updates an existing entry. + // Remove this assertion if things change. + if true { + panic("unreachable according to current FileSet requirements") + } + x.file = file + return + } + x := &node{file: file, key: file.key(), parent: parent, height: -1} + *pos = x + t.rebalanceUp(x) +} + +// delete deletes the node at pos. +func (t *tree) delete(pos **node) { + t.root.check(nil) + + x := *pos + switch { + case x == nil: + // This code path isn't currently needed because FileSet + // only calls delete after a positive locate. + // Remove this assertion if things change. + if true { + panic("unreachable according to current FileSet requirements") + } + return + + case x.left == nil: + if *pos = x.right; *pos != nil { + (*pos).parent = x.parent + } + t.rebalanceUp(x.parent) + + case x.right == nil: + *pos = x.left + x.left.parent = x.parent + t.rebalanceUp(x.parent) + + default: + t.deleteSwap(pos) + } + + x.balance = -100 + x.parent = nil + x.left = nil + x.right = nil + x.height = -1 + t.root.check(nil) +} + +// deleteSwap deletes a node that has two children by replacing +// it by its in-order successor, then triggers a rebalance. +func (t *tree) deleteSwap(pos **node) { + x := *pos + z := t.deleteMin(&x.right) + + *pos = z + unbalanced := z.parent // lowest potentially unbalanced node + if unbalanced == x { + unbalanced = z // (x a (z nil b)) -> (z a b) + } + z.parent = x.parent + z.height = x.height + z.balance = x.balance + z.setLeft(x.left) + z.setRight(x.right) + + t.rebalanceUp(unbalanced) +} + +// deleteMin updates the subtree rooted at *zpos to delete its minimum +// (leftmost) element, which may be *zpos itself. It returns the +// deleted node. +func (t *tree) deleteMin(zpos **node) (z *node) { + for (*zpos).left != nil { + zpos = &(*zpos).left + } + z = *zpos + *zpos = z.right + if *zpos != nil { + (*zpos).parent = z.parent + } + return z +} diff --git a/src/go/token/tree_test.go b/src/go/token/tree_test.go new file mode 100644 index 0000000000..4bb9f060a1 --- /dev/null +++ b/src/go/token/tree_test.go @@ -0,0 +1,86 @@ +// Copyright 2025 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 token + +import ( + "math/rand/v2" + "slices" + "testing" +) + +// TestTree provides basic coverage of the AVL tree operations. +func TestTree(t *testing.T) { + // Use a reproducible PRNG. + seed1, seed2 := rand.Uint64(), rand.Uint64() + t.Logf("random seeds: %d, %d", seed1, seed2) + rng := rand.New(rand.NewPCG(seed1, seed2)) + + // Create a number of Files of arbitrary size. + files := make([]*File, 500) + var base int + for i := range files { + base++ + size := 1000 + files[i] = &File{base: base, size: size} + base += size + } + + // Add them all to the tree in random order. + var tr tree + { + files2 := slices.Clone(files) + Shuffle(rng, files2) + for _, f := range files2 { + tr.add(f) + } + } + + // Randomly delete a subset of them. + for range 100 { + i := rng.IntN(len(files)) + file := files[i] + if file == nil { + continue // already deleted + } + files[i] = nil + + pn, _ := tr.locate(file.key()) + if (*pn).file != file { + t.Fatalf("locate returned wrong file") + } + tr.delete(pn) + } + + // Check some position lookups within each file. + for _, file := range files { + if file == nil { + continue // deleted + } + for _, pos := range []int{ + file.base, // start + file.base + file.size/2, // midpoint + file.base + file.size, // end + } { + pn, _ := tr.locate(key{pos, pos}) + if (*pn).file != file { + t.Fatalf("lookup %s@%d returned wrong file %s", + file.name, pos, + (*pn).file.name) + } + } + } + + // Check that the sequence is the same. + files = slices.DeleteFunc(files, func(f *File) bool { return f == nil }) + if !slices.Equal(slices.Collect(tr.all()), files) { + t.Fatalf("incorrect tree.all sequence") + } +} + +func Shuffle[T any](rng *rand.Rand, slice []*T) { + rng.Shuffle(len(slice), func(i, j int) { + slice[i], slice[j] = slice[j], slice[i] + }) +} diff --git a/src/go/types/api.go b/src/go/types/api.go index 00fac4a9a0..01fccbd649 100644 --- a/src/go/types/api.go +++ b/src/go/types/api.go @@ -26,6 +26,11 @@ // specification. Use the Types field of [Info] for the results of // type deduction. // +// Applications that need to type-check one or more complete packages +// of Go source code may find it more convenient not to invoke the +// type checker directly but instead to use the Load function in +// package [golang.org/x/tools/go/packages]. +// // For a tutorial, see https://go.dev/s/types-tutorial. package types diff --git a/src/go/types/stdlib_test.go b/src/go/types/stdlib_test.go index 633d7be84d..8e95d23ec3 100644 --- a/src/go/types/stdlib_test.go +++ b/src/go/types/stdlib_test.go @@ -335,6 +335,7 @@ func TestStdFixed(t *testing.T) { "issue56103.go", // anonymous interface cycles; will be a type checker error in 1.22 "issue52697.go", // go/types does not have constraints on stack size "issue73309.go", // this test requires GODEBUG=gotypesalias=1 + "issue73309b.go", // this test requires GODEBUG=gotypesalias=1 // These tests requires runtime/cgo.Incomplete, which is only available on some platforms. // However, go/types does not know about build constraints. diff --git a/src/internal/synctest/synctest_test.go b/src/internal/synctest/synctest_test.go index 2e1393591f..c2f84be736 100644 --- a/src/internal/synctest/synctest_test.go +++ b/src/internal/synctest/synctest_test.go @@ -16,6 +16,7 @@ import ( "strconv" "strings" "sync" + "sync/atomic" "testing" "time" "weak" @@ -218,6 +219,116 @@ func TestTimerFromOutsideBubble(t *testing.T) { } } +// TestTimerNondeterminism verifies that timers firing at the same instant +// don't always fire in exactly the same order. +func TestTimerNondeterminism(t *testing.T) { + synctest.Run(func() { + const iterations = 1000 + var seen1, seen2 bool + for range iterations { + tm1 := time.NewTimer(1) + tm2 := time.NewTimer(1) + select { + case <-tm1.C: + seen1 = true + case <-tm2.C: + seen2 = true + } + if seen1 && seen2 { + return + } + synctest.Wait() + } + t.Errorf("after %v iterations, seen timer1:%v, timer2:%v; want both", iterations, seen1, seen2) + }) +} + +// TestSleepNondeterminism verifies that goroutines sleeping to the same instant +// don't always schedule in exactly the same order. +func TestSleepNondeterminism(t *testing.T) { + synctest.Run(func() { + const iterations = 1000 + var seen1, seen2 bool + for range iterations { + var first atomic.Int32 + go func() { + time.Sleep(1) + first.CompareAndSwap(0, 1) + }() + go func() { + time.Sleep(1) + first.CompareAndSwap(0, 2) + }() + time.Sleep(1) + synctest.Wait() + switch v := first.Load(); v { + case 1: + seen1 = true + case 2: + seen2 = true + default: + t.Fatalf("first = %v, want 1 or 2", v) + } + if seen1 && seen2 { + return + } + synctest.Wait() + } + t.Errorf("after %v iterations, seen goroutine 1:%v, 2:%v; want both", iterations, seen1, seen2) + }) +} + +// TestTimerRunsImmediately verifies that a 0-duration timer sends on its channel +// without waiting for the bubble to block. +func TestTimerRunsImmediately(t *testing.T) { + synctest.Run(func() { + start := time.Now() + tm := time.NewTimer(0) + select { + case got := <-tm.C: + if !got.Equal(start) { + t.Errorf("<-tm.C = %v, want %v", got, start) + } + default: + t.Errorf("0-duration timer channel is not readable; want it to be") + } + }) +} + +// TestTimerRunsLater verifies that reading from a timer's channel receives the +// timer fired, even when that time is in reading from a timer's channel receives the +// time the timer fired, even when that time is in the past. +func TestTimerRanInPast(t *testing.T) { + synctest.Run(func() { + delay := 1 * time.Second + want := time.Now().Add(delay) + tm := time.NewTimer(delay) + time.Sleep(2 * delay) + select { + case got := <-tm.C: + if !got.Equal(want) { + t.Errorf("<-tm.C = %v, want %v", got, want) + } + default: + t.Errorf("0-duration timer channel is not readable; want it to be") + } + }) +} + +// TestAfterFuncRunsImmediately verifies that a 0-duration AfterFunc is scheduled +// without waiting for the bubble to block. +func TestAfterFuncRunsImmediately(t *testing.T) { + synctest.Run(func() { + var b atomic.Bool + time.AfterFunc(0, func() { + b.Store(true) + }) + for !b.Load() { + runtime.Gosched() + } + }) +} + func TestChannelFromOutsideBubble(t *testing.T) { choutside := make(chan struct{}) for _, test := range []struct { diff --git a/src/net/http/client.go b/src/net/http/client.go index 43a7a06bfb..ba095ea1e3 100644 --- a/src/net/http/client.go +++ b/src/net/http/client.go @@ -806,7 +806,8 @@ func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensit for k, vv := range ireqhdr { sensitive := false switch CanonicalHeaderKey(k) { - case "Authorization", "Www-Authenticate", "Cookie", "Cookie2": + case "Authorization", "Www-Authenticate", "Cookie", "Cookie2", + "Proxy-Authorization", "Proxy-Authenticate": sensitive = true } if !(sensitive && stripSensitiveHeaders) { diff --git a/src/net/http/client_test.go b/src/net/http/client_test.go index f2e04ca4e8..8f88e29ad2 100644 --- a/src/net/http/client_test.go +++ b/src/net/http/client_test.go @@ -1550,6 +1550,8 @@ func testClientStripHeadersOnRepeatedRedirect(t *testing.T, mode testMode) { if r.Host+r.URL.Path != "a.example.com/" { if h := r.Header.Get("Authorization"); h != "" { t.Errorf("on request to %v%v, Authorization=%q, want no header", r.Host, r.URL.Path, h) + } else if h := r.Header.Get("Proxy-Authorization"); h != "" { + t.Errorf("on request to %v%v, Proxy-Authorization=%q, want no header", r.Host, r.URL.Path, h) } } // Follow a chain of redirects from a to b and back to a. @@ -1578,6 +1580,7 @@ func testClientStripHeadersOnRepeatedRedirect(t *testing.T, mode testMode) { req, _ := NewRequest("GET", proto+"://a.example.com/", nil) req.Header.Add("Cookie", "foo=bar") req.Header.Add("Authorization", "secretpassword") + req.Header.Add("Proxy-Authorization", "secretpassword") res, err := c.Do(req) if err != nil { t.Fatal(err) diff --git a/src/runtime/chan.go b/src/runtime/chan.go index df48267e97..bb554ebfdb 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -497,7 +497,7 @@ func empty(c *hchan) bool { // c.timer is also immutable (it is set after make(chan) but before any channel operations). // All timer channels have dataqsiz > 0. if c.timer != nil { - c.timer.maybeRunChan() + c.timer.maybeRunChan(c) } return atomic.Loaduint(&c.qcount) == 0 } @@ -542,7 +542,7 @@ func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) } if c.timer != nil { - c.timer.maybeRunChan() + c.timer.maybeRunChan(c) } // Fast path: check for failed non-blocking operation without acquiring the lock. @@ -821,7 +821,7 @@ func chanlen(c *hchan) int { } async := debug.asynctimerchan.Load() != 0 if c.timer != nil && async { - c.timer.maybeRunChan() + c.timer.maybeRunChan(c) } if c.timer != nil && !async { // timer channels have a buffered implementation diff --git a/src/runtime/memmove_test.go b/src/runtime/memmove_test.go index ba9bc1f6b6..a2c3b72568 100644 --- a/src/runtime/memmove_test.go +++ b/src/runtime/memmove_test.go @@ -1123,3 +1123,102 @@ func BenchmarkMemclrKnownSize512KiB(b *testing.B) { memclrSink = x[:] } + +func BenchmarkMemmoveKnownSize112(b *testing.B) { + type T struct { + x [112]int8 + } + p := &T{} + q := &T{} + + b.SetBytes(int64(unsafe.Sizeof(T{}))) + for i := 0; i < b.N; i++ { + *p = *q + } + + memclrSink = p.x[:] +} +func BenchmarkMemmoveKnownSize128(b *testing.B) { + type T struct { + x [128]int8 + } + p := &T{} + q := &T{} + + b.SetBytes(int64(unsafe.Sizeof(T{}))) + for i := 0; i < b.N; i++ { + *p = *q + } + + memclrSink = p.x[:] +} +func BenchmarkMemmoveKnownSize192(b *testing.B) { + type T struct { + x [192]int8 + } + p := &T{} + q := &T{} + + b.SetBytes(int64(unsafe.Sizeof(T{}))) + for i := 0; i < b.N; i++ { + *p = *q + } + + memclrSink = p.x[:] +} +func BenchmarkMemmoveKnownSize248(b *testing.B) { + type T struct { + x [248]int8 + } + p := &T{} + q := &T{} + + b.SetBytes(int64(unsafe.Sizeof(T{}))) + for i := 0; i < b.N; i++ { + *p = *q + } + + memclrSink = p.x[:] +} +func BenchmarkMemmoveKnownSize256(b *testing.B) { + type T struct { + x [256]int8 + } + p := &T{} + q := &T{} + + b.SetBytes(int64(unsafe.Sizeof(T{}))) + for i := 0; i < b.N; i++ { + *p = *q + } + + memclrSink = p.x[:] +} +func BenchmarkMemmoveKnownSize512(b *testing.B) { + type T struct { + x [512]int8 + } + p := &T{} + q := &T{} + + b.SetBytes(int64(unsafe.Sizeof(T{}))) + for i := 0; i < b.N; i++ { + *p = *q + } + + memclrSink = p.x[:] +} +func BenchmarkMemmoveKnownSize1024(b *testing.B) { + type T struct { + x [1024]int8 + } + p := &T{} + q := &T{} + + b.SetBytes(int64(unsafe.Sizeof(T{}))) + for i := 0; i < b.N; i++ { + *p = *q + } + + memclrSink = p.x[:] +} diff --git a/src/runtime/mgc.go b/src/runtime/mgc.go index 87b6a748e1..38f343164c 100644 --- a/src/runtime/mgc.go +++ b/src/runtime/mgc.go @@ -131,6 +131,7 @@ package runtime import ( "internal/cpu" "internal/goarch" + "internal/goexperiment" "internal/runtime/atomic" "internal/runtime/gc" "unsafe" @@ -717,7 +718,7 @@ func gcStart(trigger gcTrigger) { throw("p mcache not flushed") } // Initialize ptrBuf if necessary. - if p.gcw.ptrBuf == nil { + if goexperiment.GreenTeaGC && p.gcw.ptrBuf == nil { p.gcw.ptrBuf = (*[gc.PageSize / goarch.PtrSize]uintptr)(persistentalloc(gc.PageSize, goarch.PtrSize, &memstats.gcMiscSys)) } } @@ -1233,14 +1234,7 @@ func gcMarkTermination(stw worldStop) { }) } if debug.gctrace > 1 { - for i := range pp.gcw.stats { - memstats.lastScanStats[i].spansDenseScanned += pp.gcw.stats[i].spansDenseScanned - memstats.lastScanStats[i].spanObjsDenseScanned += pp.gcw.stats[i].spanObjsDenseScanned - memstats.lastScanStats[i].spansSparseScanned += pp.gcw.stats[i].spansSparseScanned - memstats.lastScanStats[i].spanObjsSparseScanned += pp.gcw.stats[i].spanObjsSparseScanned - memstats.lastScanStats[i].sparseObjsScanned += pp.gcw.stats[i].sparseObjsScanned - } - clear(pp.gcw.stats[:]) + pp.gcw.flushScanStats(&memstats.lastScanStats) } pp.pinnerCache = nil }) @@ -1301,38 +1295,7 @@ func gcMarkTermination(stw worldStop) { print("\n") if debug.gctrace > 1 { - var ( - spansDenseScanned uint64 - spanObjsDenseScanned uint64 - spansSparseScanned uint64 - spanObjsSparseScanned uint64 - sparseObjsScanned uint64 - ) - for _, stats := range memstats.lastScanStats { - spansDenseScanned += stats.spansDenseScanned - spanObjsDenseScanned += stats.spanObjsDenseScanned - spansSparseScanned += stats.spansSparseScanned - spanObjsSparseScanned += stats.spanObjsSparseScanned - sparseObjsScanned += stats.sparseObjsScanned - } - totalObjs := sparseObjsScanned + spanObjsSparseScanned + spanObjsDenseScanned - totalSpans := spansSparseScanned + spansDenseScanned - print("scan: total ", sparseObjsScanned, "+", spanObjsSparseScanned, "+", spanObjsDenseScanned, "=", totalObjs, " objs") - print(", ", spansSparseScanned, "+", spansDenseScanned, "=", totalSpans, " spans\n") - for i, stats := range memstats.lastScanStats { - if stats == (sizeClassScanStats{}) { - continue - } - totalObjs := stats.sparseObjsScanned + stats.spanObjsSparseScanned + stats.spanObjsDenseScanned - totalSpans := stats.spansSparseScanned + stats.spansDenseScanned - if i == 0 { - print("scan: class L ") - } else { - print("scan: class ", gc.SizeClassToSize[i], "B ") - } - print(stats.sparseObjsScanned, "+", stats.spanObjsSparseScanned, "+", stats.spanObjsDenseScanned, "=", totalObjs, " objs") - print(", ", stats.spansSparseScanned, "+", stats.spansDenseScanned, "=", totalSpans, " spans\n") - } + dumpScanStats() } printunlock() } diff --git a/src/runtime/mgcmark_greenteagc.go b/src/runtime/mgcmark_greenteagc.go index 84cb6c99ab..75c347b9e9 100644 --- a/src/runtime/mgcmark_greenteagc.go +++ b/src/runtime/mgcmark_greenteagc.go @@ -763,3 +763,57 @@ func heapBitsSmallForAddrInline(spanBase, addr, elemsize uintptr) uintptr { } return read } + +type sizeClassScanStats struct { + spansDenseScanned uint64 + spanObjsDenseScanned uint64 + spansSparseScanned uint64 + spanObjsSparseScanned uint64 + sparseObjsScanned uint64 +} + +func dumpScanStats() { + var ( + spansDenseScanned uint64 + spanObjsDenseScanned uint64 + spansSparseScanned uint64 + spanObjsSparseScanned uint64 + sparseObjsScanned uint64 + ) + for _, stats := range memstats.lastScanStats { + spansDenseScanned += stats.spansDenseScanned + spanObjsDenseScanned += stats.spanObjsDenseScanned + spansSparseScanned += stats.spansSparseScanned + spanObjsSparseScanned += stats.spanObjsSparseScanned + sparseObjsScanned += stats.sparseObjsScanned + } + totalObjs := sparseObjsScanned + spanObjsSparseScanned + spanObjsDenseScanned + totalSpans := spansSparseScanned + spansDenseScanned + print("scan: total ", sparseObjsScanned, "+", spanObjsSparseScanned, "+", spanObjsDenseScanned, "=", totalObjs, " objs") + print(", ", spansSparseScanned, "+", spansDenseScanned, "=", totalSpans, " spans\n") + for i, stats := range memstats.lastScanStats { + if stats == (sizeClassScanStats{}) { + continue + } + totalObjs := stats.sparseObjsScanned + stats.spanObjsSparseScanned + stats.spanObjsDenseScanned + totalSpans := stats.spansSparseScanned + stats.spansDenseScanned + if i == 0 { + print("scan: class L ") + } else { + print("scan: class ", gc.SizeClassToSize[i], "B ") + } + print(stats.sparseObjsScanned, "+", stats.spanObjsSparseScanned, "+", stats.spanObjsDenseScanned, "=", totalObjs, " objs") + print(", ", stats.spansSparseScanned, "+", stats.spansDenseScanned, "=", totalSpans, " spans\n") + } +} + +func (w *gcWork) flushScanStats(dst *[gc.NumSizeClasses]sizeClassScanStats) { + for i := range w.stats { + dst[i].spansDenseScanned += w.stats[i].spansDenseScanned + dst[i].spanObjsDenseScanned += w.stats[i].spanObjsDenseScanned + dst[i].spansSparseScanned += w.stats[i].spansSparseScanned + dst[i].spanObjsSparseScanned += w.stats[i].spanObjsSparseScanned + dst[i].sparseObjsScanned += w.stats[i].sparseObjsScanned + } + clear(w.stats[:]) +} diff --git a/src/runtime/mgcmark_nogreenteagc.go b/src/runtime/mgcmark_nogreenteagc.go index 08f726a980..c0ca5c21ea 100644 --- a/src/runtime/mgcmark_nogreenteagc.go +++ b/src/runtime/mgcmark_nogreenteagc.go @@ -6,6 +6,8 @@ package runtime +import "internal/runtime/gc" + func (s *mspan) markBitsForIndex(objIndex uintptr) markBits { bytep, mask := s.gcmarkBits.bitp(objIndex) return markBits{bytep, mask, objIndex} @@ -78,3 +80,33 @@ func (w *gcWork) tryGetSpan(steal bool) objptr { func scanSpan(p objptr, gcw *gcWork) { throw("unimplemented") } + +type sizeClassScanStats struct { + sparseObjsScanned uint64 +} + +func dumpScanStats() { + var sparseObjsScanned uint64 + for _, stats := range memstats.lastScanStats { + sparseObjsScanned += stats.sparseObjsScanned + } + print("scan: total ", sparseObjsScanned, " objs\n") + for i, stats := range memstats.lastScanStats { + if stats == (sizeClassScanStats{}) { + continue + } + if i == 0 { + print("scan: class L ") + } else { + print("scan: class ", gc.SizeClassToSize[i], "B ") + } + print(stats.sparseObjsScanned, " objs\n") + } +} + +func (w *gcWork) flushScanStats(dst *[gc.NumSizeClasses]sizeClassScanStats) { + for i := range w.stats { + dst[i].sparseObjsScanned += w.stats[i].sparseObjsScanned + } + clear(w.stats[:]) +} diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go index 29ace5ec16..e34f0b10ea 100644 --- a/src/runtime/mstats.go +++ b/src/runtime/mstats.go @@ -49,14 +49,6 @@ type mstats struct { enablegc bool } -type sizeClassScanStats struct { - spansDenseScanned uint64 - spanObjsDenseScanned uint64 - spansSparseScanned uint64 - spanObjsSparseScanned uint64 - sparseObjsScanned uint64 -} - var memstats mstats // A MemStats records statistics about the memory allocator. diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 5b8db2bee4..37a7b7f684 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -3341,7 +3341,7 @@ top: // which may steal timers. It's important that between now // and then, nothing blocks, so these numbers remain mostly // relevant. - now, pollUntil, _ := pp.timers.check(0) + now, pollUntil, _ := pp.timers.check(0, nil) // Try to schedule the trace reader. if traceEnabled() || traceShuttingDown() { @@ -3780,7 +3780,7 @@ func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWo // timerpMask tells us whether the P may have timers at all. If it // can't, no need to check at all. if stealTimersOrRunNextG && timerpMask.read(enum.position()) { - tnow, w, ran := p2.timers.check(now) + tnow, w, ran := p2.timers.check(now, nil) now = tnow if w != 0 && (pollUntil == 0 || w < pollUntil) { pollUntil = w diff --git a/src/runtime/select.go b/src/runtime/select.go index 19256df6a6..ae7754b173 100644 --- a/src/runtime/select.go +++ b/src/runtime/select.go @@ -185,7 +185,7 @@ func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, blo } if cas.c.timer != nil { - cas.c.timer.maybeRunChan() + cas.c.timer.maybeRunChan(cas.c) } j := cheaprandn(uint32(norder + 1)) diff --git a/src/runtime/synctest.go b/src/runtime/synctest.go index f676afa20d..c837c792a5 100644 --- a/src/runtime/synctest.go +++ b/src/runtime/synctest.go @@ -185,7 +185,6 @@ func synctestRun(f func()) { } const synctestBaseTime = 946684800000000000 // midnight UTC 2000-01-01 bubble.now = synctestBaseTime - bubble.timers.bubble = bubble lockInit(&bubble.mu, lockRankSynctest) lockInit(&bubble.timers.mu, lockRankTimers) @@ -213,7 +212,7 @@ func synctestRun(f func()) { // so timer goroutines inherit their child race context from g0. curg := gp.m.curg gp.m.curg = nil - gp.bubble.timers.check(gp.bubble.now) + gp.bubble.timers.check(bubble.now, bubble) gp.m.curg = curg }) gopark(synctestidle_c, nil, waitReasonSynctestRun, traceBlockSynctest, 0) diff --git a/src/runtime/time.go b/src/runtime/time.go index 711f3e472d..4880dce8cd 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -62,6 +62,7 @@ type timer struct { isFake bool // timer is using fake time; immutable; can be read without lock blocked uint32 // number of goroutines blocked on timer's channel + rand uint32 // randomizes order of timers at same instant; only set when isFake // Timer wakes up at when, and then at when+period, ... (period > 0 only) // each time calling f(arg, seq, delay) in the timer goroutine, so f must be @@ -156,8 +157,6 @@ type timers struct { // heap[i].when over timers with the timerModified bit set. // If minWhenModified = 0, it means there are no timerModified timers in the heap. minWhenModified atomic.Int64 - - bubble *synctestBubble } type timerWhen struct { @@ -165,6 +164,21 @@ type timerWhen struct { when int64 } +// less reports whether tw is less than other. +func (tw timerWhen) less(other timerWhen) bool { + switch { + case tw.when < other.when: + return true + case tw.when > other.when: + return false + default: + // When timers wake at the same time, use a per-timer random value to order them. + // We only set the random value for timers using fake time, since there's + // no practical way to schedule real-time timers for the same instant. + return tw.timer.rand < other.timer.rand + } +} + func (ts *timers) lock() { lock(&ts.mu) } @@ -387,7 +401,7 @@ func newTimer(when, period int64, f func(arg any, seq uintptr, delay int64), arg throw("invalid timer channel: no capacity") } } - if gr := getg().bubble; gr != nil { + if bubble := getg().bubble; bubble != nil { t.isFake = true } t.modify(when, period, f, arg, 0) @@ -469,7 +483,7 @@ func (t *timer) maybeRunAsync() { // timer ourselves now is fine.) if now := nanotime(); t.when <= now { systemstack(func() { - t.unlockAndRun(now) // resets t.when + t.unlockAndRun(now, nil) // resets t.when }) t.lock() } @@ -605,6 +619,29 @@ func (t *timer) modify(when, period int64, f func(arg any, seq uintptr, delay in add := t.needsAdd() + if add && t.isFake { + // If this is a bubbled timer scheduled to fire immediately, + // run it now rather than waiting for the bubble's timer scheduler. + // This avoids deferring timer execution until after the bubble + // becomes durably blocked. + // + // Don't do this for non-bubbled timers: It isn't necessary, + // and there may be cases where the runtime executes timers with + // the expectation the timer func will not run in the current goroutine. + // Bubbled timers are always created by the time package, and are + // safe to run in the current goroutine. + bubble := getg().bubble + if bubble == nil { + throw("fake timer executing with no bubble") + } + if t.state&timerHeaped == 0 && when <= bubble.now { + systemstack(func() { + t.unlockAndRun(bubble.now, bubble) + }) + return pending + } + } + if !async && t.isChan { // Stop any future sends with stale values. // See timer.unlockAndRun. @@ -641,7 +678,7 @@ func (t *timer) modify(when, period int64, f func(arg any, seq uintptr, delay in // t must be locked. func (t *timer) needsAdd() bool { assertLockHeld(&t.mu) - need := t.state&timerHeaped == 0 && t.when > 0 && (!t.isChan || t.isFake || t.blocked > 0) + need := t.state&timerHeaped == 0 && t.when > 0 && (!t.isChan || t.blocked > 0) if need { t.trace("needsAdd+") } else { @@ -696,6 +733,12 @@ func (t *timer) maybeAdd() { when := int64(0) wake := false if t.needsAdd() { + if t.isFake { + // Re-randomize timer order. + // We could do this for all timers, but unbubbled timers are highly + // unlikely to have the same when. + t.rand = cheaprand() + } t.state |= timerHeaped when = t.when wakeTime := ts.wakeTime() @@ -960,7 +1003,7 @@ func (ts *timers) wakeTime() int64 { // We pass now in and out to avoid extra calls of nanotime. // //go:yeswritebarrierrec -func (ts *timers) check(now int64) (rnow, pollUntil int64, ran bool) { +func (ts *timers) check(now int64, bubble *synctestBubble) (rnow, pollUntil int64, ran bool) { ts.trace("check") // If it's not yet time for the first timer, or the first adjusted // timer, then there is nothing to do. @@ -993,7 +1036,7 @@ func (ts *timers) check(now int64) (rnow, pollUntil int64, ran bool) { ts.adjust(now, false) for len(ts.heap) > 0 { // Note that runtimer may temporarily unlock ts. - if tw := ts.run(now); tw != 0 { + if tw := ts.run(now, bubble); tw != 0 { if tw > 0 { pollUntil = tw } @@ -1025,7 +1068,7 @@ func (ts *timers) check(now int64) (rnow, pollUntil int64, ran bool) { // If a timer is run, this will temporarily unlock ts. // //go:systemstack -func (ts *timers) run(now int64) int64 { +func (ts *timers) run(now int64, bubble *synctestBubble) int64 { ts.trace("run") assertLockHeld(&ts.mu) Redo: @@ -1059,7 +1102,7 @@ Redo: return t.when } - t.unlockAndRun(now) + t.unlockAndRun(now, bubble) assertLockHeld(&ts.mu) // t is unlocked now, but not ts return 0 } @@ -1070,7 +1113,7 @@ Redo: // unlockAndRun returns with t unlocked and t.ts (re-)locked. // //go:systemstack -func (t *timer) unlockAndRun(now int64) { +func (t *timer) unlockAndRun(now int64, bubble *synctestBubble) { t.trace("unlockAndRun") assertLockHeld(&t.mu) if t.ts != nil { @@ -1082,10 +1125,10 @@ func (t *timer) unlockAndRun(now int64) { // out from under us while this function executes. gp := getg() var tsLocal *timers - if t.ts == nil || t.ts.bubble == nil { + if bubble == nil { tsLocal = &gp.m.p.ptr().timers } else { - tsLocal = &t.ts.bubble.timers + tsLocal = &bubble.timers } if tsLocal.raceCtx == 0 { tsLocal.raceCtx = racegostart(abi.FuncPCABIInternal((*timers).run) + sys.PCQuantum) @@ -1138,10 +1181,10 @@ func (t *timer) unlockAndRun(now int64) { if gp.racectx != 0 { throw("unexpected racectx") } - if ts == nil || ts.bubble == nil { + if bubble == nil { gp.racectx = gp.m.p.ptr().timers.raceCtx } else { - gp.racectx = ts.bubble.timers.raceCtx + gp.racectx = bubble.timers.raceCtx } } @@ -1149,14 +1192,14 @@ func (t *timer) unlockAndRun(now int64) { ts.unlock() } - if ts != nil && ts.bubble != nil { + if bubble != nil { // Temporarily use the timer's synctest group for the G running this timer. gp := getg() if gp.bubble != nil { throw("unexpected syncgroup set") } - gp.bubble = ts.bubble - ts.bubble.changegstatus(gp, _Gdead, _Grunning) + gp.bubble = bubble + bubble.changegstatus(gp, _Gdead, _Grunning) } if !async && t.isChan { @@ -1200,13 +1243,13 @@ func (t *timer) unlockAndRun(now int64) { unlock(&t.sendLock) } - if ts != nil && ts.bubble != nil { + if bubble != nil { gp := getg() - ts.bubble.changegstatus(gp, _Grunning, _Gdead) + bubble.changegstatus(gp, _Grunning, _Gdead) if raceenabled { // Establish a happens-before between this timer event and // the next synctest.Wait call. - racereleasemergeg(gp, ts.bubble.raceaddr()) + racereleasemergeg(gp, bubble.raceaddr()) } gp.bubble = nil } @@ -1234,7 +1277,7 @@ func (ts *timers) verify() { // The heap is timerHeapN-ary. See siftupTimer and siftdownTimer. p := int(uint(i-1) / timerHeapN) - if tw.when < ts.heap[p].when { + if tw.less(ts.heap[p]) { print("bad timer heap at ", i, ": ", p, ": ", ts.heap[p].when, ", ", i, ": ", tw.when, "\n") throw("bad timer heap") } @@ -1312,13 +1355,12 @@ func (ts *timers) siftUp(i int) { badTimer() } tw := heap[i] - when := tw.when - if when <= 0 { + if tw.when <= 0 { badTimer() } for i > 0 { p := int(uint(i-1) / timerHeapN) // parent - if when >= heap[p].when { + if !tw.less(heap[p]) { break } heap[i] = heap[p] @@ -1341,8 +1383,7 @@ func (ts *timers) siftDown(i int) { return } tw := heap[i] - when := tw.when - if when <= 0 { + if tw.when <= 0 { badTimer() } for { @@ -1350,11 +1391,11 @@ func (ts *timers) siftDown(i int) { if leftChild >= n { break } - w := when + w := tw c := -1 for j, tw := range heap[leftChild:min(leftChild+timerHeapN, n)] { - if tw.when < w { - w = tw.when + if tw.less(w) { + w = tw c = leftChild + j } } @@ -1395,24 +1436,10 @@ func badTimer() { // maybeRunChan checks whether the timer needs to run // to send a value to its associated channel. If so, it does. // The timer must not be locked. -func (t *timer) maybeRunChan() { - if t.isFake { - t.lock() - var timerBubble *synctestBubble - if t.ts != nil { - timerBubble = t.ts.bubble - } - t.unlock() - bubble := getg().bubble - if bubble == nil { - panic(plainError("synctest timer accessed from outside bubble")) - } - if timerBubble != nil && bubble != timerBubble { - panic(plainError("timer moved between synctest bubbles")) - } - // No need to do anything here. - // synctest.Run will run the timer when it advances its fake clock. - return +func (t *timer) maybeRunChan(c *hchan) { + if t.isFake && getg().bubble != c.bubble { + // This should have been checked by the caller, but check just in case. + fatal("synctest timer accessed from outside bubble") } if t.astate.Load()&timerHeaped != 0 { // If the timer is in the heap, the ordinary timer code @@ -1422,6 +1449,9 @@ func (t *timer) maybeRunChan() { t.lock() now := nanotime() + if t.isFake { + now = getg().bubble.now + } if t.state&timerHeaped != 0 || t.when == 0 || t.when > now { t.trace("maybeRunChan-") // Timer in the heap, or not running at all, or not triggered. @@ -1430,7 +1460,7 @@ func (t *timer) maybeRunChan() { } t.trace("maybeRunChan+") systemstack(func() { - t.unlockAndRun(now) + t.unlockAndRun(now, c.bubble) }) } @@ -1440,9 +1470,11 @@ func (t *timer) maybeRunChan() { // adding it if needed. func blockTimerChan(c *hchan) { t := c.timer - if t.isFake { - return + if t.isFake && c.bubble != getg().bubble { + // This should have been checked by the caller, but check just in case. + fatal("synctest timer accessed from outside bubble") } + t.lock() t.trace("blockTimerChan") if !t.isChan { @@ -1480,9 +1512,6 @@ func blockTimerChan(c *hchan) { // blocked on it anymore. func unblockTimerChan(c *hchan) { t := c.timer - if t.isFake { - return - } t.lock() t.trace("unblockTimerChan") if !t.isChan || t.blocked == 0 { diff --git a/src/slices/sort_benchmark_test.go b/src/slices/sort_benchmark_test.go index 1dde26ef1c..cafb1a4618 100644 --- a/src/slices/sort_benchmark_test.go +++ b/src/slices/sort_benchmark_test.go @@ -23,7 +23,7 @@ func BenchmarkBinarySearchFloats(b *testing.B) { needle := (floats[midpoint] + floats[midpoint+1]) / 2 b.ResetTimer() for i := 0; i < b.N; i++ { - slices.BinarySearch(floats, needle) + _, _ = slices.BinarySearch(floats, needle) } }) } @@ -46,7 +46,7 @@ func BenchmarkBinarySearchFuncStruct(b *testing.B) { cmpFunc := func(a, b *myStruct) int { return a.n - b.n } b.ResetTimer() for i := 0; i < b.N; i++ { - slices.BinarySearchFunc(structs, needle, cmpFunc) + _, _ = slices.BinarySearchFunc(structs, needle, cmpFunc) } }) } diff --git a/src/slices/sort_test.go b/src/slices/sort_test.go index 2e045e2af8..855f861c51 100644 --- a/src/slices/sort_test.go +++ b/src/slices/sort_test.go @@ -264,19 +264,19 @@ func TestMinMaxPanics(t *testing.T) { intCmp := func(a, b int) int { return a - b } emptySlice := []int{} - if !panics(func() { Min(emptySlice) }) { + if !panics(func() { _ = Min(emptySlice) }) { t.Errorf("Min([]): got no panic, want panic") } - if !panics(func() { Max(emptySlice) }) { + if !panics(func() { _ = Max(emptySlice) }) { t.Errorf("Max([]): got no panic, want panic") } - if !panics(func() { MinFunc(emptySlice, intCmp) }) { + if !panics(func() { _ = MinFunc(emptySlice, intCmp) }) { t.Errorf("MinFunc([]): got no panic, want panic") } - if !panics(func() { MaxFunc(emptySlice, intCmp) }) { + if !panics(func() { _ = MaxFunc(emptySlice, intCmp) }) { t.Errorf("MaxFunc([]): got no panic, want panic") } } diff --git a/src/sort/sort_slices_benchmark_test.go b/src/sort/sort_slices_benchmark_test.go index 069536df03..6fea511284 100644 --- a/src/sort/sort_slices_benchmark_test.go +++ b/src/sort/sort_slices_benchmark_test.go @@ -85,7 +85,7 @@ func BenchmarkSlicesIsSorted(b *testing.B) { b.StopTimer() ints := makeSortedInts(N) b.StartTimer() - slices.IsSorted(ints) + _ = slices.IsSorted(ints) } } diff --git a/src/testing/testing.go b/src/testing/testing.go index b5305f29cc..b2d4c0c938 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -900,6 +900,7 @@ type TB interface { Skipped() bool TempDir() string Context() context.Context + Output() io.Writer // A private method to prevent users implementing the // interface and so future additions to it will not diff --git a/test/fixedbugs/issue73309b.go b/test/fixedbugs/issue73309b.go new file mode 100644 index 0000000000..1e29781ba9 --- /dev/null +++ b/test/fixedbugs/issue73309b.go @@ -0,0 +1,88 @@ +// compile + +// Copyright 2025 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 Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +// a Validator instance +type Validator []Validable + +type Numeric interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~float32 | ~float64 +} + +func (v Validator) Valid() bool { + for _, field := range v { + if !field.Validate() { + return false + } + } + return true +} + +type Validable interface { + Validate() bool +} + +type FieldDef[T any] struct { + value T + rules []Rule[T] +} + +func (f FieldDef[T]) Validate() bool { + for _, rule := range f.rules { + if !rule(f) { + return false + } + } + return true +} + +type Rule[T any] = func(FieldDef[T]) bool + +func Field[T any](value T, rules ...Rule[T]) *FieldDef[T] { + return &FieldDef[T]{value: value, rules: rules} +} + +type StringRule = Rule[string] + +type NumericRule[T Numeric] = Rule[T] + +type UnsignedRule[T Unsigned] = Rule[T] + +func MinS(n int) StringRule { + return func(fd FieldDef[string]) bool { + return len(fd.value) < n + } +} + +func MinD[T Numeric](n T) NumericRule[T] { + return func(fd FieldDef[T]) bool { + return fd.value < n + } +} + +func MinU[T Unsigned](n T) UnsignedRule[T] { + return func(fd FieldDef[T]) bool { + return fd.value < n + } +} + +func main() { + v := Validator{ + Field("test", MinS(5)), + } + + if !v.Valid() { + println("invalid") + return + } + + println("valid") +} diff --git a/test/tailcall.go b/test/tailcall.go index c1c35c5e48..6b14a2f1b7 100644 --- a/test/tailcall.go +++ b/test/tailcall.go @@ -7,14 +7,16 @@ package p // Test that when generating wrappers for methods, we generate a tail call to the pointer version of -// the method. +// the method, if that method is not inlineable. We use go:noinline here to force the non-inlineability +// condition. -func (f *Foo) Get2Vals() [2]int { return [2]int{f.Val, f.Val + 1} } -func (f *Foo) Get3Vals() (int, int, int) { return f.Val, f.Val + 1, f.Val + 2 } +//go:noinline +func (f *Foo) Get2Vals() [2]int { return [2]int{f.Val, f.Val + 1} } +func (f *Foo) Get3Vals() [3]int { return [3]int{f.Val, f.Val + 1, f.Val + 2} } type Foo struct{ Val int } -type Bar struct { // ERROR "tail call emitted for the method \(\*Foo\).Get2Vals wrapper" "tail call emitted for the method \(\*Foo\).Get3Vals wrapper" +type Bar struct { // ERROR "tail call emitted for the method \(\*Foo\).Get2Vals wrapper" int64 *Foo // needs a method wrapper string