mirror of https://github.com/golang/go.git
Merge branch 'golang:master' into master
This commit is contained in:
commit
e0ce9653db
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 <pkg>
|
||||
// go doc <sym>[.<methodOrField>]
|
||||
// go doc [<pkg>.]<sym>[.<methodOrField>]
|
||||
// go doc [<pkg>.][<sym>.]<methodOrField>
|
||||
//
|
||||
// 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 <pkg> <sym>[.<methodOrField>]
|
||||
//
|
||||
// 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:])
|
||||
}
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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,
|
||||
},
|
||||
|
||||
|
|
@ -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 <pkg>
|
||||
// go doc <sym>[.<methodOrField>]
|
||||
// go doc [<pkg>.]<sym>[.<methodOrField>]
|
||||
// go doc [<pkg>.][<sym>.]<methodOrField>
|
||||
//
|
||||
// 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 <pkg> <sym>[.<methodOrField>]
|
||||
//
|
||||
// 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 <pkg>\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
|
||||
|
|
@ -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"
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
//go:build plan9 || windows
|
||||
|
||||
package main
|
||||
package doc
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
//go:build unix || js || wasip1
|
||||
|
||||
package main
|
||||
package doc
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
|
@ -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()}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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]
|
||||
})
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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[:]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[:])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[:])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func BenchmarkSlicesIsSorted(b *testing.B) {
|
|||
b.StopTimer()
|
||||
ints := makeSortedInts(N)
|
||||
b.StartTimer()
|
||||
slices.IsSorted(ints)
|
||||
_ = slices.IsSorted(ints)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue