Merge branch 'master' into feature/sync-atomic-and-or-s390x

This commit is contained in:
Mauri de Souza Meneguzzo 2024-02-17 07:23:55 -03:00
commit 20dea110c8
330 changed files with 11377 additions and 10462 deletions

View file

@ -21,3 +21,6 @@ warning output from the go api tool. Each file should be named
nnnnn.txt, after the issue number for the accepted proposal.
(The #nnnnn suffix must also appear at the end of each line in the file;
that will be preserved when next/*.txt is concatenated into go1.XX.txt.)
When you add a file to the api/next directory, you must add at least one file
under doc/next. See doc/README.md for details.

1
api/next/42888.txt Normal file
View file

@ -0,0 +1 @@
pkg runtime/debug, func SetCrashOutput(*os.File) error #42888

1
api/next/61696.txt Normal file
View file

@ -0,0 +1 @@
pkg sync, method (*Map) Clear() #61696

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
<!--{
"Title": "The Go Programming Language Specification",
"Subtitle": "Version of Dec 27, 2023",
"Subtitle": "Language version go1.22 (Feb 6, 2024)",
"Path": "/ref/spec"
}-->
@ -6661,7 +6661,7 @@ array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, &lt;-chan E element e E
integer n integer type I value i I
integer n integer type value i see below
</pre>
<ol>
@ -6703,26 +6703,33 @@ is <code>nil</code>, the range expression blocks forever.
<li>
For an integer value <code>n</code>, the iteration values 0 through <code>n-1</code>
are produced in increasing order, with the same type as <code>n</code>.
are produced in increasing order.
If <code>n</code> &lt= 0, the loop does not run any iterations.
</li>
</ol>
<p>
The iteration values are assigned to the respective
iteration variables as in an <a href="#Assignment_statements">assignment statement</a>.
</p>
<p>
The iteration variables may be declared by the "range" clause using a form of
<a href="#Short_variable_declarations">short variable declaration</a>
(<code>:=</code>).
In this case their types are set to the types of the respective iteration values
and their <a href="#Declarations_and_scope">scope</a> is the block of the "for" statement;
each iteration has its own separate variables [<a href="#Go_1.22">Go 1.22</a>]
In this case their <a href="#Declarations_and_scope">scope</a> is the block of the "for" statement
and each iteration has its own new variables [<a href="#Go_1.22">Go 1.22</a>]
(see also <a href="#For_clause">"for" statements with a ForClause</a>).
If the iteration variables are declared outside the “for” statement,
after execution their values will be those of the last iteration.
If the range expression is a (possibly untyped) integer expression <code>n</code>,
the variable has the same type as if it was
<a href="#Variable_declarations">declared</a> with initialization
expression <code>n</code>.
Otherwise, the variables have the types of their respective iteration values.
</p>
<p>
If the iteration variables are not explicitly declared by the "range" clause,
they must be preexisting.
In this case, the iteration values are assigned to the respective variables
as in an <a href="#Assignment_statements">assignment statement</a>.
If the range expression is a (possibly untyped) integer expression <code>n</code>,
<code>n</code> too must be <a href="#Assignability">assignable</a> to the iteration variable;
if there is no iteration variable, <code>n</code> must be assignable to <code>int</code>.
</p>
<pre>
@ -6765,6 +6772,11 @@ for i := range 10 {
// type of i is int (default type for untyped constant 10)
f(i)
}
// invalid: 256 cannot be assigned to uint8
var u uint8
for u = range 256 {
}
</pre>

View file

@ -0,0 +1,8 @@
The [`debug.SetCrashOutput`](/runtime#SetCrashOutput) function allows
the user to specify an alternate file to which the runtime should
write its fatal crash report
([#42888](https://github.com/golang/go/issues/42888)).
It may be used to construct an automated reporting mechanism for all
unexpected crashes, not just those in goroutines that explicitly use
`recover`.

View file

@ -0,0 +1,4 @@
The [`(*sync.Map) Clear()`](//sync#Map.Clear) method deletes
all the entries, resulting in an empty map
([#61696](https://github.com/golang/go/issues/61696)).
It is analogous to `clear`.

View file

@ -1,2 +1,8 @@
## Ports {#ports}
### Darwin {#darwin}
<!-- go.dev/issue/64207 -->
As [announced](go1.22#darwin) in the Go 1.22 release notes,
Go 1.23 requires macOS 11 Big Sur or later;
support for previous versions has been discontinued.

View file

@ -24,8 +24,8 @@
# in the CL match the update.bash in the CL.
# Versions to use.
CODE=2023d
DATA=2023d
CODE=2024a
DATA=2024a
set -e

Binary file not shown.

View file

@ -433,6 +433,10 @@ func writeHeader(w io.Writer, h *header) error {
// [Writer.CreateHeader], [Writer.CreateRaw], or [Writer.Close].
//
// In contrast to [Writer.CreateHeader], the bytes passed to Writer are not compressed.
//
// CreateRaw's argument is stored in w. If the argument is a pointer to the embedded
// [FileHeader] in a [File] obtained from a [Reader] created from in-memory data,
// then w will refer to all of that memory.
func (w *Writer) CreateRaw(fh *FileHeader) (io.Writer, error) {
if err := w.prepare(fh); err != nil {
return nil, err
@ -471,7 +475,10 @@ func (w *Writer) Copy(f *File) error {
if err != nil {
return err
}
fw, err := w.CreateRaw(&f.FileHeader)
// Copy the FileHeader so w doesn't store a pointer to the data
// of f's entire archive. See #65499.
fh := f.FileHeader
fw, err := w.CreateRaw(&fh)
if err != nil {
return err
}

View file

@ -16,8 +16,10 @@ import (
"encoding/json"
"errors"
"fmt"
"internal/testenv"
"os"
"os/exec"
"os/user"
"path/filepath"
"regexp"
"strconv"
@ -266,12 +268,28 @@ func compilerSupportsLocation() bool {
case "gcc":
return compiler.major >= 10
case "clang":
// TODO(65606): The clang toolchain on the LUCI builders is not built against
// zlib, the ASAN runtime can't actually symbolize its own stack trace. Once
// this is resolved, one way or another, switch this back to 'true'. We still
// have coverage from the 'gcc' case above.
if inLUCIBuild() {
return false
}
return true
default:
return false
}
}
// inLUCIBuild returns true if we're currently executing in a LUCI build.
func inLUCIBuild() bool {
u, err := user.Current()
if err != nil {
return false
}
return testenv.Builder() != "" && u.Username == "swarming"
}
// compilerRequiredTsanVersion reports whether the compiler is the version required by Tsan.
// Only restrictions for ppc64le are known; otherwise return true.
func compilerRequiredTsanVersion(goos, goarch string) bool {

View file

@ -124,7 +124,7 @@ type CmdFlags struct {
TraceProfile string "help:\"write an execution trace to `file`\""
TrimPath string "help:\"remove `prefix` from recorded source file paths\""
WB bool "help:\"enable write barrier\"" // TODO: remove
PgoProfile string "help:\"read profile from `file`\""
PgoProfile string "help:\"read profile or pre-process profile from `file`\""
ErrorURL bool "help:\"print explanatory URL with error message if applicable\""
// Configuration derived from flags; not a flag itself.

View file

@ -740,7 +740,7 @@ func findHotConcreteCallee(p *pgo.Profile, caller *ir.Func, call *ir.CallExpr, e
}
if base.Debug.PGODebug >= 2 {
fmt.Printf("%v call %s:%d: hottest callee %s (weight %d)\n", ir.Line(call), callerName, callOffset, hottest.Dst.Name(), hottest.Weight)
fmt.Printf("%v: call %s:%d: hottest callee %s (weight %d)\n", ir.Line(call), callerName, callOffset, hottest.Dst.Name(), hottest.Weight)
}
return hottest.Dst.AST, hottest.Weight
}

View file

@ -78,7 +78,7 @@ var (
)
// PGOInlinePrologue records the hot callsites from ir-graph.
func PGOInlinePrologue(p *pgo.Profile, funcs []*ir.Func) {
func PGOInlinePrologue(p *pgo.Profile) {
if base.Debug.PGOInlineCDFThreshold != "" {
if s, err := strconv.ParseFloat(base.Debug.PGOInlineCDFThreshold, 64); err == nil && s >= 0 && s <= 100 {
inlineCDFHotCallSiteThresholdPercent = s
@ -119,7 +119,7 @@ func PGOInlinePrologue(p *pgo.Profile, funcs []*ir.Func) {
// a percent, is the lower bound of weight for nodes to be considered hot
// (currently only used in debug prints) (in case of equal weights,
// comparing with the threshold may not accurately reflect which nodes are
// considiered hot).
// considered hot).
func hotNodesFromCDF(p *pgo.Profile) (float64, []pgo.NamedCallEdge) {
cum := int64(0)
for i, n := range p.NamedEdgeMap.ByWeight {
@ -138,41 +138,32 @@ func hotNodesFromCDF(p *pgo.Profile) (float64, []pgo.NamedCallEdge) {
// CanInlineFuncs computes whether a batch of functions are inlinable.
func CanInlineFuncs(funcs []*ir.Func, profile *pgo.Profile) {
if profile != nil {
PGOInlinePrologue(profile, funcs)
PGOInlinePrologue(profile)
}
ir.VisitFuncsBottomUp(funcs, func(list []*ir.Func, recursive bool) {
CanInlineSCC(list, recursive, profile)
})
}
// CanInlineSCC computes the inlinability of functions within an SCC
// (strongly connected component).
//
// CanInlineSCC is designed to be used by ir.VisitFuncsBottomUp
// callbacks.
func CanInlineSCC(funcs []*ir.Func, recursive bool, profile *pgo.Profile) {
if base.Flag.LowerL == 0 {
return
}
numfns := numNonClosures(funcs)
ir.VisitFuncsBottomUp(funcs, func(funcs []*ir.Func, recursive bool) {
numfns := numNonClosures(funcs)
for _, fn := range funcs {
if !recursive || numfns > 1 {
// We allow inlining if there is no
// recursion, or the recursion cycle is
// across more than one function.
CanInline(fn, profile)
} else {
if base.Flag.LowerM > 1 && fn.OClosure == nil {
fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(fn), fn.Nname)
for _, fn := range funcs {
if !recursive || numfns > 1 {
// We allow inlining if there is no
// recursion, or the recursion cycle is
// across more than one function.
CanInline(fn, profile)
} else {
if base.Flag.LowerM > 1 && fn.OClosure == nil {
fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(fn), fn.Nname)
}
}
if inlheur.Enabled() {
analyzeFuncProps(fn, profile)
}
}
if inlheur.Enabled() {
analyzeFuncProps(fn, profile)
}
}
})
}
// GarbageCollectUnreferencedHiddenClosures makes a pass over all the
@ -227,7 +218,7 @@ func GarbageCollectUnreferencedHiddenClosures() {
}
// inlineBudget determines the max budget for function 'fn' prior to
// analyzing the hairyness of the body of 'fn'. We pass in the pgo
// analyzing the hairiness of the body of 'fn'. We pass in the pgo
// profile if available (which can change the budget), also a
// 'relaxed' flag, which expands the budget slightly to allow for the
// possibility that a call to the function might have its score
@ -239,7 +230,7 @@ func inlineBudget(fn *ir.Func, profile *pgo.Profile, relaxed bool, verbose bool)
if profile != nil {
if n, ok := profile.WeightedCG.IRNodes[ir.LinkFuncName(fn)]; ok {
if _, ok := candHotCalleeMap[n]; ok {
budget = int32(inlineHotMaxBudget)
budget = inlineHotMaxBudget
if verbose {
fmt.Printf("hot-node enabled increased budget=%v for func=%v\n", budget, ir.PkgFuncName(fn))
}
@ -322,10 +313,9 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
}
n.Func.Inl = &ir.Inline{
Cost: budget - visitor.budget,
Dcl: pruneUnusedAutos(n.Func.Dcl, &visitor),
HaveDcl: true,
Cost: budget - visitor.budget,
Dcl: pruneUnusedAutos(n.Func.Dcl, &visitor),
HaveDcl: true,
CanDelayResults: canDelayResults(fn),
}
if base.Flag.LowerM != 0 || logopt.Enabled() {
@ -919,7 +909,7 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool
return true, 0, metric
}
// canInlineCallsite returns true if the call n from caller to callee
// canInlineCallExpr returns true if the call n from caller to callee
// can be inlined, plus the score computed for the call expr in
// question. bigCaller indicates that caller is a big function. log
// indicates that the 'cannot inline' reason should be logged.

View file

@ -95,7 +95,7 @@ func AnalyzeFunc(fn *ir.Func, canInline func(*ir.Func), budgetForFunc func(*ir.F
// only after the closures it contains have been processed, so
// iterate through the list in reverse order. Once a function has
// been analyzed, revisit the question of whether it should be
// inlinable; if it is over the default hairyness limit and it
// inlinable; if it is over the default hairiness limit and it
// doesn't have any interesting properties, then we don't want
// the overhead of writing out its inline body.
nameFinder := newNameFinder(fn)

View file

@ -45,7 +45,7 @@ func addParamsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, nf
return analyzers
}
// makeParamAnalyzer creates a new helper object to analyze parameters
// makeParamsAnalyzer creates a new helper object to analyze parameters
// of function fn. If the function doesn't have any interesting
// params, a nil helper is returned along with a set of default param
// flags for the func.

View file

@ -590,7 +590,7 @@ func GetCallSiteScore(fn *ir.Func, call *ir.CallExpr) (int, bool) {
// BudgetExpansion returns the amount to relax/expand the base
// inlining budget when the new inliner is turned on; the inliner
// will add the returned value to the hairyness budget.
// will add the returned value to the hairiness budget.
//
// Background: with the new inliner, the score for a given callsite
// can be adjusted down by some amount due to heuristics, however we
@ -617,7 +617,7 @@ var allCallSites CallSiteTab
// along with info on call site scoring and the adjustments made to a
// given score. Here profile is the PGO profile in use (may be
// nil), budgetCallback is a callback that can be invoked to find out
// the original pre-adjustment hairyness limit for the function, and
// the original pre-adjustment hairiness limit for the function, and
// inlineHotMaxBudget is the constant of the same name used in the
// inliner. Sample output lines:
//
@ -629,7 +629,7 @@ var allCallSites CallSiteTab
//
// In the dump above, "Score" is the final score calculated for the
// callsite, "Adjustment" is the amount added to or subtracted from
// the original hairyness estimate to form the score. "Status" shows
// the original hairiness estimate to form the score. "Status" shows
// whether anything changed with the site -- did the adjustment bump
// it down just below the threshold ("PROMOTED") or instead bump it
// above the threshold ("DEMOTED"); this will be blank ("---") if no

View file

@ -38,27 +38,16 @@ func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgo.Profile) {
if base.Debug.PGOInline != 0 {
inlProfile = profile
}
if inlProfile != nil {
inline.PGOInlinePrologue(inlProfile, pkg.Funcs)
// First compute inlinability of all functions in the package.
inline.CanInlineFuncs(pkg.Funcs, inlProfile)
// Now we make a second pass to do devirtualization and inlining of
// calls. Order here should not matter.
for _, fn := range pkg.Funcs {
DevirtualizeAndInlineFunc(fn, inlProfile)
}
ir.VisitFuncsBottomUp(pkg.Funcs, func(funcs []*ir.Func, recursive bool) {
// We visit functions within an SCC in fairly arbitrary order,
// so by computing inlinability for all functions in the SCC
// before performing any inlining, the results are less
// sensitive to the order within the SCC (see #58905 for an
// example).
// First compute inlinability for all functions in the SCC ...
inline.CanInlineSCC(funcs, recursive, inlProfile)
// ... then make a second pass to do devirtualization and inlining
// of calls.
for _, fn := range funcs {
DevirtualizeAndInlineFunc(fn, inlProfile)
}
})
if base.Flag.LowerL != 0 {
// Perform a garbage collection of hidden closures functions that
// are no longer reachable from top-level functions following

View file

@ -22,7 +22,7 @@ func checkStaticValueResult(n Node, newres Node) {
}
}
// checkStaticValueResult compares the result from ReassignOracle.Reassigned
// checkReassignedResult compares the result from ReassignOracle.Reassigned
// with the corresponding result from ir.Reassigned to make sure they agree.
// This method is called only when turned on via build tag.
func checkReassignedResult(n *Name, newres bool) {

View file

@ -663,9 +663,24 @@ func (pr *pkgReader) objInstIdx(info objInfo, dict *readerDict, shaped bool) ir.
}
// objIdx returns the specified object, instantiated with the given
// type arguments, if any. If shaped is true, then the shaped variant
// of the object is returned instead.
// type arguments, if any.
// If shaped is true, then the shaped variant of the object is returned
// instead.
func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) ir.Node {
n, err := pr.objIdxMayFail(idx, implicits, explicits, shaped)
if err != nil {
base.Fatalf("%v", err)
}
return n
}
// objIdxMayFail is equivalent to objIdx, but returns an error rather than
// failing the build if this object requires type arguments and the incorrect
// number of type arguments were passed.
//
// Other sources of internal failure (such as duplicate definitions) still fail
// the build.
func (pr *pkgReader) objIdxMayFail(idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) (ir.Node, error) {
rname := pr.newReader(pkgbits.RelocName, idx, pkgbits.SyncObject1)
_, sym := rname.qualifiedIdent()
tag := pkgbits.CodeObj(rname.Code(pkgbits.SyncCodeObj))
@ -674,22 +689,25 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
assert(!sym.IsBlank())
switch sym.Pkg {
case types.BuiltinPkg, types.UnsafePkg:
return sym.Def.(ir.Node)
return sym.Def.(ir.Node), nil
}
if pri, ok := objReader[sym]; ok {
return pri.pr.objIdx(pri.idx, nil, explicits, shaped)
return pri.pr.objIdxMayFail(pri.idx, nil, explicits, shaped)
}
if sym.Pkg.Path == "runtime" {
return typecheck.LookupRuntime(sym.Name)
return typecheck.LookupRuntime(sym.Name), nil
}
base.Fatalf("unresolved stub: %v", sym)
}
dict := pr.objDictIdx(sym, idx, implicits, explicits, shaped)
dict, err := pr.objDictIdx(sym, idx, implicits, explicits, shaped)
if err != nil {
return nil, err
}
sym = dict.baseSym
if !sym.IsBlank() && sym.Def != nil {
return sym.Def.(*ir.Name)
return sym.Def.(*ir.Name), nil
}
r := pr.newReader(pkgbits.RelocObj, idx, pkgbits.SyncObject1)
@ -725,7 +743,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
name := do(ir.OTYPE, false)
setType(name, r.typ())
name.SetAlias(true)
return name
return name, nil
case pkgbits.ObjConst:
name := do(ir.OLITERAL, false)
@ -733,7 +751,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
val := FixValue(typ, r.Value())
setType(name, typ)
setValue(name, val)
return name
return name, nil
case pkgbits.ObjFunc:
if sym.Name == "init" {
@ -768,7 +786,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
}
rext.funcExt(name, nil)
return name
return name, nil
case pkgbits.ObjType:
name := do(ir.OTYPE, true)
@ -805,13 +823,13 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index, implicits, explicits []*types.Typ
r.needWrapper(typ)
}
return name
return name, nil
case pkgbits.ObjVar:
name := do(ir.ONAME, false)
setType(name, r.typ())
rext.varExt(name)
return name
return name, nil
}
}
@ -908,7 +926,7 @@ func shapify(targ *types.Type, basic bool) *types.Type {
}
// objDictIdx reads and returns the specified object dictionary.
func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) *readerDict {
func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, explicits []*types.Type, shaped bool) (*readerDict, error) {
r := pr.newReader(pkgbits.RelocObjDict, idx, pkgbits.SyncObject1)
dict := readerDict{
@ -919,7 +937,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex
nexplicits := r.Len()
if nimplicits > len(implicits) || nexplicits != len(explicits) {
base.Fatalf("%v has %v+%v params, but instantiated with %v+%v args", sym, nimplicits, nexplicits, len(implicits), len(explicits))
return nil, fmt.Errorf("%v has %v+%v params, but instantiated with %v+%v args", sym, nimplicits, nexplicits, len(implicits), len(explicits))
}
dict.targs = append(implicits[:nimplicits:nimplicits], explicits...)
@ -984,7 +1002,7 @@ func (pr *pkgReader) objDictIdx(sym *types.Sym, idx pkgbits.Index, implicits, ex
dict.itabs[i] = itabInfo{typ: r.typInfo(), iface: r.typInfo()}
}
return &dict
return &dict, nil
}
func (r *reader) typeParamNames() {
@ -2529,7 +2547,10 @@ func (pr *pkgReader) objDictName(idx pkgbits.Index, implicits, explicits []*type
base.Fatalf("unresolved stub: %v", sym)
}
dict := pr.objDictIdx(sym, idx, implicits, explicits, false)
dict, err := pr.objDictIdx(sym, idx, implicits, explicits, false)
if err != nil {
base.Fatalf("%v", err)
}
return pr.dictNameOf(dict)
}

View file

@ -80,7 +80,11 @@ func lookupFunction(pkg *types.Pkg, symName string) (*ir.Func, error) {
return nil, fmt.Errorf("func sym %v missing objReader", sym)
}
name := pri.pr.objIdx(pri.idx, nil, nil, false).(*ir.Name)
node, err := pri.pr.objIdxMayFail(pri.idx, nil, nil, false)
if err != nil {
return nil, fmt.Errorf("func sym %v lookup error: %w", sym, err)
}
name := node.(*ir.Name)
if name.Op() != ir.ONAME || name.Class != ir.PFUNC {
return nil, fmt.Errorf("func sym %v refers to non-function name: %v", sym, name)
}
@ -105,7 +109,11 @@ func lookupMethod(pkg *types.Pkg, symName string) (*ir.Func, error) {
return nil, fmt.Errorf("type sym %v missing objReader", typ)
}
name := pri.pr.objIdx(pri.idx, nil, nil, false).(*ir.Name)
node, err := pri.pr.objIdxMayFail(pri.idx, nil, nil, false)
if err != nil {
return nil, fmt.Errorf("func sym %v lookup error: %w", typ, err)
}
name := node.(*ir.Name)
if name.Op() != ir.OTYPE {
return nil, fmt.Errorf("type sym %v refers to non-type name: %v", typ, name)
}

View file

@ -41,16 +41,19 @@
package pgo
import (
"bufio"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/pgo/internal/graph"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"errors"
"fmt"
"internal/profile"
"io"
"os"
"sort"
"strconv"
"strings"
)
// IRGraph is a call graph with nodes pointing to IRs of functions and edges
@ -139,14 +142,54 @@ type Profile struct {
WeightedCG *IRGraph
}
// New generates a profile-graph from the profile.
var wantHdr = "GO PREPROFILE V1\n"
func isPreProfileFile(r *bufio.Reader) (bool, error) {
hdr, err := r.Peek(len(wantHdr))
if err == io.EOF {
// Empty file.
return false, nil
} else if err != nil {
return false, fmt.Errorf("error reading profile header: %w", err)
}
return string(hdr) == wantHdr, nil
}
// New generates a profile-graph from the profile or pre-processed profile.
func New(profileFile string) (*Profile, error) {
f, err := os.Open(profileFile)
if err != nil {
return nil, fmt.Errorf("error opening profile: %w", err)
}
defer f.Close()
p, err := profile.Parse(f)
r := bufio.NewReader(f)
isPreProf, err := isPreProfileFile(r)
if err != nil {
return nil, fmt.Errorf("error processing profile header: %w", err)
}
if isPreProf {
profile, err := processPreprof(r)
if err != nil {
return nil, fmt.Errorf("error processing preprocessed PGO profile: %w", err)
}
return profile, nil
}
profile, err := processProto(r)
if err != nil {
return nil, fmt.Errorf("error processing pprof PGO profile: %w", err)
}
return profile, nil
}
// processProto generates a profile-graph from the profile.
func processProto(r io.Reader) (*Profile, error) {
p, err := profile.Parse(r)
if errors.Is(err, profile.ErrNoData) {
// Treat a completely empty file the same as a profile with no
// samples: nothing to do.
@ -175,7 +218,7 @@ func New(profileFile string) (*Profile, error) {
return nil, fmt.Errorf(`profile does not contain a sample index with value/type "samples/count" or cpu/nanoseconds"`)
}
g := graph.NewGraph(p, &graph.Options{
g := profile.NewGraph(p, &profile.Options{
SampleValue: func(v []int64) int64 { return v[valueIndex] },
})
@ -198,45 +241,31 @@ func New(profileFile string) (*Profile, error) {
}, nil
}
// createNamedEdgeMap builds a map of callsite-callee edge weights from the
// profile-graph.
//
// Caller should ignore the profile if totalWeight == 0.
func createNamedEdgeMap(g *graph.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
seenStartLine := false
// Process graph and build various node and edge maps which will
// be consumed by AST walk.
weight := make(map[NamedCallEdge]int64)
for _, n := range g.Nodes {
seenStartLine = seenStartLine || n.Info.StartLine != 0
canonicalName := n.Info.Name
// Create the key to the nodeMapKey.
namedEdge := NamedCallEdge{
CallerName: canonicalName,
CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
}
for _, e := range n.Out {
totalWeight += e.WeightValue()
namedEdge.CalleeName = e.Dest.Info.Name
// Create new entry or increment existing entry.
weight[namedEdge] += e.WeightValue()
}
// processPreprof generates a profile-graph from the pre-procesed profile.
func processPreprof(r io.Reader) (*Profile, error) {
namedEdgeMap, totalWeight, err := createNamedEdgeMapFromPreprocess(r)
if err != nil {
return nil, err
}
if totalWeight == 0 {
return nil, nil // accept but ignore profile with no samples.
}
// Create package-level call graph with weights from profile and IR.
wg := createIRGraph(namedEdgeMap)
return &Profile{
TotalWeight: totalWeight,
NamedEdgeMap: namedEdgeMap,
WeightedCG: wg,
}, nil
}
func postProcessNamedEdgeMap(weight map[NamedCallEdge]int64, weightVal int64) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
if weightVal == 0 {
return NamedEdgeMap{}, 0, nil // accept but ignore profile with no samples.
}
if !seenStartLine {
// TODO(prattmic): If Function.start_line is missing we could
// fall back to using absolute line numbers, which is better
// than nothing.
return NamedEdgeMap{}, 0, fmt.Errorf("profile missing Function.start_line data (Go version of profiled application too old? Go 1.20+ automatically adds this to profiles)")
}
byWeight := make([]NamedCallEdge, 0, len(weight))
for namedEdge := range weight {
byWeight = append(byWeight, namedEdge)
@ -261,9 +290,110 @@ func createNamedEdgeMap(g *graph.Graph) (edgeMap NamedEdgeMap, totalWeight int64
ByWeight: byWeight,
}
totalWeight = weightVal
return edgeMap, totalWeight, nil
}
// restore NodeMap information from a preprocessed profile.
// The reader can refer to the format of preprocessed profile in cmd/preprofile/main.go.
func createNamedEdgeMapFromPreprocess(r io.Reader) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
fileScanner := bufio.NewScanner(r)
fileScanner.Split(bufio.ScanLines)
weight := make(map[NamedCallEdge]int64)
if !fileScanner.Scan() {
if err := fileScanner.Err(); err != nil {
return NamedEdgeMap{}, 0, fmt.Errorf("error reading preprocessed profile: %w", err)
}
return NamedEdgeMap{}, 0, fmt.Errorf("preprocessed profile missing header")
}
if gotHdr := fileScanner.Text() + "\n"; gotHdr != wantHdr {
return NamedEdgeMap{}, 0, fmt.Errorf("preprocessed profile malformed header; got %q want %q", gotHdr, wantHdr)
}
for fileScanner.Scan() {
readStr := fileScanner.Text()
callerName := readStr
if !fileScanner.Scan() {
if err := fileScanner.Err(); err != nil {
return NamedEdgeMap{}, 0, fmt.Errorf("error reading preprocessed profile: %w", err)
}
return NamedEdgeMap{}, 0, fmt.Errorf("preprocessed profile entry missing callee")
}
calleeName := fileScanner.Text()
if !fileScanner.Scan() {
if err := fileScanner.Err(); err != nil {
return NamedEdgeMap{}, 0, fmt.Errorf("error reading preprocessed profile: %w", err)
}
return NamedEdgeMap{}, 0, fmt.Errorf("preprocessed profile entry missing weight")
}
readStr = fileScanner.Text()
split := strings.Split(readStr, " ")
if len(split) != 2 {
return NamedEdgeMap{}, 0, fmt.Errorf("preprocessed profile entry got %v want 2 fields", split)
}
co, _ := strconv.Atoi(split[0])
namedEdge := NamedCallEdge{
CallerName: callerName,
CalleeName: calleeName,
CallSiteOffset: co,
}
EWeight, _ := strconv.ParseInt(split[1], 10, 64)
weight[namedEdge] += EWeight
totalWeight += EWeight
}
return postProcessNamedEdgeMap(weight, totalWeight)
}
// createNamedEdgeMap builds a map of callsite-callee edge weights from the
// profile-graph.
//
// Caller should ignore the profile if totalWeight == 0.
func createNamedEdgeMap(g *profile.Graph) (edgeMap NamedEdgeMap, totalWeight int64, err error) {
seenStartLine := false
// Process graph and build various node and edge maps which will
// be consumed by AST walk.
weight := make(map[NamedCallEdge]int64)
for _, n := range g.Nodes {
seenStartLine = seenStartLine || n.Info.StartLine != 0
canonicalName := n.Info.Name
// Create the key to the nodeMapKey.
namedEdge := NamedCallEdge{
CallerName: canonicalName,
CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
}
for _, e := range n.Out {
totalWeight += e.WeightValue()
namedEdge.CalleeName = e.Dest.Info.Name
// Create new entry or increment existing entry.
weight[namedEdge] += e.WeightValue()
}
}
if !seenStartLine {
// TODO(prattmic): If Function.start_line is missing we could
// fall back to using absolute line numbers, which is better
// than nothing.
return NamedEdgeMap{}, 0, fmt.Errorf("profile missing Function.start_line data (Go version of profiled application too old? Go 1.20+ automatically adds this to profiles)")
}
return postProcessNamedEdgeMap(weight, totalWeight)
}
// initializeIRGraph builds the IRGraph by visiting all the ir.Func in decl list
// of a package.
func createIRGraph(namedEdgeMap NamedEdgeMap) *IRGraph {

View file

@ -1331,20 +1331,25 @@ func writeITab(lsym *obj.LSym, typ, iface *types.Type, allowNonImplement bool) {
// _ [4]byte
// fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
// }
o := objw.SymPtr(lsym, 0, writeType(iface), 0)
o = objw.SymPtr(lsym, o, writeType(typ), 0)
o = objw.Uint32(lsym, o, types.TypeHash(typ)) // copy of type hash
o += 4 // skip unused field
c := rttype.NewCursor(lsym, 0, rttype.ITab)
c.Field("Inter").WritePtr(writeType(iface))
c.Field("Type").WritePtr(writeType(typ))
c.Field("Hash").WriteUint32(types.TypeHash(typ)) // copy of type hash
var delta int64
c = c.Field("Fun")
if !completeItab {
// If typ doesn't implement iface, make method entries be zero.
o = objw.Uintptr(lsym, o, 0)
entries = entries[:0]
}
for _, fn := range entries {
o = objw.SymPtrWeak(lsym, o, fn, 0) // method pointer for each method
c.Elem(0).WriteUintptr(0)
} else {
var a rttype.ArrayCursor
a, delta = c.ModifyArray(len(entries))
for i, fn := range entries {
a.Elem(i).WritePtrWeak(fn) // method pointer for each method
}
}
// Nothing writes static itabs, so they are read only.
objw.Global(lsym, int32(o), int16(obj.DUPOK|obj.RODATA))
objw.Global(lsym, int32(rttype.ITab.Size()+delta), int16(obj.DUPOK|obj.RODATA))
lsym.Set(obj.AttrContentAddressable, true)
}

View file

@ -278,7 +278,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Type = obj.TYPE_REG
p.To.Reg = rd
case ssa.OpRISCV64ADD, ssa.OpRISCV64SUB, ssa.OpRISCV64SUBW, ssa.OpRISCV64XOR, ssa.OpRISCV64OR, ssa.OpRISCV64AND,
ssa.OpRISCV64SLL, ssa.OpRISCV64SRA, ssa.OpRISCV64SRAW, ssa.OpRISCV64SRL, ssa.OpRISCV64SRLW,
ssa.OpRISCV64SLL, ssa.OpRISCV64SLLW, ssa.OpRISCV64SRA, ssa.OpRISCV64SRAW, ssa.OpRISCV64SRL, ssa.OpRISCV64SRLW,
ssa.OpRISCV64SLT, ssa.OpRISCV64SLTU, ssa.OpRISCV64MUL, ssa.OpRISCV64MULW, ssa.OpRISCV64MULH,
ssa.OpRISCV64MULHU, ssa.OpRISCV64DIV, ssa.OpRISCV64DIVU, ssa.OpRISCV64DIVW,
ssa.OpRISCV64DIVUW, ssa.OpRISCV64REM, ssa.OpRISCV64REMU, ssa.OpRISCV64REMW,
@ -422,8 +422,8 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
p.To.Type = obj.TYPE_REG
p.To.Reg = v.Reg()
case ssa.OpRISCV64ADDI, ssa.OpRISCV64ADDIW, ssa.OpRISCV64XORI, ssa.OpRISCV64ORI, ssa.OpRISCV64ANDI,
ssa.OpRISCV64SLLI, ssa.OpRISCV64SRAI, ssa.OpRISCV64SRAIW, ssa.OpRISCV64SRLI, ssa.OpRISCV64SRLIW, ssa.OpRISCV64SLTI,
ssa.OpRISCV64SLTIU:
ssa.OpRISCV64SLLI, ssa.OpRISCV64SLLIW, ssa.OpRISCV64SRAI, ssa.OpRISCV64SRAIW,
ssa.OpRISCV64SRLI, ssa.OpRISCV64SRLIW, ssa.OpRISCV64SLTI, ssa.OpRISCV64SLTIU:
p := s.Prog(v.Op.Asm())
p.From.Type = obj.TYPE_CONST
p.From.Offset = v.AuxInt

View file

@ -42,6 +42,9 @@ var UncommonType *types.Type
var InterfaceSwitch *types.Type
var TypeAssert *types.Type
// Interface tables (itabs)
var ITab *types.Type
func Init() {
// Note: this has to be called explicitly instead of being
// an init function so it runs after the types package has
@ -64,6 +67,8 @@ func Init() {
InterfaceSwitch = fromReflect(reflect.TypeOf(abi.InterfaceSwitch{}))
TypeAssert = fromReflect(reflect.TypeOf(abi.TypeAssert{}))
ITab = fromReflect(reflect.TypeOf(abi.ITab{}))
// Make sure abi functions are correct. These functions are used
// by the linker which doesn't have the ability to do type layout,
// so we check the functions it uses here.
@ -80,6 +85,9 @@ func Init() {
if got, want := int64(abi.TFlagOff(ptrSize)), Type.OffsetOf("TFlag"); got != want {
base.Fatalf("abi.TFlagOff() == %d, want %d", got, want)
}
if got, want := int64(abi.ITabTypeOff(ptrSize)), ITab.OffsetOf("Type"); got != want {
base.Fatalf("abi.ITabTypeOff() == %d, want %d", got, want)
}
}
// fromReflect translates from a host type to the equivalent target type.
@ -154,6 +162,12 @@ func (c Cursor) WritePtr(target *obj.LSym) {
objw.SymPtr(c.lsym, int(c.offset), target, 0)
}
}
func (c Cursor) WritePtrWeak(target *obj.LSym) {
if c.typ.Kind() != types.TUINTPTR {
base.Fatalf("can't write ptr, it has kind %s", c.typ.Kind())
}
objw.SymPtrWeak(c.lsym, int(c.offset), target, 0)
}
func (c Cursor) WriteUintptr(val uint64) {
if c.typ.Kind() != types.TUINTPTR {
base.Fatalf("can't write uintptr, it has kind %s", c.typ.Kind())
@ -250,6 +264,17 @@ func (c Cursor) Field(name string) Cursor {
return Cursor{}
}
func (c Cursor) Elem(i int64) Cursor {
if c.typ.Kind() != types.TARRAY {
base.Fatalf("can't call Elem on non-array %v", c.typ)
}
if i < 0 || i >= c.typ.NumElem() {
base.Fatalf("element access out of bounds [%d] in [0:%d]", i, c.typ.NumElem())
}
elem := c.typ.Elem()
return Cursor{lsym: c.lsym, offset: c.offset + i*elem.Size(), typ: elem}
}
type ArrayCursor struct {
c Cursor // cursor pointing at first element
n int // number of elements

View file

@ -214,10 +214,10 @@
(Rsh64x(64|32|16|8) x y) && shiftIsBounded(v) => (SRA x y)
// Rotates.
(RotateLeft8 <t> x (MOVDconst [c])) => (Or8 (Lsh8x64 <t> x (MOVDconst [c&7])) (Rsh8Ux64 <t> x (MOVDconst [-c&7])))
(RotateLeft16 <t> x (MOVDconst [c])) => (Or16 (Lsh16x64 <t> x (MOVDconst [c&15])) (Rsh16Ux64 <t> x (MOVDconst [-c&15])))
(RotateLeft32 <t> x (MOVDconst [c])) => (Or32 (Lsh32x64 <t> x (MOVDconst [c&31])) (Rsh32Ux64 <t> x (MOVDconst [-c&31])))
(RotateLeft64 <t> x (MOVDconst [c])) => (Or64 (Lsh64x64 <t> x (MOVDconst [c&63])) (Rsh64Ux64 <t> x (MOVDconst [-c&63])))
(RotateLeft8 <t> x y) => (OR (SLL <t> x (ANDI [7] <y.Type> y)) (SRL <t> (ZeroExt8to64 x) (ANDI [7] <y.Type> (NEG <y.Type> y))))
(RotateLeft16 <t> x y) => (OR (SLL <t> x (ANDI [15] <y.Type> y)) (SRL <t> (ZeroExt16to64 x) (ANDI [15] <y.Type> (NEG <y.Type> y))))
(RotateLeft32 <t> x y) => (OR (SLLW <t> x y) (SRLW <t> x (NEG <y.Type> y)))
(RotateLeft64 <t> x y) => (OR (SLL <t> x y) (SRL <t> x (NEG <y.Type> y)))
(Less64 ...) => (SLT ...)
(Less32 x y) => (SLT (SignExt32to64 x) (SignExt32to64 y))
@ -733,6 +733,7 @@
(XOR (MOVDconst [val]) x) && is32Bit(val) => (XORI [val] x)
(SLL x (MOVDconst [val])) => (SLLI [int64(val&63)] x)
(SRL x (MOVDconst [val])) => (SRLI [int64(val&63)] x)
(SLLW x (MOVDconst [val])) => (SLLIW [int64(val&31)] x)
(SRLW x (MOVDconst [val])) => (SRLIW [int64(val&31)] x)
(SRA x (MOVDconst [val])) => (SRAI [int64(val&63)] x)
(SRAW x (MOVDconst [val])) => (SRAIW [int64(val&31)] x)

View file

@ -207,16 +207,18 @@ func init() {
{name: "MOVDnop", argLength: 1, reg: regInfo{inputs: []regMask{gpMask}, outputs: []regMask{gpMask}}, resultInArg0: true}, // nop, return arg0 in same register
// Shift ops
{name: "SLL", argLength: 2, reg: gp21, asm: "SLL"}, // arg0 << (aux1 & 63)
{name: "SRA", argLength: 2, reg: gp21, asm: "SRA"}, // arg0 >> (aux1 & 63), signed
{name: "SRAW", argLength: 2, reg: gp21, asm: "SRAW"}, // arg0 >> (aux1 & 31), signed
{name: "SRL", argLength: 2, reg: gp21, asm: "SRL"}, // arg0 >> (aux1 & 63), unsigned
{name: "SRLW", argLength: 2, reg: gp21, asm: "SRLW"}, // arg0 >> (aux1 & 31), unsigned
{name: "SLLI", argLength: 1, reg: gp11, asm: "SLLI", aux: "Int64"}, // arg0 << auxint, shift amount 0-63
{name: "SRAI", argLength: 1, reg: gp11, asm: "SRAI", aux: "Int64"}, // arg0 >> auxint, signed, shift amount 0-63
{name: "SRAIW", argLength: 1, reg: gp11, asm: "SRAIW", aux: "Int64"}, // arg0 >> auxint, signed, shift amount 0-31
{name: "SRLI", argLength: 1, reg: gp11, asm: "SRLI", aux: "Int64"}, // arg0 >> auxint, unsigned, shift amount 0-63
{name: "SRLIW", argLength: 1, reg: gp11, asm: "SRLIW", aux: "Int64"}, // arg0 >> auxint, unsigned, shift amount 0-31
{name: "SLL", argLength: 2, reg: gp21, asm: "SLL"}, // arg0 << (aux1 & 63), logical left shift
{name: "SLLW", argLength: 2, reg: gp21, asm: "SLLW"}, // arg0 << (aux1 & 31), logical left shift of 32 bit value, sign extended to 64 bits
{name: "SRA", argLength: 2, reg: gp21, asm: "SRA"}, // arg0 >> (aux1 & 63), arithmetic right shift
{name: "SRAW", argLength: 2, reg: gp21, asm: "SRAW"}, // arg0 >> (aux1 & 31), arithmetic right shift of 32 bit value, sign extended to 64 bits
{name: "SRL", argLength: 2, reg: gp21, asm: "SRL"}, // arg0 >> (aux1 & 63), logical right shift
{name: "SRLW", argLength: 2, reg: gp21, asm: "SRLW"}, // arg0 >> (aux1 & 31), logical right shift of 32 bit value, sign extended to 64 bits
{name: "SLLI", argLength: 1, reg: gp11, asm: "SLLI", aux: "Int64"}, // arg0 << auxint, shift amount 0-63, logical left shift
{name: "SLLIW", argLength: 1, reg: gp11, asm: "SLLIW", aux: "Int64"}, // arg0 << auxint, shift amount 0-31, logical left shift of 32 bit value, sign extended to 64 bits
{name: "SRAI", argLength: 1, reg: gp11, asm: "SRAI", aux: "Int64"}, // arg0 >> auxint, shift amount 0-63, arithmetic right shift
{name: "SRAIW", argLength: 1, reg: gp11, asm: "SRAIW", aux: "Int64"}, // arg0 >> auxint, shift amount 0-31, arithmetic right shift of 32 bit value, sign extended to 64 bits
{name: "SRLI", argLength: 1, reg: gp11, asm: "SRLI", aux: "Int64"}, // arg0 >> auxint, shift amount 0-63, logical right shift
{name: "SRLIW", argLength: 1, reg: gp11, asm: "SRLIW", aux: "Int64"}, // arg0 >> auxint, shift amount 0-31, logical right shift of 32 bit value, sign extended to 64 bits
// Bitwise ops
{name: "XOR", argLength: 2, reg: gp21, asm: "XOR", commutative: true}, // arg0 ^ arg1

View file

@ -2388,11 +2388,13 @@ const (
OpRISCV64MOVWUreg
OpRISCV64MOVDnop
OpRISCV64SLL
OpRISCV64SLLW
OpRISCV64SRA
OpRISCV64SRAW
OpRISCV64SRL
OpRISCV64SRLW
OpRISCV64SLLI
OpRISCV64SLLIW
OpRISCV64SRAI
OpRISCV64SRAIW
OpRISCV64SRLI
@ -32045,6 +32047,20 @@ var opcodeTable = [...]opInfo{
},
},
},
{
name: "SLLW",
argLen: 2,
asm: riscv.ASLLW,
reg: regInfo{
inputs: []inputInfo{
{0, 1006632944}, // X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30
{1, 1006632944}, // X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30
},
outputs: []outputInfo{
{0, 1006632944}, // X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30
},
},
},
{
name: "SRA",
argLen: 2,
@ -32115,6 +32131,20 @@ var opcodeTable = [...]opInfo{
},
},
},
{
name: "SLLIW",
auxType: auxInt64,
argLen: 1,
asm: riscv.ASLLIW,
reg: regInfo{
inputs: []inputInfo{
{0, 1006632944}, // X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30
},
outputs: []outputInfo{
{0, 1006632944}, // X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15 X16 X17 X18 X19 X20 X21 X22 X23 X24 X25 X26 X28 X29 X30
},
},
},
{
name: "SRAI",
auxType: auxInt64,

View file

@ -2144,7 +2144,7 @@ func canRotate(c *Config, bits int64) bool {
return false
}
switch c.arch {
case "386", "amd64", "arm64":
case "386", "amd64", "arm64", "riscv64":
return true
case "arm", "s390x", "ppc64", "ppc64le", "wasm", "loong64":
return bits >= 32

View file

@ -536,6 +536,8 @@ func rewriteValueRISCV64(v *Value) bool {
return rewriteValueRISCV64_OpRISCV64SLL(v)
case OpRISCV64SLLI:
return rewriteValueRISCV64_OpRISCV64SLLI(v)
case OpRISCV64SLLW:
return rewriteValueRISCV64_OpRISCV64SLLW(v)
case OpRISCV64SLT:
return rewriteValueRISCV64_OpRISCV64SLT(v)
case OpRISCV64SLTI:
@ -6070,6 +6072,24 @@ func rewriteValueRISCV64_OpRISCV64SLLI(v *Value) bool {
}
return false
}
func rewriteValueRISCV64_OpRISCV64SLLW(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
// match: (SLLW x (MOVDconst [val]))
// result: (SLLIW [int64(val&31)] x)
for {
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
break
}
val := auxIntToInt64(v_1.AuxInt)
v.reset(OpRISCV64SLLIW)
v.AuxInt = int64ToAuxInt(int64(val & 31))
v.AddArg(x)
return true
}
return false
}
func rewriteValueRISCV64_OpRISCV64SLT(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
@ -6644,112 +6664,102 @@ func rewriteValueRISCV64_OpRotateLeft16(v *Value) bool {
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (RotateLeft16 <t> x (MOVDconst [c]))
// result: (Or16 (Lsh16x64 <t> x (MOVDconst [c&15])) (Rsh16Ux64 <t> x (MOVDconst [-c&15])))
// match: (RotateLeft16 <t> x y)
// result: (OR (SLL <t> x (ANDI [15] <y.Type> y)) (SRL <t> (ZeroExt16to64 x) (ANDI [15] <y.Type> (NEG <y.Type> y))))
for {
t := v.Type
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
break
}
c := auxIntToInt64(v_1.AuxInt)
v.reset(OpOr16)
v0 := b.NewValue0(v.Pos, OpLsh16x64, t)
v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v1.AuxInt = int64ToAuxInt(c & 15)
y := v_1
v.reset(OpRISCV64OR)
v0 := b.NewValue0(v.Pos, OpRISCV64SLL, t)
v1 := b.NewValue0(v.Pos, OpRISCV64ANDI, y.Type)
v1.AuxInt = int64ToAuxInt(15)
v1.AddArg(y)
v0.AddArg2(x, v1)
v2 := b.NewValue0(v.Pos, OpRsh16Ux64, t)
v3 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v3.AuxInt = int64ToAuxInt(-c & 15)
v2.AddArg2(x, v3)
v2 := b.NewValue0(v.Pos, OpRISCV64SRL, t)
v3 := b.NewValue0(v.Pos, OpZeroExt16to64, typ.UInt64)
v3.AddArg(x)
v4 := b.NewValue0(v.Pos, OpRISCV64ANDI, y.Type)
v4.AuxInt = int64ToAuxInt(15)
v5 := b.NewValue0(v.Pos, OpRISCV64NEG, y.Type)
v5.AddArg(y)
v4.AddArg(v5)
v2.AddArg2(v3, v4)
v.AddArg2(v0, v2)
return true
}
return false
}
func rewriteValueRISCV64_OpRotateLeft32(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (RotateLeft32 <t> x (MOVDconst [c]))
// result: (Or32 (Lsh32x64 <t> x (MOVDconst [c&31])) (Rsh32Ux64 <t> x (MOVDconst [-c&31])))
// match: (RotateLeft32 <t> x y)
// result: (OR (SLLW <t> x y) (SRLW <t> x (NEG <y.Type> y)))
for {
t := v.Type
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
break
}
c := auxIntToInt64(v_1.AuxInt)
v.reset(OpOr32)
v0 := b.NewValue0(v.Pos, OpLsh32x64, t)
v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v1.AuxInt = int64ToAuxInt(c & 31)
v0.AddArg2(x, v1)
v2 := b.NewValue0(v.Pos, OpRsh32Ux64, t)
v3 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v3.AuxInt = int64ToAuxInt(-c & 31)
v2.AddArg2(x, v3)
v.AddArg2(v0, v2)
y := v_1
v.reset(OpRISCV64OR)
v0 := b.NewValue0(v.Pos, OpRISCV64SLLW, t)
v0.AddArg2(x, y)
v1 := b.NewValue0(v.Pos, OpRISCV64SRLW, t)
v2 := b.NewValue0(v.Pos, OpRISCV64NEG, y.Type)
v2.AddArg(y)
v1.AddArg2(x, v2)
v.AddArg2(v0, v1)
return true
}
return false
}
func rewriteValueRISCV64_OpRotateLeft64(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (RotateLeft64 <t> x (MOVDconst [c]))
// result: (Or64 (Lsh64x64 <t> x (MOVDconst [c&63])) (Rsh64Ux64 <t> x (MOVDconst [-c&63])))
// match: (RotateLeft64 <t> x y)
// result: (OR (SLL <t> x y) (SRL <t> x (NEG <y.Type> y)))
for {
t := v.Type
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
break
}
c := auxIntToInt64(v_1.AuxInt)
v.reset(OpOr64)
v0 := b.NewValue0(v.Pos, OpLsh64x64, t)
v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v1.AuxInt = int64ToAuxInt(c & 63)
v0.AddArg2(x, v1)
v2 := b.NewValue0(v.Pos, OpRsh64Ux64, t)
v3 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v3.AuxInt = int64ToAuxInt(-c & 63)
v2.AddArg2(x, v3)
v.AddArg2(v0, v2)
y := v_1
v.reset(OpRISCV64OR)
v0 := b.NewValue0(v.Pos, OpRISCV64SLL, t)
v0.AddArg2(x, y)
v1 := b.NewValue0(v.Pos, OpRISCV64SRL, t)
v2 := b.NewValue0(v.Pos, OpRISCV64NEG, y.Type)
v2.AddArg(y)
v1.AddArg2(x, v2)
v.AddArg2(v0, v1)
return true
}
return false
}
func rewriteValueRISCV64_OpRotateLeft8(v *Value) bool {
v_1 := v.Args[1]
v_0 := v.Args[0]
b := v.Block
typ := &b.Func.Config.Types
// match: (RotateLeft8 <t> x (MOVDconst [c]))
// result: (Or8 (Lsh8x64 <t> x (MOVDconst [c&7])) (Rsh8Ux64 <t> x (MOVDconst [-c&7])))
// match: (RotateLeft8 <t> x y)
// result: (OR (SLL <t> x (ANDI [7] <y.Type> y)) (SRL <t> (ZeroExt8to64 x) (ANDI [7] <y.Type> (NEG <y.Type> y))))
for {
t := v.Type
x := v_0
if v_1.Op != OpRISCV64MOVDconst {
break
}
c := auxIntToInt64(v_1.AuxInt)
v.reset(OpOr8)
v0 := b.NewValue0(v.Pos, OpLsh8x64, t)
v1 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v1.AuxInt = int64ToAuxInt(c & 7)
y := v_1
v.reset(OpRISCV64OR)
v0 := b.NewValue0(v.Pos, OpRISCV64SLL, t)
v1 := b.NewValue0(v.Pos, OpRISCV64ANDI, y.Type)
v1.AuxInt = int64ToAuxInt(7)
v1.AddArg(y)
v0.AddArg2(x, v1)
v2 := b.NewValue0(v.Pos, OpRsh8Ux64, t)
v3 := b.NewValue0(v.Pos, OpRISCV64MOVDconst, typ.UInt64)
v3.AuxInt = int64ToAuxInt(-c & 7)
v2.AddArg2(x, v3)
v2 := b.NewValue0(v.Pos, OpRISCV64SRL, t)
v3 := b.NewValue0(v.Pos, OpZeroExt8to64, typ.UInt64)
v3.AddArg(x)
v4 := b.NewValue0(v.Pos, OpRISCV64ANDI, y.Type)
v4.AuxInt = int64ToAuxInt(7)
v5 := b.NewValue0(v.Pos, OpRISCV64NEG, y.Type)
v5.AddArg(y)
v4.AddArg(v5)
v2.AddArg2(v3, v4)
v.AddArg2(v0, v2)
return true
}
return false
}
func rewriteValueRISCV64_OpRsh16Ux16(v *Value) bool {
v_1 := v.Args[1]

View file

@ -22,6 +22,7 @@ import (
"cmd/compile/internal/liveness"
"cmd/compile/internal/objw"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/rttype"
"cmd/compile/internal/ssa"
"cmd/compile/internal/staticdata"
"cmd/compile/internal/typecheck"
@ -4894,22 +4895,22 @@ func InitTables() {
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
return s.newValue2(ssa.OpRotateLeft8, types.Types[types.TUINT8], args[0], args[1])
},
sys.AMD64)
sys.AMD64, sys.RISCV64)
addF("math/bits", "RotateLeft16",
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
return s.newValue2(ssa.OpRotateLeft16, types.Types[types.TUINT16], args[0], args[1])
},
sys.AMD64)
sys.AMD64, sys.RISCV64)
addF("math/bits", "RotateLeft32",
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
return s.newValue2(ssa.OpRotateLeft32, types.Types[types.TUINT32], args[0], args[1])
},
sys.AMD64, sys.ARM, sys.ARM64, sys.S390X, sys.PPC64, sys.Wasm, sys.Loong64)
sys.AMD64, sys.ARM, sys.ARM64, sys.Loong64, sys.PPC64, sys.RISCV64, sys.S390X, sys.Wasm)
addF("math/bits", "RotateLeft64",
func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
return s.newValue2(ssa.OpRotateLeft64, types.Types[types.TUINT64], args[0], args[1])
},
sys.AMD64, sys.ARM64, sys.S390X, sys.PPC64, sys.Wasm, sys.Loong64)
sys.AMD64, sys.ARM64, sys.Loong64, sys.PPC64, sys.RISCV64, sys.S390X, sys.Wasm)
alias("math/bits", "RotateLeft", "math/bits", "RotateLeft64", p8...)
makeOnesCountAMD64 := func(op ssa.Op) func(s *state, n *ir.CallExpr, args []*ssa.Value) *ssa.Value {
@ -5537,7 +5538,7 @@ func (s *state) getClosureAndRcvr(fn *ir.SelectorExpr) (*ssa.Value, *ssa.Value)
i := s.expr(fn.X)
itab := s.newValue1(ssa.OpITab, types.Types[types.TUINTPTR], i)
s.nilCheck(itab)
itabidx := fn.Offset() + 2*int64(types.PtrSize) + 8 // offset of fun field in runtime.itab
itabidx := fn.Offset() + rttype.ITab.OffsetOf("Fun")
closure := s.newValue1I(ssa.OpOffPtr, s.f.Config.Types.UintptrPtr, itabidx, itab)
rcvr := s.newValue1(ssa.OpIData, s.f.Config.Types.BytePtr, i)
return closure, rcvr
@ -6522,7 +6523,7 @@ func (s *state) dynamicDottype(n *ir.DynamicTypeAssertExpr, commaok bool) (res,
targetItab = s.expr(n.ITab)
// TODO(mdempsky): Investigate whether compiling n.RType could be
// better than loading itab.typ.
target = s.load(byteptr, s.newValue1I(ssa.OpOffPtr, byteptr, int64(types.PtrSize), targetItab)) // itab.typ
target = s.load(byteptr, s.newValue1I(ssa.OpOffPtr, byteptr, rttype.ITab.OffsetOf("Type"), targetItab))
} else {
target = s.expr(n.RType)
}
@ -6580,7 +6581,7 @@ func (s *state) dottype1(pos src.XPos, src, dst *types.Type, iface, source, targ
return
}
// Load type out of itab, build interface with existing idata.
off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(types.PtrSize), itab)
off := s.newValue1I(ssa.OpOffPtr, byteptr, rttype.ITab.OffsetOf("Type"), itab)
typ := s.load(byteptr, off)
idata := s.newValue1(ssa.OpIData, byteptr, iface)
res = s.newValue2(ssa.OpIMake, dst, typ, idata)
@ -6590,7 +6591,7 @@ func (s *state) dottype1(pos src.XPos, src, dst *types.Type, iface, source, targ
s.startBlock(bOk)
// nonempty -> empty
// Need to load type from itab
off := s.newValue1I(ssa.OpOffPtr, byteptr, int64(types.PtrSize), itab)
off := s.newValue1I(ssa.OpOffPtr, byteptr, rttype.ITab.OffsetOf("Type"), itab)
s.vars[typVar] = s.load(byteptr, off)
s.endBlock()
@ -6644,7 +6645,7 @@ func (s *state) dottype1(pos src.XPos, src, dst *types.Type, iface, source, targ
s.startBlock(bNonNil)
typ := itab
if !src.IsEmptyInterface() {
typ = s.load(byteptr, s.newValue1I(ssa.OpOffPtr, byteptr, int64(types.PtrSize), itab))
typ = s.load(byteptr, s.newValue1I(ssa.OpOffPtr, byteptr, rttype.ITab.OffsetOf("Type"), itab))
}
// Check the cache first.
@ -6685,9 +6686,9 @@ func (s *state) dottype1(pos src.XPos, src, dst *types.Type, iface, source, targ
// Load hash from type or itab.
var hash *ssa.Value
if src.IsEmptyInterface() {
hash = s.newValue2(ssa.OpLoad, typs.UInt32, s.newValue1I(ssa.OpOffPtr, typs.UInt32Ptr, 2*s.config.PtrSize, typ), s.mem())
hash = s.newValue2(ssa.OpLoad, typs.UInt32, s.newValue1I(ssa.OpOffPtr, typs.UInt32Ptr, rttype.Type.OffsetOf("Hash"), typ), s.mem())
} else {
hash = s.newValue2(ssa.OpLoad, typs.UInt32, s.newValue1I(ssa.OpOffPtr, typs.UInt32Ptr, 2*s.config.PtrSize, itab), s.mem())
hash = s.newValue2(ssa.OpLoad, typs.UInt32, s.newValue1I(ssa.OpOffPtr, typs.UInt32Ptr, rttype.ITab.OffsetOf("Hash"), itab), s.mem())
}
hash = s.newValue1(zext, typs.Uintptr, hash)
s.vars[hashVar] = hash
@ -7380,7 +7381,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
if b.Pos == src.NoXPos {
b.Pos = p.Pos // It needs a file, otherwise a no-file non-zero line causes confusion. See #35652.
if b.Pos == src.NoXPos {
b.Pos = pp.Text.Pos // Sometimes p.Pos is empty. See #35695.
b.Pos = s.pp.Text.Pos // Sometimes p.Pos is empty. See #35695.
}
}
b.Pos = b.Pos.WithBogusLine() // Debuggers are not good about infinite loops, force a change in line number
@ -7415,14 +7416,14 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
// still be inside the function in question. So if
// it ends in a call which doesn't return, add a
// nop (which will never execute) after the call.
Arch.Ginsnop(pp)
Arch.Ginsnop(s.pp)
}
if openDeferInfo != nil {
// When doing open-coded defers, generate a disconnected call to
// deferreturn and a return. This will be used to during panic
// recovery to unwind the stack and return back to the runtime.
s.pp.NextLive = s.livenessMap.DeferReturn
p := pp.Prog(obj.ACALL)
p := s.pp.Prog(obj.ACALL)
p.To.Type = obj.TYPE_MEM
p.To.Name = obj.NAME_EXTERN
p.To.Sym = ir.Syms.Deferreturn
@ -7439,7 +7440,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
}
}
pp.Prog(obj.ARET)
s.pp.Prog(obj.ARET)
}
if inlMarks != nil {
@ -7448,7 +7449,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
// We have some inline marks. Try to find other instructions we're
// going to emit anyway, and use those instructions instead of the
// inline marks.
for p := pp.Text; p != nil; p = p.Link {
for p := s.pp.Text; p != nil; p = p.Link {
if p.As == obj.ANOP || p.As == obj.AFUNCDATA || p.As == obj.APCDATA || p.As == obj.ATEXT || p.As == obj.APCALIGN || Arch.LinkArch.Family == sys.Wasm {
// Don't use 0-sized instructions as inline marks, because we need
// to identify inline mark instructions by pc offset.
@ -7466,16 +7467,16 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
hasCall = true
}
pos := p.Pos.AtColumn1()
s := inlMarksByPos[pos]
if len(s) == 0 {
marks := inlMarksByPos[pos]
if len(marks) == 0 {
continue
}
for _, m := range s {
for _, m := range marks {
// We found an instruction with the same source position as
// some of the inline marks.
// Use this instruction instead.
p.Pos = p.Pos.WithIsStmt() // promote position to a statement
pp.CurFunc.LSym.Func().AddInlMark(p, inlMarks[m])
s.pp.CurFunc.LSym.Func().AddInlMark(p, inlMarks[m])
// Make the inline mark a real nop, so it doesn't generate any code.
m.As = obj.ANOP
m.Pos = src.NoXPos
@ -7487,7 +7488,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
// Any unmatched inline marks now need to be added to the inlining tree (and will generate a nop instruction).
for _, p := range inlMarkList {
if p.As != obj.ANOP {
pp.CurFunc.LSym.Func().AddInlMark(p, inlMarks[p])
s.pp.CurFunc.LSym.Func().AddInlMark(p, inlMarks[p])
}
}
@ -7498,27 +7499,27 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
// equal to the start of the function.
// This ensures that runtime.FuncForPC(uintptr(reflect.ValueOf(fn).Pointer())).Name()
// returns the right answer. See issue 58300.
for p := pp.Text; p != nil; p = p.Link {
for p := s.pp.Text; p != nil; p = p.Link {
if p.As == obj.AFUNCDATA || p.As == obj.APCDATA || p.As == obj.ATEXT || p.As == obj.ANOP {
continue
}
if base.Ctxt.PosTable.Pos(p.Pos).Base().InliningIndex() >= 0 {
// Make a real (not 0-sized) nop.
nop := Arch.Ginsnop(pp)
nop := Arch.Ginsnop(s.pp)
nop.Pos = e.curfn.Pos().WithIsStmt()
// Unfortunately, Ginsnop puts the instruction at the
// end of the list. Move it up to just before p.
// Unlink from the current list.
for x := pp.Text; x != nil; x = x.Link {
for x := s.pp.Text; x != nil; x = x.Link {
if x.Link == nop {
x.Link = nop.Link
break
}
}
// Splice in right before p.
for x := pp.Text; x != nil; x = x.Link {
for x := s.pp.Text; x != nil; x = x.Link {
if x.Link == p {
nop.Link = p
x.Link = nop
@ -7588,13 +7589,13 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
// Add to list of jump tables to be resolved at assembly time.
// The assembler converts from *Prog entries to absolute addresses
// once it knows instruction byte offsets.
fi := pp.CurFunc.LSym.Func()
fi := s.pp.CurFunc.LSym.Func()
fi.JumpTables = append(fi.JumpTables, obj.JumpTable{Sym: jt.Aux.(*obj.LSym), Targets: targets})
}
if e.log { // spew to stdout
filename := ""
for p := pp.Text; p != nil; p = p.Link {
for p := s.pp.Text; p != nil; p = p.Link {
if p.Pos.IsKnown() && p.InnermostFilename() != filename {
filename = p.InnermostFilename()
f.Logf("# %s\n", filename)
@ -7616,7 +7617,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
buf.WriteString("<code>")
buf.WriteString("<dl class=\"ssa-gen\">")
filename := ""
for p := pp.Text; p != nil; p = p.Link {
for p := s.pp.Text; p != nil; p = p.Link {
// Don't spam every line with the file name, which is often huge.
// Only print changes, and "unknown" is not a change.
if p.Pos.IsKnown() && p.InnermostFilename() != filename {
@ -7664,7 +7665,7 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
var allPosOld []src.Pos
var allPos []src.Pos
for p := pp.Text; p != nil; p = p.Link {
for p := s.pp.Text; p != nil; p = p.Link {
if p.Pos.IsKnown() {
allPos = allPos[:0]
p.Ctxt.AllPos(p.Pos, func(pos src.Pos) { allPos = append(allPos, pos) })

View file

@ -14,8 +14,16 @@ import (
"testing"
)
type devirtualization struct {
pos string
callee string
}
const profFileName = "devirt.pprof"
const preProfFileName = "devirt.pprof.node_map"
// testPGODevirtualize tests that specific PGO devirtualize rewrites are performed.
func testPGODevirtualize(t *testing.T, dir string) {
func testPGODevirtualize(t *testing.T, dir string, want []devirtualization, pgoProfileName string) {
testenv.MustHaveGoRun(t)
t.Parallel()
@ -23,7 +31,7 @@ func testPGODevirtualize(t *testing.T, dir string) {
// Add a go.mod so we have a consistent symbol names in this temp dir.
goMod := fmt.Sprintf(`module %s
go 1.19
go 1.21
`, pkg)
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0644); err != nil {
t.Fatalf("error writing go.mod: %v", err)
@ -40,7 +48,7 @@ go 1.19
}
// Build the test with the profile.
pprof := filepath.Join(dir, "devirt.pprof")
pprof := filepath.Join(dir, pgoProfileName)
gcflag := fmt.Sprintf("-gcflags=-m=2 -pgoprofile=%s -d=pgodebug=3", pprof)
out := filepath.Join(dir, "test.exe")
cmd = testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), "test", "-o", out, gcflag, "."))
@ -60,51 +68,6 @@ go 1.19
t.Fatalf("error starting go test: %v", err)
}
type devirtualization struct {
pos string
callee string
}
want := []devirtualization{
// ExerciseIface
{
pos: "./devirt.go:101:20",
callee: "mult.Mult.Multiply",
},
{
pos: "./devirt.go:101:39",
callee: "Add.Add",
},
// ExerciseFuncConcrete
{
pos: "./devirt.go:173:36",
callee: "AddFn",
},
{
pos: "./devirt.go:173:15",
callee: "mult.MultFn",
},
// ExerciseFuncField
{
pos: "./devirt.go:207:35",
callee: "AddFn",
},
{
pos: "./devirt.go:207:19",
callee: "mult.MultFn",
},
// ExerciseFuncClosure
// TODO(prattmic): Closure callees not implemented.
//{
// pos: "./devirt.go:249:27",
// callee: "AddClosure.func1",
//},
//{
// pos: "./devirt.go:249:15",
// callee: "mult.MultClosure.func1",
//},
}
got := make(map[devirtualization]struct{})
devirtualizedLine := regexp.MustCompile(`(.*): PGO devirtualizing \w+ call .* to (.*)`)
@ -166,11 +129,199 @@ func TestPGODevirtualize(t *testing.T) {
if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
t.Fatalf("error creating dir: %v", err)
}
for _, file := range []string{"devirt.go", "devirt_test.go", "devirt.pprof", filepath.Join("mult.pkg", "mult.go")} {
for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s: %v", file, err)
}
}
testPGODevirtualize(t, dir)
want := []devirtualization{
// ExerciseIface
{
pos: "./devirt.go:101:20",
callee: "mult.Mult.Multiply",
},
{
pos: "./devirt.go:101:39",
callee: "Add.Add",
},
// ExerciseFuncConcrete
{
pos: "./devirt.go:173:36",
callee: "AddFn",
},
{
pos: "./devirt.go:173:15",
callee: "mult.MultFn",
},
// ExerciseFuncField
{
pos: "./devirt.go:207:35",
callee: "AddFn",
},
{
pos: "./devirt.go:207:19",
callee: "mult.MultFn",
},
// ExerciseFuncClosure
// TODO(prattmic): Closure callees not implemented.
//{
// pos: "./devirt.go:249:27",
// callee: "AddClosure.func1",
//},
//{
// pos: "./devirt.go:249:15",
// callee: "mult.MultClosure.func1",
//},
}
testPGODevirtualize(t, dir, want, profFileName)
}
// TestPGOPreprocessDevirtualize tests that specific functions are devirtualized when PGO
// is applied to the exact source that was profiled. The input profile is PGO preprocessed file.
func TestPGOPreprocessDevirtualize(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("error getting wd: %v", err)
}
srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
// Copy the module to a scratch location so we can add a go.mod.
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
t.Fatalf("error creating dir: %v", err)
}
for _, file := range []string{"devirt.go", "devirt_test.go", preProfFileName, filepath.Join("mult.pkg", "mult.go")} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s: %v", file, err)
}
}
want := []devirtualization{
// ExerciseIface
{
pos: "./devirt.go:101:20",
callee: "mult.Mult.Multiply",
},
{
pos: "./devirt.go:101:39",
callee: "Add.Add",
},
// ExerciseFuncConcrete
{
pos: "./devirt.go:173:36",
callee: "AddFn",
},
{
pos: "./devirt.go:173:15",
callee: "mult.MultFn",
},
// ExerciseFuncField
{
pos: "./devirt.go:207:35",
callee: "AddFn",
},
{
pos: "./devirt.go:207:19",
callee: "mult.MultFn",
},
// ExerciseFuncClosure
// TODO(prattmic): Closure callees not implemented.
//{
// pos: "./devirt.go:249:27",
// callee: "AddClosure.func1",
//},
//{
// pos: "./devirt.go:249:15",
// callee: "mult.MultClosure.func1",
//},
}
testPGODevirtualize(t, dir, want, preProfFileName)
}
// Regression test for https://go.dev/issue/65615. If a target function changes
// from non-generic to generic we can't devirtualize it (don't know the type
// parameters), but the compiler should not crash.
func TestLookupFuncGeneric(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("error getting wd: %v", err)
}
srcDir := filepath.Join(wd, "testdata", "pgo", "devirtualize")
// Copy the module to a scratch location so we can add a go.mod.
dir := t.TempDir()
if err := os.Mkdir(filepath.Join(dir, "mult.pkg"), 0755); err != nil {
t.Fatalf("error creating dir: %v", err)
}
for _, file := range []string{"devirt.go", "devirt_test.go", profFileName, filepath.Join("mult.pkg", "mult.go")} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s: %v", file, err)
}
}
// Change MultFn from a concrete function to a parameterized function.
if err := convertMultToGeneric(filepath.Join(dir, "mult.pkg", "mult.go")); err != nil {
t.Fatalf("error editing mult.go: %v", err)
}
// Same as TestPGODevirtualize except for MultFn, which we cannot
// devirtualize to because it has become generic.
//
// Note that the important part of this test is that the build is
// successful, not the specific devirtualizations.
want := []devirtualization{
// ExerciseIface
{
pos: "./devirt.go:101:20",
callee: "mult.Mult.Multiply",
},
{
pos: "./devirt.go:101:39",
callee: "Add.Add",
},
// ExerciseFuncConcrete
{
pos: "./devirt.go:173:36",
callee: "AddFn",
},
// ExerciseFuncField
{
pos: "./devirt.go:207:35",
callee: "AddFn",
},
// ExerciseFuncClosure
// TODO(prattmic): Closure callees not implemented.
//{
// pos: "./devirt.go:249:27",
// callee: "AddClosure.func1",
//},
//{
// pos: "./devirt.go:249:15",
// callee: "mult.MultClosure.func1",
//},
}
testPGODevirtualize(t, dir, want, profFileName)
}
var multFnRe = regexp.MustCompile(`func MultFn\(a, b int64\) int64`)
func convertMultToGeneric(path string) error {
content, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("error opening: %w", err)
}
if !multFnRe.Match(content) {
return fmt.Errorf("MultFn not found; update regexp?")
}
// Users of MultFn shouldn't need adjustment, type inference should
// work OK.
content = multFnRe.ReplaceAll(content, []byte(`func MultFn[T int32|int64](a, b T) T`))
return os.WriteFile(path, content, 0644)
}

View file

@ -18,6 +18,9 @@ import (
"testing"
)
const profFile = "inline_hot.pprof"
const preProfFile = "inline_hot.pprof.node_map"
func buildPGOInliningTest(t *testing.T, dir string, gcflag string) []byte {
const pkg = "example.com/pgo/inline"
@ -43,7 +46,7 @@ go 1.19
}
// testPGOIntendedInlining tests that specific functions are inlined.
func testPGOIntendedInlining(t *testing.T, dir string) {
func testPGOIntendedInlining(t *testing.T, dir string, profFile string) {
testenv.MustHaveGoRun(t)
t.Parallel()
@ -86,8 +89,7 @@ func testPGOIntendedInlining(t *testing.T, dir string) {
// Build the test with the profile. Use a smaller threshold to test.
// TODO: maybe adjust the test to work with default threshold.
pprof := filepath.Join(dir, "inline_hot.pprof")
gcflag := fmt.Sprintf("-m -m -pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90", pprof)
gcflag := fmt.Sprintf("-m -m -pgoprofile=%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90", profFile)
out := buildPGOInliningTest(t, dir, gcflag)
scanner := bufio.NewScanner(bytes.NewReader(out))
@ -155,13 +157,34 @@ func TestPGOIntendedInlining(t *testing.T) {
// Copy the module to a scratch location so we can add a go.mod.
dir := t.TempDir()
for _, file := range []string{"inline_hot.go", "inline_hot_test.go", "inline_hot.pprof"} {
for _, file := range []string{"inline_hot.go", "inline_hot_test.go", profFile} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s: %v", file, err)
}
}
testPGOIntendedInlining(t, dir)
testPGOIntendedInlining(t, dir, profFile)
}
// TestPGOIntendedInlining tests that specific functions are inlined when PGO
// is applied to the exact source that was profiled.
func TestPGOPreprocessInlining(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("error getting wd: %v", err)
}
srcDir := filepath.Join(wd, "testdata/pgo/inline")
// Copy the module to a scratch location so we can add a go.mod.
dir := t.TempDir()
for _, file := range []string{"inline_hot.go", "inline_hot_test.go", preProfFile} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s: %v", file, err)
}
}
testPGOIntendedInlining(t, dir, preProfFile)
}
// TestPGOIntendedInlining tests that specific functions are inlined when PGO
@ -177,7 +200,7 @@ func TestPGOIntendedInliningShiftedLines(t *testing.T) {
dir := t.TempDir()
// Copy most of the files unmodified.
for _, file := range []string{"inline_hot_test.go", "inline_hot.pprof"} {
for _, file := range []string{"inline_hot_test.go", profFile} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s : %v", file, err)
}
@ -209,7 +232,7 @@ func TestPGOIntendedInliningShiftedLines(t *testing.T) {
dst.Close()
testPGOIntendedInlining(t, dir)
testPGOIntendedInlining(t, dir, profFile)
}
// TestPGOSingleIndex tests that the sample index can not be 1 and compilation
@ -239,15 +262,15 @@ func TestPGOSingleIndex(t *testing.T) {
// Copy the module to a scratch location so we can add a go.mod.
dir := t.TempDir()
originalPprofFile, err := os.Open(filepath.Join(srcDir, "inline_hot.pprof"))
originalPprofFile, err := os.Open(filepath.Join(srcDir, profFile))
if err != nil {
t.Fatalf("error opening inline_hot.pprof: %v", err)
t.Fatalf("error opening %v: %v", profFile, err)
}
defer originalPprofFile.Close()
p, err := profile.Parse(originalPprofFile)
if err != nil {
t.Fatalf("error parsing inline_hot.pprof: %v", err)
t.Fatalf("error parsing %v: %v", profFile, err)
}
// Move the samples count value-type to the 0 index.
@ -258,14 +281,14 @@ func TestPGOSingleIndex(t *testing.T) {
s.Value = []int64{s.Value[tc.originalIndex]}
}
modifiedPprofFile, err := os.Create(filepath.Join(dir, "inline_hot.pprof"))
modifiedPprofFile, err := os.Create(filepath.Join(dir, profFile))
if err != nil {
t.Fatalf("error creating inline_hot.pprof: %v", err)
t.Fatalf("error creating %v: %v", profFile, err)
}
defer modifiedPprofFile.Close()
if err := p.Write(modifiedPprofFile); err != nil {
t.Fatalf("error writing inline_hot.pprof: %v", err)
t.Fatalf("error writing %v: %v", profFile, err)
}
for _, file := range []string{"inline_hot.go", "inline_hot_test.go"} {
@ -274,7 +297,7 @@ func TestPGOSingleIndex(t *testing.T) {
}
}
testPGOIntendedInlining(t, dir)
testPGOIntendedInlining(t, dir, profFile)
})
}
}
@ -312,13 +335,13 @@ func TestPGOHash(t *testing.T) {
// Copy the module to a scratch location so we can add a go.mod.
dir := t.TempDir()
for _, file := range []string{"inline_hot.go", "inline_hot_test.go", "inline_hot.pprof"} {
for _, file := range []string{"inline_hot.go", "inline_hot_test.go", profFile} {
if err := copyFile(filepath.Join(dir, file), filepath.Join(srcDir, file)); err != nil {
t.Fatalf("error copying %s: %v", file, err)
}
}
pprof := filepath.Join(dir, "inline_hot.pprof")
pprof := filepath.Join(dir, profFile)
// build with -trimpath so the source location (thus the hash)
// does not depend on the temporary directory path.
gcflag0 := fmt.Sprintf("-pgoprofile=%s -trimpath %s=>%s -d=pgoinlinebudget=160,pgoinlinecdfthreshold=90,pgodebug=1", pprof, dir, pkg)

View file

@ -0,0 +1,52 @@
GO PREPROFILE V1
example.com/pgo/devirtualize.ExerciseFuncClosure
example.com/pgo/devirtualize/mult%2epkg.MultClosure.func1
18 93
example.com/pgo/devirtualize.ExerciseIface
example.com/pgo/devirtualize/mult%2epkg.NegMult.Multiply
49 4
example.com/pgo/devirtualize.ExerciseFuncConcrete
example.com/pgo/devirtualize.AddFn
48 103
example.com/pgo/devirtualize.ExerciseFuncField
example.com/pgo/devirtualize/mult%2epkg.NegMultFn
23 8
example.com/pgo/devirtualize.ExerciseFuncField
example.com/pgo/devirtualize/mult%2epkg.MultFn
23 94
example.com/pgo/devirtualize.ExerciseIface
example.com/pgo/devirtualize/mult%2epkg.Mult.Multiply
49 40
example.com/pgo/devirtualize.ExerciseIface
example.com/pgo/devirtualize.Add.Add
49 55
example.com/pgo/devirtualize.ExerciseFuncConcrete
example.com/pgo/devirtualize/mult%2epkg.NegMultFn
48 8
example.com/pgo/devirtualize.ExerciseFuncClosure
example.com/pgo/devirtualize/mult%2epkg.NegMultClosure.func1
18 10
example.com/pgo/devirtualize.ExerciseIface
example.com/pgo/devirtualize.Sub.Add
49 7
example.com/pgo/devirtualize.ExerciseFuncField
example.com/pgo/devirtualize.AddFn
23 101
example.com/pgo/devirtualize.ExerciseFuncField
example.com/pgo/devirtualize.SubFn
23 12
example.com/pgo/devirtualize.BenchmarkDevirtFuncConcrete
example.com/pgo/devirtualize.ExerciseFuncConcrete
1 2
example.com/pgo/devirtualize.ExerciseFuncConcrete
example.com/pgo/devirtualize/mult%2epkg.MultFn
48 91
example.com/pgo/devirtualize.ExerciseFuncConcrete
example.com/pgo/devirtualize.SubFn
48 5
example.com/pgo/devirtualize.ExerciseFuncClosure
example.com/pgo/devirtualize.Add.Add
18 92
example.com/pgo/devirtualize.ExerciseFuncClosure
example.com/pgo/devirtualize.Sub.Add
18 14

View file

@ -0,0 +1,13 @@
GO PREPROFILE V1
example.com/pgo/inline.benchmarkB
example.com/pgo/inline.A
18 1
example.com/pgo/inline.(*BS).NS
example.com/pgo/inline.T
8 3
example.com/pgo/inline.(*BS).NS
example.com/pgo/inline.T
13 2
example.com/pgo/inline.A
example.com/pgo/inline.(*BS).NS
7 129

View file

@ -34,7 +34,7 @@ func AllowsGoVersion(major, minor int) bool {
}
// ParseLangFlag verifies that the -lang flag holds a valid value, and
// exits if not. It initializes data used by langSupported.
// exits if not. It initializes data used by AllowsGoVersion.
func ParseLangFlag() {
if base.Flag.Lang == "" {
return
@ -59,6 +59,10 @@ func ParseLangFlag() {
// parseLang parses a -lang option into a langVer.
func parseLang(s string) (lang, error) {
if s == "go1" { // cmd/go's new spelling of "go1.0" (#65528)
s = "go1.0"
}
matches := goVersionRE.FindStringSubmatch(s)
if matches == nil {
return lang{}, fmt.Errorf(`should be something like "go1.12"`)

View file

@ -21,11 +21,14 @@ type Alias struct {
// NewAlias creates a new Alias type with the given type name and rhs.
// rhs must not be nil.
func NewAlias(obj *TypeName, rhs Type) *Alias {
return (*Checker)(nil).newAlias(obj, rhs)
alias := (*Checker)(nil).newAlias(obj, rhs)
// Ensure that alias.actual is set (#65455).
unalias(alias)
return alias
}
func (a *Alias) Obj() *TypeName { return a.obj }
func (a *Alias) Underlying() Type { return a.actual.Underlying() }
func (a *Alias) Underlying() Type { return unalias(a).Underlying() }
func (a *Alias) String() string { return TypeString(a, nil) }
// Type accessors
@ -36,24 +39,26 @@ func (a *Alias) String() string { return TypeString(a, nil) }
// Consequently, the result is never an alias type.
func Unalias(t Type) Type {
if a0, _ := t.(*Alias); a0 != nil {
if a0.actual != nil {
return a0.actual
}
for a := a0; ; {
t = a.fromRHS
a, _ = t.(*Alias)
if a == nil {
break
}
}
if t == nil {
panic(fmt.Sprintf("non-terminated alias %s", a0.obj.name))
}
a0.actual = t
return unalias(a0)
}
return t
}
func unalias(a0 *Alias) Type {
if a0.actual != nil {
return a0.actual
}
var t Type
for a := a0; a != nil; a, _ = t.(*Alias) {
t = a.fromRHS
}
if t == nil {
panic(fmt.Sprintf("non-terminated alias %s", a0.obj.name))
}
a0.actual = t
return t
}
// asNamed returns t as *Named if that is t's
// actual type. It returns nil otherwise.
func asNamed(t Type) *Named {

View file

@ -2195,6 +2195,12 @@ func TestIssue61737(t *testing.T) {
iface.NumMethods() // unlike go/types, there is no Complete() method, so we complete implicitly
}
func TestNewAlias_Issue65455(t *testing.T) {
obj := NewTypeName(nopos, nil, "A", nil)
alias := NewAlias(obj, Typ[Int])
alias.Underlying() // must not panic
}
func TestIssue15305(t *testing.T) {
const src = "package p; func f() int16; var _ = f(undef)"
f := mustParse(src)

View file

@ -22,7 +22,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
// append is the only built-in that permits the use of ... for the last argument
bin := predeclaredFuncs[id]
if call.HasDots && id != _Append {
if hasDots(call) && id != _Append {
//check.errorf(call.Ellipsis, invalidOp + "invalid use of ... with built-in %s", bin.name)
check.errorf(call,
InvalidDotDotDot,
@ -114,7 +114,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
// spec: "As a special case, append also accepts a first argument assignable
// to type []byte with a second argument of string type followed by ... .
// This form appends the bytes of the string.
if nargs == 2 && call.HasDots {
if nargs == 2 && hasDots(call) {
if ok, _ := x.assignableTo(check, NewSlice(universeByte), nil); ok {
y := args[1]
if t := coreString(y.typ); t != nil && isString(t) {
@ -1034,14 +1034,3 @@ func arrayPtrDeref(typ Type) Type {
}
return typ
}
// unparen returns e with any enclosing parentheses stripped.
func unparen(e syntax.Expr) syntax.Expr {
for {
p, ok := e.(*syntax.ParenExpr)
if !ok {
return e
}
e = p.X
}
}

View file

@ -209,7 +209,7 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind {
break
}
}
if call.HasDots {
if hasDots(call) {
check.errorf(call.ArgList[0], BadDotDotDotSyntax, "invalid use of ... in conversion to %s", T)
break
}
@ -468,7 +468,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
nargs := len(args)
npars := sig.params.Len()
ddd := call.HasDots
ddd := hasDots(call)
// set up parameters
sigParams := sig.params // adjusted for variadic functions (may be nil for empty parameter lists!)
@ -824,22 +824,8 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName
if isInterfacePtr(x.typ) {
why = check.interfacePtrError(x.typ)
} else {
why = check.sprintf("type %s has no field or method %s", x.typ, sel)
// check if there's a field or method with different capitalization
if obj, _, _ = lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel, true); obj != nil {
var what string // empty or description with trailing space " " (default case, should never be reached)
switch obj.(type) {
case *Var:
what = "field "
case *Func:
what = "method "
}
if samePkg(obj.Pkg(), check.pkg) || obj.Exported() {
why = check.sprintf("%s, but does have %s%s", why, what, obj.Name())
} else if obj.Name() == sel {
why = check.sprintf("%s%s is not exported", what, obj.Name())
}
}
alt, _, _ := lookupFieldOrMethod(x.typ, x.mode == variable, check.pkg, sel, true)
why = check.lookupError(x.typ, sel, alt, false)
}
check.errorf(e.Sel, MissingFieldOrMethod, "%s.%s undefined (%s)", x.expr, sel, why)
goto Error

View file

@ -0,0 +1,113 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file implements support functions for error messages.
package types2
// lookupError returns a case-specific error when a lookup of selector sel in the
// given type fails but an object with alternative spelling (case folding) is found.
// If structLit is set, the error message is specifically for struct literal fields.
func (check *Checker) lookupError(typ Type, sel string, obj Object, structLit bool) string {
// Provide more detail if there is an unexported object, or one with different capitalization.
// If selector and object are in the same package (==), export doesn't matter, otherwise (!=) it does.
// Messages depend on whether it's a general lookup or a field lookup in a struct literal.
//
// case sel pkg have message (examples for general lookup)
// ---------------------------------------------------------------------------------------------------------
// ok x.Foo == Foo
// misspelled x.Foo == FoO type X has no field or method Foo, but does have field FoO
// misspelled x.Foo == foo type X has no field or method Foo, but does have field foo
// misspelled x.Foo == foO type X has no field or method Foo, but does have field foO
//
// misspelled x.foo == Foo type X has no field or method foo, but does have field Foo
// misspelled x.foo == FoO type X has no field or method foo, but does have field FoO
// ok x.foo == foo
// misspelled x.foo == foO type X has no field or method foo, but does have field foO
//
// ok x.Foo != Foo
// misspelled x.Foo != FoO type X has no field or method Foo, but does have field FoO
// unexported x.Foo != foo type X has no field or method Foo, but does have unexported field foo
// missing x.Foo != foO type X has no field or method Foo
//
// misspelled x.foo != Foo type X has no field or method foo, but does have field Foo
// missing x.foo != FoO type X has no field or method foo
// inaccessible x.foo != foo cannot refer to unexported field foo
// missing x.foo != foO type X has no field or method foo
const (
ok = iota
missing // no object found
misspelled // found object with different spelling
unexported // found object with name differing only in first letter
inaccessible // found object with matching name but inaccessible from the current package
)
// determine case
e := missing
var alt string // alternative spelling of selector; if any
if obj != nil {
alt = obj.Name()
if obj.Pkg() == check.pkg {
assert(alt != sel) // otherwise there is no lookup error
e = misspelled
} else if isExported(sel) {
if isExported(alt) {
e = misspelled
} else if tail(sel) == tail(alt) {
e = unexported
}
} else if isExported(alt) {
if tail(sel) == tail(alt) {
e = misspelled
}
} else if sel == alt {
e = inaccessible
}
}
if structLit {
switch e {
case missing:
return check.sprintf("unknown field %s in struct literal of type %s", sel, typ)
case misspelled:
return check.sprintf("unknown field %s in struct literal of type %s, but does have %s", sel, typ, alt)
case unexported:
return check.sprintf("unknown field %s in struct literal of type %s, but does have unexported %s", sel, typ, alt)
case inaccessible:
return check.sprintf("cannot refer to unexported field %s in struct literal of type %s", alt, typ)
}
} else {
what := "object"
switch obj.(type) {
case *Var:
what = "field"
case *Func:
what = "method"
}
switch e {
case missing:
return check.sprintf("type %s has no field or method %s", typ, sel)
case misspelled:
return check.sprintf("type %s has no field or method %s, but does have %s %s", typ, sel, what, alt)
case unexported:
return check.sprintf("type %s has no field or method %s, but does have unexported %s %s", typ, sel, what, alt)
case inaccessible:
return check.sprintf("cannot refer to unexported %s %s", what, alt)
}
}
panic("unreachable")
}
// tail returns the string s without its first (UTF-8) character.
// If len(s) == 0, the result is s.
func tail(s string) string {
for i, _ := range s {
if i > 0 {
return s[i:]
}
}
return s
}

View file

@ -1184,9 +1184,14 @@ func (check *Checker) exprInternal(T *target, x *operand, e syntax.Expr, hint Ty
check.errorf(kv, InvalidLitField, "invalid field name %s in struct literal", kv.Key)
continue
}
i := fieldIndex(utyp.fields, check.pkg, key.Value, false)
i := fieldIndex(fields, check.pkg, key.Value, false)
if i < 0 {
check.errorf(kv.Key, MissingLitField, "unknown field %s in struct literal of type %s", key.Value, base)
var alt Object
if j := fieldIndex(fields, check.pkg, key.Value, true); j >= 0 {
alt = fields[j]
}
msg := check.lookupError(base, key.Value, alt, true)
check.error(kv.Key, MissingLitField, msg)
continue
}
fld := fields[i]

View file

@ -590,9 +590,9 @@ func fieldIndex(fields []*Var, pkg *Package, name string, foldCase bool) int {
return -1
}
// lookupMethod returns the index of and method with matching package and name, or (-1, nil).
// methodIndex returns the index of and method with matching package and name, or (-1, nil).
// See Object.sameId for the meaning of foldCase.
func lookupMethod(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
func methodIndex(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
if name != "_" {
for i, m := range methods {
if m.sameId(pkg, name, foldCase) {

View file

@ -6,6 +6,7 @@ package types2
import (
"cmd/compile/internal/syntax"
"strings"
"sync"
"sync/atomic"
)
@ -334,6 +335,12 @@ func (t *Named) NumMethods() int {
// For an ordinary or instantiated type t, the receiver base type of this
// method is the named type t. For an uninstantiated generic type t, each
// method receiver is instantiated with its receiver type parameters.
//
// Methods are numbered deterministically: given the same list of source files
// presented to the type checker, or the same sequence of NewMethod and AddMethod
// calls, the mapping from method index to corresponding method remains the same.
// But the specific ordering is not specified and must not be relied on as it may
// change in the future.
func (t *Named) Method(i int) *Func {
t.resolve()
@ -444,15 +451,40 @@ func (t *Named) SetUnderlying(underlying Type) {
}
// AddMethod adds method m unless it is already in the method list.
// t must not have type arguments.
// The method must be in the same package as t, and t must not have
// type arguments.
func (t *Named) AddMethod(m *Func) {
assert(samePkg(t.obj.pkg, m.pkg))
assert(t.inst == nil)
t.resolve()
if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 {
if t.methodIndex(m.name, false) < 0 {
t.methods = append(t.methods, m)
}
}
// methodIndex returns the index of the method with the given name.
// If foldCase is set, capitalization in the name is ignored.
// The result is negative if no such method exists.
func (t *Named) methodIndex(name string, foldCase bool) int {
if name == "_" {
return -1
}
if foldCase {
for i, m := range t.methods {
if strings.EqualFold(m.name, name) {
return i
}
}
} else {
for i, m := range t.methods {
if m.name == name {
return i
}
}
}
return -1
}
// TODO(gri) Investigate if Unalias can be moved to where underlying is set.
func (t *Named) Underlying() Type { return Unalias(t.resolve().underlying) }
func (t *Named) String() string { return TypeString(t, nil) }
@ -553,15 +585,16 @@ loop:
func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Func) {
n.resolve()
// If n is an instance, we may not have yet instantiated all of its methods.
// Look up the method index in orig, and only instantiate method at the
// matching index (if any).
i, _ := lookupMethod(n.Origin().methods, pkg, name, foldCase)
if i < 0 {
return -1, nil
if samePkg(n.obj.pkg, pkg) || isExported(name) || foldCase {
// If n is an instance, we may not have yet instantiated all of its methods.
// Look up the method index in orig, and only instantiate method at the
// matching index (if any).
if i := n.Origin().methodIndex(name, foldCase); i >= 0 {
// For instances, m.Method(i) will be different from the orig method.
return i, n.Method(i)
}
}
// For instances, m.Method(i) will be different from the orig method.
return i, n.Method(i)
return -1, nil
}
// context returns the type-checker context.

View file

@ -112,3 +112,51 @@ type Inst = *Tree[int]
t.Errorf("Duplicate instances in cycle: %s (%p) -> %s (%p) -> %s (%p)", Inst, Inst, Node, Node, Tree, Tree)
}
}
// TestMethodOrdering is a simple test verifying that the indices of methods of
// a named type remain the same as long as the same source and AddMethod calls
// are presented to the type checker in the same order (go.dev/issue/61298).
func TestMethodOrdering(t *testing.T) {
const src = `
package p
type T struct{}
func (T) a() {}
func (T) c() {}
func (T) b() {}
`
// should get the same method order each time
var methods []string
for i := 0; i < 5; i++ {
// collect T methods as provided in src
pkg := mustTypecheck(src, nil, nil)
T := pkg.Scope().Lookup("T").Type().(*Named)
// add a few more methods manually
for _, name := range []string{"foo", "bar", "bal"} {
m := NewFunc(nopos, pkg, name, nil /* don't care about signature */)
T.AddMethod(m)
}
// check method order
if i == 0 {
// first round: collect methods in given order
methods = make([]string, T.NumMethods())
for j := range methods {
methods[j] = T.Method(j).Name()
}
} else {
// successive rounds: methods must appear in the same order
if got := T.NumMethods(); got != len(methods) {
t.Errorf("got %d methods, want %d", got, len(methods))
continue
}
for j, m := range methods {
if got := T.Method(j).Name(); got != m {
t.Errorf("got method %s, want %s", got, m)
}
}
}
}
}

View file

@ -57,7 +57,7 @@ func (s *_TypeSet) Method(i int) *Func { return s.methods[i] }
// LookupMethod returns the index of and method with matching package and name, or (-1, nil).
func (s *_TypeSet) LookupMethod(pkg *Package, name string, foldCase bool) (int, *Func) {
return lookupMethod(s.methods, pkg, name, foldCase)
return methodIndex(s.methods, pkg, name, foldCase)
}
func (s *_TypeSet) String() string {

View file

@ -48,6 +48,20 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
}
check.recordUse(e, obj)
// If we want a type but don't have one, stop right here and avoid potential problems
// with missing underlying types. This also gives better error messages in some cases
// (see go.dev/issue/65344).
_, gotType := obj.(*TypeName)
if !gotType && wantType {
check.errorf(e, NotAType, "%s is not a type", obj.Name())
// avoid "declared but not used" errors
// (don't use Checker.use - we don't want to evaluate too much)
if v, _ := obj.(*Var); v != nil && v.pkg == check.pkg /* see Checker.use1 */ {
v.used = true
}
return
}
// Type-check the object.
// Only call Checker.objDecl if the object doesn't have a type yet
// (in which case we must actually determine it) or the object is a
@ -57,7 +71,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
// informative "not a type/value" error that this function's caller
// will issue (see go.dev/issue/25790).
typ := obj.Type()
if _, gotType := obj.(*TypeName); typ == nil || gotType && wantType {
if typ == nil || gotType && wantType {
check.objDecl(obj, def)
typ = obj.Type() // type must have been assigned by Checker.objDecl
}

View file

@ -20,3 +20,6 @@ import "cmd/compile/internal/syntax"
// If p and q are in different files, p is before q if the filename
// of p sorts lexicographically before the filename of q.
func cmpPos(p, q syntax.Pos) int { return p.Cmp(q) }
// hasDots reports whether the last argument in the call is followed by ...
func hasDots(call *syntax.CallExpr) bool { return call.HasDots }

View file

@ -700,7 +700,7 @@ func typeHashFieldOf(pos src.XPos, itab *ir.UnaryExpr) *ir.SelectorExpr {
} else {
// runtime.itab's hash field
if itabHashField == nil {
itabHashField = runtimeField("hash", int64(2*types.PtrSize), types.Types[types.TUINT32])
itabHashField = runtimeField("hash", rttype.ITab.OffsetOf("Hash"), types.Types[types.TUINT32])
}
hashField = itabHashField
}

View file

@ -10,6 +10,7 @@ import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/reflectdata"
"cmd/compile/internal/rttype"
"cmd/compile/internal/ssagen"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
@ -345,8 +346,8 @@ func mayCall(n ir.Node) bool {
// itabType loads the _type field from a runtime.itab struct.
func itabType(itab ir.Node) ir.Node {
if itabTypeField == nil {
// runtime.itab's _type field
itabTypeField = runtimeField("_type", int64(types.PtrSize), types.NewPtr(types.Types[types.TUINT8]))
// internal/abi.ITab's Type field
itabTypeField = runtimeField("Type", rttype.ITab.OffsetOf("Type"), types.NewPtr(types.Types[types.TUINT8]))
}
return boundedDotPtr(base.Pos, itab, itabTypeField)
}

18
src/cmd/dist/build.go vendored
View file

@ -903,6 +903,20 @@ func runInstall(pkg string, ch chan struct{}) {
// Define GORISCV64_value from goriscv64
asmArgs = append(asmArgs, "-D", "GORISCV64_"+goriscv64)
}
if goarch == "arm" {
// Define GOARM_value from goarm, which can be either a version
// like "6", or a version and a FP mode, like "7,hardfloat".
switch {
case strings.Contains(goarm, "7"):
asmArgs = append(asmArgs, "-D", "GOARM_7")
fallthrough
case strings.Contains(goarm, "6"):
asmArgs = append(asmArgs, "-D", "GOARM_6")
fallthrough
default:
asmArgs = append(asmArgs, "-D", "GOARM_5")
}
}
goasmh := pathf("%s/go_asm.h", workdir)
// Collect symabis from assembly code.
@ -1760,8 +1774,8 @@ var cgoEnabled = map[string]bool{
// get filtered out of cgoEnabled for 'dist list'.
// See go.dev/issue/56679.
var broken = map[string]bool{
"linux/sparc64": true, // An incomplete port. See CL 132155.
"openbsd/mips64": true, // Broken: go.dev/issue/58110.
"linux/sparc64": true, // An incomplete port. See CL 132155.
"openbsd/mips64": true, // Broken: go.dev/issue/58110.
}
// List of platforms which are first class ports. See go.dev/issue/38874.

View file

@ -3,12 +3,13 @@ module cmd
go 1.23
require (
github.com/google/pprof v0.0.0-20230811205829-9131a7e9cc17
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5
golang.org/x/arch v0.7.0
golang.org/x/build v0.0.0-20240122184708-c291ad69d6be
golang.org/x/mod v0.14.0
golang.org/x/build v0.0.0-20240201175143-3ee44a092755
golang.org/x/mod v0.15.1-0.20240207185259-766dc5df63e3
golang.org/x/sync v0.6.0
golang.org/x/sys v0.16.1-0.20240110015235-f69d32aa924f
golang.org/x/sys v0.17.0
golang.org/x/telemetry v0.0.0-20240208185543-e9b074dd3804
golang.org/x/term v0.16.0
golang.org/x/tools v0.17.1-0.20240119231502-e1555a36d006
)

View file

@ -1,21 +1,39 @@
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20230811205829-9131a7e9cc17 h1:0h35ESZ02+hN/MFZb7XZOXg+Rl9+Rk8fBIf5YLws9gA=
github.com/google/pprof v0.0.0-20230811205829-9131a7e9cc17/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5 h1:E/LAvt58di64hlYjx7AsNS6C/ysHWYo+2qPCZKTQhRo=
github.com/google/pprof v0.0.0-20240207164012-fb44976bdcd5/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/build v0.0.0-20240122184708-c291ad69d6be h1:h1qJlb1MudWuUMYotaFX+nSdSgv6zrBBDNojV68uqCA=
golang.org/x/build v0.0.0-20240122184708-c291ad69d6be/go.mod h1:RHSzqFUzT4+buJlGik6WptO5NxLQiR/ewD2uz3fgWuA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/build v0.0.0-20240201175143-3ee44a092755 h1:irSM9p93GT4I3+Pu/grZlkwIjrXA3GfyKwlSosVbmtU=
golang.org/x/build v0.0.0-20240201175143-3ee44a092755/go.mod h1:RHSzqFUzT4+buJlGik6WptO5NxLQiR/ewD2uz3fgWuA=
golang.org/x/mod v0.15.1-0.20240207185259-766dc5df63e3 h1:/p/VemLWiTsjHqHwME1Iu+xIu8s9fBtwBk8bU/ejA1A=
golang.org/x/mod v0.15.1-0.20240207185259-766dc5df63e3/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.16.1-0.20240110015235-f69d32aa924f h1:GvGFYRZ5kIldzXQj3UmUiUTMe5spPODuLKQvP38A+Qc=
golang.org/x/sys v0.16.1-0.20240110015235-f69d32aa924f/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240208185543-e9b074dd3804 h1:mLYQpgq+cJOnmn3pR2U9o5rzEuOVgnmw59GHPgypGeo=
golang.org/x/telemetry v0.0.0-20240208185543-e9b074dd3804/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=

View file

@ -1004,6 +1004,8 @@
// Retracted []string // retraction information, if any (with -retracted or -u)
// Deprecated string // deprecation message, if any (with -u)
// Error *ModuleError // error loading module
// Sum string // checksum for path, version (as in go.sum)
// GoModSum string // checksum for go.mod (as in go.sum)
// Origin any // provenance of module
// Reuse bool // reuse of old module info is safe
// }

View file

@ -245,6 +245,8 @@ applied to a Go struct, but now a Module struct:
Retracted []string // retraction information, if any (with -retracted or -u)
Deprecated string // deprecation message, if any (with -u)
Error *ModuleError // error loading module
Sum string // checksum for path, version (as in go.sum)
GoModSum string // checksum for go.mod (as in go.sum)
Origin any // provenance of module
Reuse bool // reuse of old module info is safe
}
@ -725,6 +727,9 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
b.IsCmdList = true
b.NeedExport = *listExport
b.NeedCompiledGoFiles = *listCompiled
if cfg.Experiment.CoverageRedesign && cfg.BuildCover {
load.PrepareForCoverageBuild(pkgs)
}
a := &work.Action{}
// TODO: Use pkgsFilter?
for _, p := range pkgs {
@ -732,9 +737,6 @@ func runList(ctx context.Context, cmd *base.Command, args []string) {
a.Deps = append(a.Deps, b.AutoAction(work.ModeInstall, work.ModeInstall, p))
}
}
if cfg.Experiment.CoverageRedesign && cfg.BuildCover {
load.PrepareForCoverageBuild(pkgs)
}
b.Do(ctx, a)
}

View file

@ -2306,7 +2306,7 @@ func (p *Package) setBuildInfo(ctx context.Context, autoVCS bool) {
}
if mi.Replace != nil {
dm.Replace = debugModFromModinfo(mi.Replace)
} else if mi.Version != "" {
} else if mi.Version != "" && cfg.BuildMod != "vendor" {
dm.Sum = modfetch.Sum(ctx, module.Version{Path: mi.Path, Version: mi.Version})
}
return dm

View file

@ -569,6 +569,47 @@ func HaveSum(mod module.Version) bool {
return false
}
// RecordedSum returns the sum if the go.sum file contains an entry for mod.
// The boolean reports true if an entry was found or
// false if no entry found or two conflicting sums are found.
// The entry's hash must be generated with a known hash algorithm.
// mod.Version may have a "/go.mod" suffix to distinguish sums for
// .mod and .zip files.
func RecordedSum(mod module.Version) (sum string, ok bool) {
goSum.mu.Lock()
defer goSum.mu.Unlock()
inited, err := initGoSum()
foundSum := ""
if err != nil || !inited {
return "", false
}
for _, goSums := range goSum.w {
for _, h := range goSums[mod] {
if !strings.HasPrefix(h, "h1:") {
continue
}
if !goSum.status[modSum{mod, h}].dirty {
if foundSum != "" && foundSum != h { // conflicting sums exist
return "", false
}
foundSum = h
}
}
}
for _, h := range goSum.m[mod] {
if !strings.HasPrefix(h, "h1:") {
continue
}
if !goSum.status[modSum{mod, h}].dirty {
if foundSum != "" && foundSum != h { // conflicting sums exist
return "", false
}
foundSum = h
}
}
return foundSum, true
}
// checkMod checks the given module's checksum and Go version.
func checkMod(ctx context.Context, mod module.Version) {
// Do the file I/O before acquiring the go.sum lock.

View file

@ -124,7 +124,7 @@ var (
errNotFromModuleCache = fmt.Errorf("%w: not from module cache", ErrNotIndexed)
)
// GetPackage returns the IndexPackage for the package at the given path.
// GetPackage returns the IndexPackage for the directory at the given path.
// It will return ErrNotIndexed if the directory should be read without
// using the index, for instance because the index is disabled, or the package
// is not in a module.
@ -669,11 +669,9 @@ func IsStandardPackage(goroot_, compiler, path string) bool {
reldir = str.TrimFilePathPrefix(reldir, "cmd")
modroot = filepath.Join(modroot, "cmd")
}
if _, err := GetPackage(modroot, filepath.Join(modroot, reldir)); err == nil {
// Note that goroot.IsStandardPackage doesn't check that the directory
// actually contains any go files-- merely that it exists. GetPackage
// returning a nil error is enough for us to know the directory exists.
return true
if pkg, err := GetPackage(modroot, filepath.Join(modroot, reldir)); err == nil {
hasGo, err := pkg.IsDirWithGoFiles()
return err == nil && hasGo
} else if errors.Is(err, ErrNotIndexed) {
// Fall back because package isn't indexable. (Probably because
// a file was modified recently)
@ -786,8 +784,8 @@ func shouldBuild(sf *sourceFile, tags map[string]bool) bool {
return true
}
// IndexPackage holds the information needed to access information in the
// index needed to load a package in a specific directory.
// IndexPackage holds the information in the index
// needed to load a package in a specific directory.
type IndexPackage struct {
error error
dir string // directory of the package relative to the modroot

View file

@ -14,24 +14,25 @@ import (
// and the fields are documented in the help text in ../list/list.go
type ModulePublic struct {
Path string `json:",omitempty"` // module path
Version string `json:",omitempty"` // module version
Query string `json:",omitempty"` // version query corresponding to this version
Versions []string `json:",omitempty"` // available module versions
Replace *ModulePublic `json:",omitempty"` // replaced by this module
Time *time.Time `json:",omitempty"` // time version was created
Update *ModulePublic `json:",omitempty"` // available update (with -u)
Main bool `json:",omitempty"` // is this the main module?
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
Dir string `json:",omitempty"` // directory holding local copy of files, if any
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
GoVersion string `json:",omitempty"` // go version used in module
Retracted []string `json:",omitempty"` // retraction information, if any (with -retracted or -u)
Deprecated string `json:",omitempty"` // deprecation message, if any (with -u)
Error *ModuleError `json:",omitempty"` // error loading module
Origin *codehost.Origin `json:",omitempty"` // provenance of module
Reuse bool `json:",omitempty"` // reuse of old module info is safe
Path string `json:",omitempty"` // module path
Version string `json:",omitempty"` // module version
Query string `json:",omitempty"` // version query corresponding to this version
Versions []string `json:",omitempty"` // available module versions
Replace *ModulePublic `json:",omitempty"` // replaced by this module
Time *time.Time `json:",omitempty"` // time version was created
Update *ModulePublic `json:",omitempty"` // available update (with -u)
Main bool `json:",omitempty"` // is this the main module?
Indirect bool `json:",omitempty"` // module is only indirectly needed by main module
Dir string `json:",omitempty"` // directory holding local copy of files, if any
GoMod string `json:",omitempty"` // path to go.mod file describing module, if any
GoVersion string `json:",omitempty"` // go version used in module
Retracted []string `json:",omitempty"` // retraction information, if any (with -retracted or -u)
Deprecated string `json:",omitempty"` // deprecation message, if any (with -u)
Error *ModuleError `json:",omitempty"` // error loading module
Sum string `json:",omitempty"` // checksum for path, version (as in go.sum)
GoModSum string `json:",omitempty"` // checksum for go.mod (as in go.sum)
Origin *codehost.Origin `json:",omitempty"` // provenance of module
Reuse bool `json:",omitempty"` // reuse of old module info is safe
}
type ModuleError struct {

View file

@ -364,12 +364,18 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
m.GoMod = gomod
}
}
if gomodsum, ok := modfetch.RecordedSum(modkey(mod)); ok {
m.GoModSum = gomodsum
}
}
if checksumOk("") {
dir, err := modfetch.DownloadDir(ctx, mod)
if err == nil {
m.Dir = dir
}
if sum, ok := modfetch.RecordedSum(mod); ok {
m.Sum = sum
}
}
if mode&ListRetracted != 0 {

View file

@ -367,12 +367,13 @@ func asmArgs(a *Action, p *load.Package) []any {
}
if cfg.Goarch == "arm" {
// Define GOARM_value from cfg.GOARM.
switch cfg.GOARM {
case "7":
// Define GOARM_value from cfg.GOARM, which can be either a version
// like "6", or a version and a FP mode, like "7,hardfloat".
switch {
case strings.Contains(cfg.GOARM, "7"):
args = append(args, "-D", "GOARM_7")
fallthrough
case "6":
case strings.Contains(cfg.GOARM, "6"):
args = append(args, "-D", "GOARM_6")
fallthrough
default:

View file

@ -7,8 +7,6 @@
package main
import (
"cmd/go/internal/toolchain"
"cmd/go/internal/workcmd"
"context"
"flag"
"fmt"
@ -38,10 +36,14 @@ import (
"cmd/go/internal/run"
"cmd/go/internal/test"
"cmd/go/internal/tool"
"cmd/go/internal/toolchain"
"cmd/go/internal/trace"
"cmd/go/internal/version"
"cmd/go/internal/vet"
"cmd/go/internal/work"
"cmd/go/internal/workcmd"
"golang.org/x/telemetry/counter"
)
func init() {
@ -89,11 +91,13 @@ var _ = go11tag
func main() {
log.SetFlags(0)
counter.Open() // Open the telemetry counter file so counters can be written to it.
handleChdirFlag()
toolchain.Select()
flag.Usage = base.Usage
flag.Parse()
counter.CountFlags("cmd/go:flag-", *flag.CommandLine)
args := flag.Args()
if len(args) < 1 {
@ -149,6 +153,7 @@ func main() {
cmd, used := lookupCmd(args)
cfg.CmdName = strings.Join(args[:used], " ")
counter.Inc("cmd/go:subcommand-" + strings.ReplaceAll(cfg.CmdName, " ", "-"))
if len(cmd.Commands) > 0 {
if used >= len(args) {
help.PrintUsage(os.Stderr, cmd)
@ -236,6 +241,7 @@ func invoke(cmd *base.Command, args []string) {
} else {
base.SetFromGOFLAGS(&cmd.Flag)
cmd.Flag.Parse(args[1:])
counter.CountFlags("cmd/go/"+cmd.Name()+":flag-", cmd.Flag)
args = cmd.Flag.Args()
}
@ -320,6 +326,7 @@ func handleChdirFlag() {
_, dir, _ = strings.Cut(a, "=")
os.Args = slices.Delete(os.Args, used, used+1)
}
counter.Inc("cmd/go:flag-C")
if err := os.Chdir(dir); err != nil {
base.Fatalf("go: %v", err)

View file

@ -55,6 +55,7 @@ func scriptConditions() map[string]script.Cond {
add("msan", sysCondition("-msan", platform.MSanSupported, true))
add("mustlinkext", script.Condition("platform always requires external linking", mustLinkExt))
add("net", script.PrefixCondition("can connect to external network host <suffix>", hasNet))
add("pielinkext", script.Condition("platform requires external linking for PIE", pieLinkExt))
add("race", sysCondition("-race", platform.RaceDetectorSupported, true))
add("symlink", lazyBool("testenv.HasSymlink()", testenv.HasSymlink))
add("trimpath", script.OnceCondition("test binary was built with -trimpath", isTrimpath))
@ -233,3 +234,9 @@ func mustLinkExt(s *script.State) (bool, error) {
GOARCH, _ := s.LookupEnv("GOARCH")
return platform.MustLinkExternal(GOOS, GOARCH, false), nil
}
func pieLinkExt(s *script.State) (bool, error) {
GOOS, _ := s.LookupEnv("GOOS")
GOARCH, _ := s.LookupEnv("GOARCH")
return !platform.InternalLinkPIESupported(GOOS, GOARCH), nil
}

View file

@ -410,6 +410,8 @@ The available conditions are:
platform always requires external linking
[net:*]
can connect to external network host <suffix>
[pielinkext]
platform requires external linking for PIE
[race]
GOOS/GOARCH supports -race
[root]

View file

@ -0,0 +1,9 @@
go build
-- go.mod --
module test
go 1.0
-- p.go --
package p

View file

@ -1,5 +1,6 @@
[!buildmode:plugin] skip
[short] skip
[!cgo] skip '-buildmode=plugin requires external linking'
go build -trimpath -buildvcs=false -buildmode=plugin -o a.so main.go
go build -trimpath -buildvcs=false -buildmode=plugin -o b.so main.go
@ -8,4 +9,4 @@ cmp -q a.so b.so
-- main.go --
package main
func main() {}
func main() {}

View file

@ -38,6 +38,10 @@ cp stdout $WORK/toolbuildid.txt
# Build IDs should match here.
cmp $WORK/toolbuildid.txt $WORK/listbuildid.txt
# Make sure that the build succeeds regardless of covermode.
go list -export -covermode=atomic m/example
go list -export -covermode=count m/example
-- go.mod --
module m

View file

@ -8,8 +8,7 @@ env TESTGO_VERSION=go1.21pre3
# Compile a fake toolchain to put in the path under various names.
env GOTOOLCHAIN=
mkdir $WORK/bin
go build -o $WORK/bin/ ./fakego.go # adds .exe extension implicitly on Windows
cp $WORK/bin/fakego$GOEXE $WORK/bin/go1.50.0$GOEXE
go build -o $WORK/bin/go1.50.0$GOEXE ./fakego.go # adds .exe extension implicitly on Windows
[!GOOS:plan9] env PATH=$WORK/bin
[GOOS:plan9] env path=$WORK/bin

View file

@ -0,0 +1,11 @@
# Issue 65406. The testdata directory in GOROOT/src
# shouldn't be treated as a standard package.
go list -f '{{.ImportPath}} {{.Dir}}' testdata
! stderr 'found package testdata in multiple modules'
stdout 'testdata '$WORK${/}'gopath'${/}'src'
-- go.mod --
module testdata
-- p.go --
package p

View file

@ -0,0 +1,32 @@
# This test verifies that GOMODCACHE does not affect whether checksums are embedded
# with vendored files.
# See issue #46400
[short] skip 'builds and links a binary twice'
go mod tidy
go mod vendor
go build -mod=vendor
go version -m example$GOEXE
cp stdout version-m.txt
env GOMODCACHE=$WORK${/}modcache
go build -mod=vendor
go version -m example$GOEXE
cmp stdout version-m.txt
-- go.mod --
module example
go 1.22
require rsc.io/sampler v1.3.0
-- main.go --
package main
import (
"fmt"
"rsc.io/sampler"
)
func main() {
fmt.Println(sampler.Hello())
}

View file

@ -44,9 +44,9 @@ stderr '^go: module rsc.io/quote/buggy: not a known dependency'
# Module loader does not interfere with list -e (golang.org/issue/24149).
go list -e -f '{{.Error.Err}}' database
stdout 'no Go files in '
stdout 'package database is not in std'
! go list database
stderr 'no Go files in '
stderr 'package database is not in std'
-- go.mod --
module x

View file

@ -0,0 +1,16 @@
go mod tidy
go list -m -json all
stdout '"GoModSum":\s+"h1:.+"'
stdout '"Sum":\s+"h1:.+"'
-- go.mod --
module example
go 1.21
require rsc.io/quote v1.5.1
-- example.go --
package example
import _ "rsc.io/quote"

View file

@ -2,6 +2,16 @@
[short] skip
env GOCACHE=$WORK/cache
# Warm up the build cache with GOMAXPROCS unrestricted.
go test -c -o $devnull
# For the fuzzing phase, we reduce GOMAXPROCS to avoid consuming too many
# resources during the test. Ideally this would just free up resources to run
# other parallel tests more quickly, but unfortunately it is actually necessary
# in some 32-bit environments to prevent the fuzzing engine from running out of
# address space (see https://go.dev/issue/65434).
env GOMAXPROCS=2
# The fuzz function should be able to detect whether -timeout
# was set with T.Deadline. Note there is no F.Deadline, and
# there is no timeout while fuzzing, even if -fuzztime is set.

View file

@ -5,6 +5,13 @@ env GOCACHE=$WORK/cache
# There are no seed values, so 'go test' should finish quickly.
go test
# For the fuzzing phase, we reduce GOMAXPROCS to avoid consuming too many
# resources during the test. Ideally this would just free up resources to run
# other parallel tests more quickly, but unfortunately it is actually necessary
# in some 32-bit environments to prevent the fuzzing engine from running out of
# address space (see https://go.dev/issue/65434).
env GOMAXPROCS=2
# Fuzzing should exit 0 after fuzztime, even if timeout is short.
go test -timeout=3s -fuzz=FuzzFast -fuzztime=5s

View file

@ -57,8 +57,9 @@ stdout '^test2json.exe: .+'
stdout '^\tpath\tcmd/test2json$'
! stdout 'mod[^e]'
# Repeat the test with -buildmode=pie.
# Repeat the test with -buildmode=pie and default linking.
[!buildmode:pie] stop
[pielinkext] [!cgo] stop
go build -buildmode=pie -o external.exe rsc.io/fortune
go version external.exe
stdout '^external.exe: .+'
@ -68,9 +69,7 @@ stdout '^\tpath\trsc.io/fortune'
stdout '^\tmod\trsc.io/fortune\tv1.0.0'
# Also test PIE with internal linking.
# currently only supported on linux/amd64, linux/arm64 and windows/amd64.
[!GOOS:linux] [!GOOS:windows] stop
[!GOARCH:amd64] [!GOARCH:arm64] stop
[pielinkext] stop
go build -buildmode=pie -ldflags=-linkmode=internal -o internal.exe rsc.io/fortune
go version internal.exe
stdout '^internal.exe: .+'

View file

@ -1,4 +1,5 @@
[short] skip
[!cgo] skip '-buildmode=c-shared requires external linking'
[!buildmode:c-shared] stop
env GO111MODULE=on

View file

@ -422,15 +422,18 @@ const (
C_U15CON /* 15 bit unsigned constant */
C_S16CON /* 16 bit signed constant */
C_U16CON /* 16 bit unsigned constant */
C_16CON /* Any constant which fits into 16 bits. Can be signed or unsigned */
C_U31CON /* 31 bit unsigned constant */
C_S32CON /* 32 bit signed constant */
C_U32CON /* 32 bit unsigned constant */
C_32CON /* Any constant which fits into 32 bits. Can be signed or unsigned */
C_S34CON /* 34 bit signed constant */
C_64CON /* Any constant which fits into 64 bits. Can be signed or unsigned */
C_SACON /* $n(REG) where n <= int16 */
C_LACON /* $n(REG) where n <= int32 */
C_DACON /* $n(REG) where n <= int64 */
C_SBRA /* A short offset argument to a branching instruction */
C_LBRA /* A long offset argument to a branching instruction */
C_LBRAPIC /* Like C_LBRA, but requires an extra NOP for potential TOC restore by the linker. */
C_BRA /* A short offset argument to a branching instruction */
C_BRAPIC /* Like C_BRA, but requires an extra NOP for potential TOC restore by the linker. */
C_ZOREG /* An $0+reg memory op */
C_SOREG /* An $n+reg memory arg where n is a 16 bit signed offset */
C_LOREG /* An $n+reg memory arg where n is a 32 bit signed offset */
@ -446,16 +449,6 @@ const (
C_TEXTSIZE /* An argument with Type obj.TYPE_TEXTSIZE */
C_NCLASS /* must be the last */
/* Aliased names which should be cleaned up, or integrated. */
C_SCON = C_U15CON
C_ADDCON = C_S16CON
C_ANDCON = C_U16CON
C_LCON = C_32CON
/* Aliased names which may be generated by ppc64map for the optab. */
C_S32CON = C_32CON
C_U32CON = C_32CON
)
const (

View file

@ -27,15 +27,18 @@ var cnames9 = []string{
"U15CON",
"S16CON",
"U16CON",
"16CON",
"U31CON",
"S32CON",
"U32CON",
"32CON",
"S34CON",
"64CON",
"SACON",
"LACON",
"DACON",
"SBRA",
"LBRA",
"LBRAPIC",
"BRA",
"BRAPIC",
"ZOREG",
"SOREG",
"LOREG",

View file

@ -110,60 +110,56 @@ var optab []Optab
var optabBase = []Optab{
{as: obj.ATEXT, a1: C_LOREG, a6: C_TEXTSIZE, type_: 0, size: 0},
{as: obj.ATEXT, a1: C_LOREG, a3: C_LCON, a6: C_TEXTSIZE, type_: 0, size: 0},
{as: obj.ATEXT, a1: C_LOREG, a3: C_32CON, a6: C_TEXTSIZE, type_: 0, size: 0},
{as: obj.ATEXT, a1: C_ADDR, a6: C_TEXTSIZE, type_: 0, size: 0},
{as: obj.ATEXT, a1: C_ADDR, a3: C_LCON, a6: C_TEXTSIZE, type_: 0, size: 0},
{as: obj.ATEXT, a1: C_ADDR, a3: C_32CON, a6: C_TEXTSIZE, type_: 0, size: 0},
/* move register */
{as: AADD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4},
{as: AADD, a1: C_REG, a6: C_REG, type_: 2, size: 4},
{as: AADD, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AADD, a1: C_SCON, a6: C_REG, type_: 4, size: 4},
{as: AADD, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AADD, a1: C_ADDCON, a6: C_REG, type_: 4, size: 4},
{as: AADD, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 22, size: 8},
{as: AADD, a1: C_ANDCON, a6: C_REG, type_: 22, size: 8},
{as: AADDIS, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 20, size: 4},
{as: AADDIS, a1: C_ADDCON, a6: C_REG, type_: 20, size: 4},
{as: AADD, a1: C_S16CON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AADD, a1: C_S16CON, a6: C_REG, type_: 4, size: 4},
{as: AADD, a1: C_U16CON, a2: C_REG, a6: C_REG, type_: 22, size: 8},
{as: AADD, a1: C_U16CON, a6: C_REG, type_: 22, size: 8},
{as: AADDIS, a1: C_S16CON, a2: C_REG, a6: C_REG, type_: 20, size: 4},
{as: AADDIS, a1: C_S16CON, a6: C_REG, type_: 20, size: 4},
{as: AADDC, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4},
{as: AADDC, a1: C_REG, a6: C_REG, type_: 2, size: 4},
{as: AADDC, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AADDC, a1: C_ADDCON, a6: C_REG, type_: 4, size: 4},
{as: AADDC, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 22, size: 12},
{as: AADDC, a1: C_LCON, a6: C_REG, type_: 22, size: 12},
{as: AADDC, a1: C_S16CON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AADDC, a1: C_S16CON, a6: C_REG, type_: 4, size: 4},
{as: AADDC, a1: C_32CON, a2: C_REG, a6: C_REG, type_: 22, size: 12},
{as: AADDC, a1: C_32CON, a6: C_REG, type_: 22, size: 12},
{as: AAND, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, /* logical, no literal */
{as: AAND, a1: C_REG, a6: C_REG, type_: 6, size: 4},
{as: AANDCC, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4},
{as: AANDCC, a1: C_REG, a6: C_REG, type_: 6, size: 4},
{as: AANDCC, a1: C_ANDCON, a6: C_REG, type_: 58, size: 4},
{as: AANDCC, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: AANDCC, a1: C_ADDCON, a6: C_REG, type_: 23, size: 8},
{as: AANDCC, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 23, size: 8},
{as: AANDCC, a1: C_LCON, a6: C_REG, type_: 23, size: 12},
{as: AANDCC, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 23, size: 12},
{as: AANDISCC, a1: C_ANDCON, a6: C_REG, type_: 58, size: 4},
{as: AANDISCC, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: AANDCC, a1: C_U16CON, a6: C_REG, type_: 58, size: 4},
{as: AANDCC, a1: C_U16CON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: AANDCC, a1: C_S16CON, a6: C_REG, type_: 23, size: 8},
{as: AANDCC, a1: C_S16CON, a2: C_REG, a6: C_REG, type_: 23, size: 8},
{as: AANDCC, a1: C_32CON, a6: C_REG, type_: 23, size: 12},
{as: AANDCC, a1: C_32CON, a2: C_REG, a6: C_REG, type_: 23, size: 12},
{as: AANDISCC, a1: C_U16CON, a6: C_REG, type_: 58, size: 4},
{as: AANDISCC, a1: C_U16CON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: AMULLW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4},
{as: AMULLW, a1: C_REG, a6: C_REG, type_: 2, size: 4},
{as: AMULLW, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AMULLW, a1: C_ADDCON, a6: C_REG, type_: 4, size: 4},
{as: AMULLW, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AMULLW, a1: C_ANDCON, a6: C_REG, type_: 4, size: 4},
{as: AMULLW, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 22, size: 12},
{as: AMULLW, a1: C_LCON, a6: C_REG, type_: 22, size: 12},
{as: AMULLW, a1: C_S16CON, a2: C_REG, a6: C_REG, type_: 4, size: 4},
{as: AMULLW, a1: C_S16CON, a6: C_REG, type_: 4, size: 4},
{as: AMULLW, a1: C_32CON, a2: C_REG, a6: C_REG, type_: 22, size: 12},
{as: AMULLW, a1: C_32CON, a6: C_REG, type_: 22, size: 12},
{as: ASUBC, a1: C_REG, a2: C_REG, a6: C_REG, type_: 10, size: 4},
{as: ASUBC, a1: C_REG, a6: C_REG, type_: 10, size: 4},
{as: ASUBC, a1: C_REG, a3: C_ADDCON, a6: C_REG, type_: 27, size: 4},
{as: ASUBC, a1: C_REG, a3: C_LCON, a6: C_REG, type_: 28, size: 12},
{as: ASUBC, a1: C_REG, a3: C_S16CON, a6: C_REG, type_: 27, size: 4},
{as: ASUBC, a1: C_REG, a3: C_32CON, a6: C_REG, type_: 28, size: 12},
{as: AOR, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4}, /* logical, literal not cc (or/xor) */
{as: AOR, a1: C_REG, a6: C_REG, type_: 6, size: 4},
{as: AOR, a1: C_ANDCON, a6: C_REG, type_: 58, size: 4},
{as: AOR, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: AOR, a1: C_ADDCON, a6: C_REG, type_: 23, size: 8},
{as: AOR, a1: C_ADDCON, a2: C_REG, a6: C_REG, type_: 23, size: 8},
{as: AOR, a1: C_LCON, a6: C_REG, type_: 23, size: 12},
{as: AOR, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 23, size: 12},
{as: AORIS, a1: C_ANDCON, a6: C_REG, type_: 58, size: 4},
{as: AORIS, a1: C_ANDCON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: AOR, a1: C_U16CON, a6: C_REG, type_: 58, size: 4},
{as: AOR, a1: C_U16CON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: AOR, a1: C_S16CON, a6: C_REG, type_: 23, size: 8},
{as: AOR, a1: C_S16CON, a2: C_REG, a6: C_REG, type_: 23, size: 8},
{as: AOR, a1: C_32CON, a6: C_REG, type_: 23, size: 12},
{as: AOR, a1: C_32CON, a2: C_REG, a6: C_REG, type_: 23, size: 12},
{as: AORIS, a1: C_U16CON, a6: C_REG, type_: 58, size: 4},
{as: AORIS, a1: C_U16CON, a2: C_REG, a6: C_REG, type_: 58, size: 4},
{as: ADIVW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 2, size: 4}, /* op r1[,r2],r3 */
{as: ADIVW, a1: C_REG, a6: C_REG, type_: 2, size: 4},
{as: ASUB, a1: C_REG, a2: C_REG, a6: C_REG, type_: 10, size: 4}, /* op r2[,r1],r3 */
@ -172,33 +168,33 @@ var optabBase = []Optab{
{as: ASLW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4},
{as: ASLD, a1: C_REG, a6: C_REG, type_: 6, size: 4},
{as: ASLD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4},
{as: ASLD, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 25, size: 4},
{as: ASLD, a1: C_SCON, a6: C_REG, type_: 25, size: 4},
{as: AEXTSWSLI, a1: C_SCON, a6: C_REG, type_: 25, size: 4},
{as: AEXTSWSLI, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 25, size: 4},
{as: ASLW, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 57, size: 4},
{as: ASLW, a1: C_SCON, a6: C_REG, type_: 57, size: 4},
{as: ASLD, a1: C_U15CON, a2: C_REG, a6: C_REG, type_: 25, size: 4},
{as: ASLD, a1: C_U15CON, a6: C_REG, type_: 25, size: 4},
{as: AEXTSWSLI, a1: C_U15CON, a6: C_REG, type_: 25, size: 4},
{as: AEXTSWSLI, a1: C_U15CON, a2: C_REG, a6: C_REG, type_: 25, size: 4},
{as: ASLW, a1: C_U15CON, a2: C_REG, a6: C_REG, type_: 57, size: 4},
{as: ASLW, a1: C_U15CON, a6: C_REG, type_: 57, size: 4},
{as: ASRAW, a1: C_REG, a6: C_REG, type_: 6, size: 4},
{as: ASRAW, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4},
{as: ASRAW, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 56, size: 4},
{as: ASRAW, a1: C_SCON, a6: C_REG, type_: 56, size: 4},
{as: ASRAW, a1: C_U15CON, a2: C_REG, a6: C_REG, type_: 56, size: 4},
{as: ASRAW, a1: C_U15CON, a6: C_REG, type_: 56, size: 4},
{as: ASRAD, a1: C_REG, a6: C_REG, type_: 6, size: 4},
{as: ASRAD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 6, size: 4},
{as: ASRAD, a1: C_SCON, a2: C_REG, a6: C_REG, type_: 56, size: 4},
{as: ASRAD, a1: C_SCON, a6: C_REG, type_: 56, size: 4},
{as: ARLWNM, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 63, size: 4},
{as: ARLWNM, a1: C_SCON, a2: C_REG, a3: C_SCON, a4: C_SCON, a6: C_REG, type_: 63, size: 4},
{as: ARLWNM, a1: C_REG, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 63, size: 4},
{as: ARLWNM, a1: C_REG, a2: C_REG, a3: C_SCON, a4: C_SCON, a6: C_REG, type_: 63, size: 4},
{as: ACLRLSLWI, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 62, size: 4},
{as: ARLDMI, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 30, size: 4},
{as: ARLDC, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 29, size: 4},
{as: ASRAD, a1: C_U15CON, a2: C_REG, a6: C_REG, type_: 56, size: 4},
{as: ASRAD, a1: C_U15CON, a6: C_REG, type_: 56, size: 4},
{as: ARLWNM, a1: C_U15CON, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 63, size: 4},
{as: ARLWNM, a1: C_U15CON, a2: C_REG, a3: C_U15CON, a4: C_U15CON, a6: C_REG, type_: 63, size: 4},
{as: ARLWNM, a1: C_REG, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 63, size: 4},
{as: ARLWNM, a1: C_REG, a2: C_REG, a3: C_U15CON, a4: C_U15CON, a6: C_REG, type_: 63, size: 4},
{as: ACLRLSLWI, a1: C_U15CON, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 62, size: 4},
{as: ARLDMI, a1: C_U15CON, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 30, size: 4},
{as: ARLDC, a1: C_U15CON, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 29, size: 4},
{as: ARLDC, a1: C_REG, a3: C_U8CON, a4: C_U8CON, a6: C_REG, type_: 9, size: 4},
{as: ARLDCL, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 29, size: 4},
{as: ARLDCL, a1: C_REG, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4},
{as: ARLDICL, a1: C_REG, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4},
{as: ARLDICL, a1: C_SCON, a2: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4},
{as: ARLDCL, a1: C_REG, a3: C_LCON, a6: C_REG, type_: 14, size: 4},
{as: ARLDCL, a1: C_U15CON, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 29, size: 4},
{as: ARLDCL, a1: C_REG, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 14, size: 4},
{as: ARLDICL, a1: C_REG, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 14, size: 4},
{as: ARLDICL, a1: C_U15CON, a2: C_REG, a3: C_32CON, a6: C_REG, type_: 14, size: 4},
{as: ARLDCL, a1: C_REG, a3: C_32CON, a6: C_REG, type_: 14, size: 4},
{as: AFADD, a1: C_FREG, a6: C_FREG, type_: 2, size: 4},
{as: AFADD, a1: C_FREG, a2: C_FREG, a6: C_FREG, type_: 2, size: 4},
{as: AFABS, a1: C_FREG, a6: C_FREG, type_: 33, size: 4},
@ -232,8 +228,7 @@ var optabBase = []Optab{
{as: AMOVBZ, a1: C_REG, a6: C_XOREG, type_: 108, size: 4},
{as: AMOVBZ, a1: C_REG, a6: C_REG, type_: 13, size: 4},
{as: AMOVD, a1: C_ADDCON, a6: C_REG, type_: 3, size: 4},
{as: AMOVD, a1: C_ANDCON, a6: C_REG, type_: 3, size: 4},
{as: AMOVD, a1: C_16CON, a6: C_REG, type_: 3, size: 4},
{as: AMOVD, a1: C_SACON, a6: C_REG, type_: 3, size: 4},
{as: AMOVD, a1: C_SOREG, a6: C_REG, type_: 8, size: 4},
{as: AMOVD, a1: C_XOREG, a6: C_REG, type_: 109, size: 4},
@ -245,8 +240,7 @@ var optabBase = []Optab{
{as: AMOVD, a1: C_REG, a6: C_SPR, type_: 66, size: 4},
{as: AMOVD, a1: C_REG, a6: C_REG, type_: 13, size: 4},
{as: AMOVW, a1: C_ADDCON, a6: C_REG, type_: 3, size: 4},
{as: AMOVW, a1: C_ANDCON, a6: C_REG, type_: 3, size: 4},
{as: AMOVW, a1: C_16CON, a6: C_REG, type_: 3, size: 4},
{as: AMOVW, a1: C_SACON, a6: C_REG, type_: 3, size: 4},
{as: AMOVW, a1: C_CREG, a6: C_REG, type_: 68, size: 4},
{as: AMOVW, a1: C_SOREG, a6: C_REG, type_: 8, size: 4},
@ -258,7 +252,7 @@ var optabBase = []Optab{
{as: AMOVW, a1: C_REG, a6: C_SPR, type_: 66, size: 4},
{as: AMOVW, a1: C_REG, a6: C_REG, type_: 13, size: 4},
{as: AFMOVD, a1: C_ADDCON, a6: C_FREG, type_: 24, size: 8},
{as: AFMOVD, a1: C_S16CON, a6: C_FREG, type_: 24, size: 8},
{as: AFMOVD, a1: C_SOREG, a6: C_FREG, type_: 8, size: 4},
{as: AFMOVD, a1: C_XOREG, a6: C_FREG, type_: 109, size: 4},
{as: AFMOVD, a1: C_ZCON, a6: C_FREG, type_: 24, size: 4},
@ -275,29 +269,28 @@ var optabBase = []Optab{
{as: AMOVFL, a1: C_CREG, a6: C_CREG, type_: 67, size: 4},
{as: AMOVFL, a1: C_FPSCR, a6: C_CREG, type_: 73, size: 4},
{as: AMOVFL, a1: C_FPSCR, a6: C_FREG, type_: 53, size: 4},
{as: AMOVFL, a1: C_FREG, a3: C_LCON, a6: C_FPSCR, type_: 64, size: 4},
{as: AMOVFL, a1: C_FREG, a3: C_32CON, a6: C_FPSCR, type_: 64, size: 4},
{as: AMOVFL, a1: C_FREG, a6: C_FPSCR, type_: 64, size: 4},
{as: AMOVFL, a1: C_LCON, a6: C_FPSCR, type_: 65, size: 4},
{as: AMOVFL, a1: C_32CON, a6: C_FPSCR, type_: 65, size: 4},
{as: AMOVFL, a1: C_REG, a6: C_CREG, type_: 69, size: 4},
{as: AMOVFL, a1: C_REG, a6: C_LCON, type_: 69, size: 4},
{as: AMOVFL, a1: C_REG, a6: C_32CON, type_: 69, size: 4},
{as: ASYSCALL, type_: 5, size: 4},
{as: ASYSCALL, a1: C_REG, type_: 77, size: 12},
{as: ASYSCALL, a1: C_SCON, type_: 77, size: 12},
{as: ABEQ, a6: C_SBRA, type_: 16, size: 4},
{as: ABEQ, a1: C_CREG, a6: C_SBRA, type_: 16, size: 4},
{as: ABR, a6: C_LBRA, type_: 11, size: 4}, // b label
{as: ABR, a6: C_LBRAPIC, type_: 11, size: 8}, // b label; nop
{as: ABR, a6: C_LR, type_: 18, size: 4}, // blr
{as: ABR, a6: C_CTR, type_: 18, size: 4}, // bctr
{as: ABC, a1: C_SCON, a2: C_CRBIT, a6: C_SBRA, type_: 16, size: 4}, // bc bo, bi, label
{as: ABC, a1: C_SCON, a2: C_CRBIT, a6: C_LBRA, type_: 17, size: 4}, // bc bo, bi, label
{as: ABC, a1: C_SCON, a2: C_CRBIT, a6: C_LR, type_: 18, size: 4}, // bclr bo, bi
{as: ABC, a1: C_SCON, a2: C_CRBIT, a3: C_SCON, a6: C_LR, type_: 18, size: 4}, // bclr bo, bi, bh
{as: ABC, a1: C_SCON, a2: C_CRBIT, a6: C_CTR, type_: 18, size: 4}, // bcctr bo, bi
{as: ABDNZ, a6: C_SBRA, type_: 16, size: 4},
{as: ASYSCALL, a1: C_U15CON, type_: 77, size: 12},
{as: ABEQ, a6: C_BRA, type_: 16, size: 4},
{as: ABEQ, a1: C_CREG, a6: C_BRA, type_: 16, size: 4},
{as: ABR, a6: C_BRA, type_: 11, size: 4}, // b label
{as: ABR, a6: C_BRAPIC, type_: 11, size: 8}, // b label; nop
{as: ABR, a6: C_LR, type_: 18, size: 4}, // blr
{as: ABR, a6: C_CTR, type_: 18, size: 4}, // bctr
{as: ABC, a1: C_U15CON, a2: C_CRBIT, a6: C_BRA, type_: 16, size: 4}, // bc bo, bi, label
{as: ABC, a1: C_U15CON, a2: C_CRBIT, a6: C_LR, type_: 18, size: 4}, // bclr bo, bi
{as: ABC, a1: C_U15CON, a2: C_CRBIT, a3: C_U15CON, a6: C_LR, type_: 18, size: 4}, // bclr bo, bi, bh
{as: ABC, a1: C_U15CON, a2: C_CRBIT, a6: C_CTR, type_: 18, size: 4}, // bcctr bo, bi
{as: ABDNZ, a6: C_BRA, type_: 16, size: 4},
{as: ASYNC, type_: 46, size: 4},
{as: AWORD, a1: C_LCON, type_: 40, size: 4},
{as: AWORD, a1: C_32CON, type_: 40, size: 4},
{as: ADWORD, a1: C_64CON, type_: 31, size: 8},
{as: ADWORD, a1: C_LACON, type_: 31, size: 8},
{as: AADDME, a1: C_REG, a6: C_REG, type_: 47, size: 4},
@ -313,19 +306,19 @@ var optabBase = []Optab{
{as: AREMU, a1: C_REG, a2: C_REG, a6: C_REG, type_: 50, size: 16},
{as: AREMD, a1: C_REG, a6: C_REG, type_: 51, size: 12},
{as: AREMD, a1: C_REG, a2: C_REG, a6: C_REG, type_: 51, size: 12},
{as: AMTFSB0, a1: C_SCON, type_: 52, size: 4},
{as: AMTFSB0, a1: C_U15CON, type_: 52, size: 4},
/* Other ISA 2.05+ instructions */
{as: APOPCNTD, a1: C_REG, a6: C_REG, type_: 93, size: 4}, /* population count, x-form */
{as: ACMPB, a1: C_REG, a2: C_REG, a6: C_REG, type_: 92, size: 4}, /* compare byte, x-form */
{as: ACMPEQB, a1: C_REG, a2: C_REG, a6: C_CREG, type_: 92, size: 4}, /* compare equal byte, x-form, ISA 3.0 */
{as: ACMPEQB, a1: C_REG, a6: C_REG, type_: 70, size: 4},
{as: AFTDIV, a1: C_FREG, a2: C_FREG, a6: C_SCON, type_: 92, size: 4}, /* floating test for sw divide, x-form */
{as: AFTSQRT, a1: C_FREG, a6: C_SCON, type_: 93, size: 4}, /* floating test for sw square root, x-form */
{as: ACOPY, a1: C_REG, a6: C_REG, type_: 92, size: 4}, /* copy/paste facility, x-form */
{as: ADARN, a1: C_SCON, a6: C_REG, type_: 92, size: 4}, /* deliver random number, x-form */
{as: AMADDHD, a1: C_REG, a2: C_REG, a3: C_REG, a6: C_REG, type_: 83, size: 4}, /* multiply-add high/low doubleword, va-form */
{as: AADDEX, a1: C_REG, a2: C_REG, a3: C_SCON, a6: C_REG, type_: 94, size: 4}, /* add extended using alternate carry, z23-form */
{as: ACRAND, a1: C_CRBIT, a2: C_CRBIT, a6: C_CRBIT, type_: 2, size: 4}, /* logical ops for condition register bits xl-form */
{as: AFTDIV, a1: C_FREG, a2: C_FREG, a6: C_U15CON, type_: 92, size: 4}, /* floating test for sw divide, x-form */
{as: AFTSQRT, a1: C_FREG, a6: C_U15CON, type_: 93, size: 4}, /* floating test for sw square root, x-form */
{as: ACOPY, a1: C_REG, a6: C_REG, type_: 92, size: 4}, /* copy/paste facility, x-form */
{as: ADARN, a1: C_U15CON, a6: C_REG, type_: 92, size: 4}, /* deliver random number, x-form */
{as: AMADDHD, a1: C_REG, a2: C_REG, a3: C_REG, a6: C_REG, type_: 83, size: 4}, /* multiply-add high/low doubleword, va-form */
{as: AADDEX, a1: C_REG, a2: C_REG, a3: C_U15CON, a6: C_REG, type_: 94, size: 4}, /* add extended using alternate carry, z23-form */
{as: ACRAND, a1: C_CRBIT, a2: C_CRBIT, a6: C_CRBIT, type_: 2, size: 4}, /* logical ops for condition register bits xl-form */
/* Misc ISA 3.0 instructions */
{as: ASETB, a1: C_CREG, a6: C_REG, type_: 110, size: 4},
@ -368,7 +361,7 @@ var optabBase = []Optab{
/* Vector shift */
{as: AVS, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector shift, vx-form */
{as: AVSA, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector shift algebraic, vx-form */
{as: AVSOI, a1: C_ANDCON, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector shift by octet immediate, va-form */
{as: AVSOI, a1: C_U16CON, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector shift by octet immediate, va-form */
/* Vector count */
{as: AVCLZ, a1: C_VREG, a6: C_VREG, type_: 85, size: 4}, /* vector count leading zeros, vx-form */
@ -392,10 +385,8 @@ var optabBase = []Optab{
{as: AVSEL, a1: C_VREG, a2: C_VREG, a3: C_VREG, a6: C_VREG, type_: 83, size: 4}, /* vector select, va-form */
/* Vector splat */
{as: AVSPLTB, a1: C_SCON, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector splat, vx-form */
{as: AVSPLTB, a1: C_ADDCON, a2: C_VREG, a6: C_VREG, type_: 82, size: 4},
{as: AVSPLTISB, a1: C_SCON, a6: C_VREG, type_: 82, size: 4}, /* vector splat immediate, vx-form */
{as: AVSPLTISB, a1: C_ADDCON, a6: C_VREG, type_: 82, size: 4},
{as: AVSPLTB, a1: C_S16CON, a2: C_VREG, a6: C_VREG, type_: 82, size: 4},
{as: AVSPLTISB, a1: C_S16CON, a6: C_VREG, type_: 82, size: 4},
/* Vector AES */
{as: AVCIPH, a1: C_VREG, a2: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector AES cipher, vx-form */
@ -403,7 +394,7 @@ var optabBase = []Optab{
{as: AVSBOX, a1: C_VREG, a6: C_VREG, type_: 82, size: 4}, /* vector AES subbytes, vx-form */
/* Vector SHA */
{as: AVSHASIGMA, a1: C_ANDCON, a2: C_VREG, a3: C_ANDCON, a6: C_VREG, type_: 82, size: 4}, /* vector SHA sigma, vx-form */
{as: AVSHASIGMA, a1: C_U16CON, a2: C_VREG, a3: C_U16CON, a6: C_VREG, type_: 82, size: 4}, /* vector SHA sigma, vx-form */
/* VSX vector load */
{as: ALXVD2X, a1: C_XOREG, a6: C_VSREG, type_: 87, size: 4}, /* vsx vector load, xx1-form */
@ -447,14 +438,14 @@ var optabBase = []Optab{
{as: AXXMRGHW, a1: C_VSREG, a2: C_VSREG, a6: C_VSREG, type_: 90, size: 4}, /* vsx merge, xx3-form */
/* VSX splat */
{as: AXXSPLTW, a1: C_VSREG, a3: C_SCON, a6: C_VSREG, type_: 89, size: 4}, /* vsx splat, xx2-form */
{as: AXXSPLTIB, a1: C_SCON, a6: C_VSREG, type_: 100, size: 4}, /* vsx splat, xx2-form */
{as: AXXSPLTW, a1: C_VSREG, a3: C_U15CON, a6: C_VSREG, type_: 89, size: 4}, /* vsx splat, xx2-form */
{as: AXXSPLTIB, a1: C_U15CON, a6: C_VSREG, type_: 100, size: 4}, /* vsx splat, xx2-form */
/* VSX permute */
{as: AXXPERM, a1: C_VSREG, a2: C_VSREG, a6: C_VSREG, type_: 90, size: 4}, /* vsx permute, xx3-form */
/* VSX shift */
{as: AXXSLDWI, a1: C_VSREG, a2: C_VSREG, a3: C_SCON, a6: C_VSREG, type_: 90, size: 4}, /* vsx shift immediate, xx3-form */
{as: AXXSLDWI, a1: C_VSREG, a2: C_VSREG, a3: C_U15CON, a6: C_VSREG, type_: 90, size: 4}, /* vsx shift immediate, xx3-form */
/* VSX reverse bytes */
{as: AXXBRQ, a1: C_VSREG, a6: C_VSREG, type_: 101, size: 4}, /* vsx reverse bytes */
@ -479,45 +470,45 @@ var optabBase = []Optab{
{as: ACMP, a1: C_REG, a6: C_REG, type_: 70, size: 4},
{as: ACMP, a1: C_REG, a2: C_CREG, a6: C_REG, type_: 70, size: 4},
{as: ACMP, a1: C_REG, a6: C_ADDCON, type_: 71, size: 4},
{as: ACMP, a1: C_REG, a2: C_CREG, a6: C_ADDCON, type_: 71, size: 4},
{as: ACMP, a1: C_REG, a6: C_S16CON, type_: 71, size: 4},
{as: ACMP, a1: C_REG, a2: C_CREG, a6: C_S16CON, type_: 71, size: 4},
{as: ACMPU, a1: C_REG, a6: C_REG, type_: 70, size: 4},
{as: ACMPU, a1: C_REG, a2: C_CREG, a6: C_REG, type_: 70, size: 4},
{as: ACMPU, a1: C_REG, a6: C_ANDCON, type_: 71, size: 4},
{as: ACMPU, a1: C_REG, a2: C_CREG, a6: C_ANDCON, type_: 71, size: 4},
{as: ACMPU, a1: C_REG, a6: C_U16CON, type_: 71, size: 4},
{as: ACMPU, a1: C_REG, a2: C_CREG, a6: C_U16CON, type_: 71, size: 4},
{as: AFCMPO, a1: C_FREG, a6: C_FREG, type_: 70, size: 4},
{as: AFCMPO, a1: C_FREG, a2: C_CREG, a6: C_FREG, type_: 70, size: 4},
{as: ATW, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 60, size: 4},
{as: ATW, a1: C_LCON, a2: C_REG, a6: C_ADDCON, type_: 61, size: 4},
{as: ATW, a1: C_32CON, a2: C_REG, a6: C_REG, type_: 60, size: 4},
{as: ATW, a1: C_32CON, a2: C_REG, a6: C_S16CON, type_: 61, size: 4},
{as: ADCBF, a1: C_SOREG, type_: 43, size: 4},
{as: ADCBF, a1: C_XOREG, type_: 43, size: 4},
{as: ADCBF, a1: C_XOREG, a2: C_REG, a6: C_SCON, type_: 43, size: 4},
{as: ADCBF, a1: C_SOREG, a6: C_SCON, type_: 43, size: 4},
{as: ADCBF, a1: C_XOREG, a6: C_SCON, type_: 43, size: 4},
{as: ADCBF, a1: C_XOREG, a2: C_REG, a6: C_U15CON, type_: 43, size: 4},
{as: ADCBF, a1: C_SOREG, a6: C_U15CON, type_: 43, size: 4},
{as: ADCBF, a1: C_XOREG, a6: C_U15CON, type_: 43, size: 4},
{as: ASTDCCC, a1: C_REG, a2: C_REG, a6: C_XOREG, type_: 44, size: 4},
{as: ASTDCCC, a1: C_REG, a6: C_XOREG, type_: 44, size: 4},
{as: ALDAR, a1: C_XOREG, a6: C_REG, type_: 45, size: 4},
{as: ALDAR, a1: C_XOREG, a3: C_ANDCON, a6: C_REG, type_: 45, size: 4},
{as: ALDAR, a1: C_XOREG, a3: C_U16CON, a6: C_REG, type_: 45, size: 4},
{as: AEIEIO, type_: 46, size: 4},
{as: ATLBIE, a1: C_REG, type_: 49, size: 4},
{as: ATLBIE, a1: C_SCON, a6: C_REG, type_: 49, size: 4},
{as: ATLBIE, a1: C_U15CON, a6: C_REG, type_: 49, size: 4},
{as: ASLBMFEE, a1: C_REG, a6: C_REG, type_: 55, size: 4},
{as: ASLBMTE, a1: C_REG, a6: C_REG, type_: 55, size: 4},
{as: ASTSW, a1: C_REG, a6: C_XOREG, type_: 44, size: 4},
{as: ASTSW, a1: C_REG, a3: C_LCON, a6: C_ZOREG, type_: 41, size: 4},
{as: ASTSW, a1: C_REG, a3: C_32CON, a6: C_ZOREG, type_: 41, size: 4},
{as: ALSW, a1: C_XOREG, a6: C_REG, type_: 45, size: 4},
{as: ALSW, a1: C_ZOREG, a3: C_LCON, a6: C_REG, type_: 42, size: 4},
{as: ALSW, a1: C_ZOREG, a3: C_32CON, a6: C_REG, type_: 42, size: 4},
{as: obj.AUNDEF, type_: 78, size: 4},
{as: obj.APCDATA, a1: C_LCON, a6: C_LCON, type_: 0, size: 0},
{as: obj.AFUNCDATA, a1: C_SCON, a6: C_ADDR, type_: 0, size: 0},
{as: obj.APCDATA, a1: C_32CON, a6: C_32CON, type_: 0, size: 0},
{as: obj.AFUNCDATA, a1: C_U15CON, a6: C_ADDR, type_: 0, size: 0},
{as: obj.ANOP, type_: 0, size: 0},
{as: obj.ANOP, a1: C_LCON, type_: 0, size: 0}, // NOP operand variations added for #40689
{as: obj.ANOP, a1: C_REG, type_: 0, size: 0}, // to preserve previous behavior
{as: obj.ANOP, a1: C_32CON, type_: 0, size: 0}, // NOP operand variations added for #40689
{as: obj.ANOP, a1: C_REG, type_: 0, size: 0}, // to preserve previous behavior
{as: obj.ANOP, a1: C_FREG, type_: 0, size: 0},
{as: obj.ADUFFZERO, a6: C_LBRA, type_: 11, size: 4}, // same as ABR/ABL
{as: obj.ADUFFCOPY, a6: C_LBRA, type_: 11, size: 4}, // same as ABR/ABL
{as: obj.APCALIGN, a1: C_LCON, type_: 0, size: 0}, // align code
{as: obj.ADUFFZERO, a6: C_BRA, type_: 11, size: 4}, // same as ABR/ABL
{as: obj.ADUFFCOPY, a6: C_BRA, type_: 11, size: 4}, // same as ABR/ABL
{as: obj.APCALIGN, a1: C_32CON, type_: 0, size: 0}, // align code
}
// These are opcodes above which may generate different sequences depending on whether prefix opcode support
@ -552,7 +543,7 @@ var prefixableOptab = []PrefixableOptab{
{Optab: Optab{as: AMOVD, a1: C_REG, a6: C_LOREG, type_: 35, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AMOVD, a1: C_REG, a6: C_ADDR, type_: 74, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AMOVW, a1: C_LCON, a6: C_REG, type_: 19, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AMOVW, a1: C_32CON, a6: C_REG, type_: 19, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AMOVW, a1: C_LACON, a6: C_REG, type_: 26, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AMOVW, a1: C_LOREG, a6: C_REG, type_: 36, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AMOVW, a1: C_ADDR, a6: C_REG, type_: 75, size: 8}, minGOPPC64: 10, pfxsize: 8},
@ -574,8 +565,8 @@ var prefixableOptab = []PrefixableOptab{
{Optab: Optab{as: AFMOVD, a1: C_FREG, a6: C_LOREG, type_: 35, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AFMOVD, a1: C_FREG, a6: C_ADDR, type_: 74, size: 8}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AADD, a1: C_LCON, a2: C_REG, a6: C_REG, type_: 22, size: 12}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AADD, a1: C_LCON, a6: C_REG, type_: 22, size: 12}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AADD, a1: C_32CON, a2: C_REG, a6: C_REG, type_: 22, size: 12}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AADD, a1: C_32CON, a6: C_REG, type_: 22, size: 12}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AADD, a1: C_S34CON, a2: C_REG, a6: C_REG, type_: 22, size: 20}, minGOPPC64: 10, pfxsize: 8},
{Optab: Optab{as: AADD, a1: C_S34CON, a6: C_REG, type_: 22, size: 20}, minGOPPC64: 10, pfxsize: 8},
}
@ -955,7 +946,7 @@ func (c *ctxt9) aclass(a *obj.Addr) int {
f64 := a.Val.(float64)
if f64 == 0 {
if math.Signbit(f64) {
return C_ADDCON
return C_S16CON
}
return C_ZCON
}
@ -1017,7 +1008,7 @@ func (c *ctxt9) aclass(a *obj.Addr) int {
case sbits <= 16:
return C_U16CON
case sbits <= 31:
return C_U32CON
return C_U31CON
case sbits <= 32:
return C_U32CON
case sbits <= 33:
@ -1041,9 +1032,9 @@ func (c *ctxt9) aclass(a *obj.Addr) int {
case obj.TYPE_BRANCH:
if a.Sym != nil && c.ctxt.Flag_dynlink && !pfxEnabled {
return C_LBRAPIC
return C_BRAPIC
}
return C_SBRA
return C_BRA
}
return C_GOK
@ -1114,7 +1105,7 @@ func (c *ctxt9) oplook(p *obj.Prog) *Optab {
return &ops[0]
}
// Compare two operand types (ex C_REG, or C_SCON)
// Compare two operand types (ex C_REG, or C_U15CON)
// and return true if b is compatible with a.
//
// Argument comparison isn't reflexitive, so care must be taken.
@ -1145,13 +1136,20 @@ func cmp(a int, b int) bool {
return cmp(C_U5CON, b)
case C_U15CON:
return cmp(C_U8CON, b)
case C_U16CON:
return cmp(C_U15CON, b)
case C_S16CON:
return cmp(C_U15CON, b)
case C_32CON:
case C_U16CON:
return cmp(C_U15CON, b)
case C_16CON:
return cmp(C_S16CON, b) || cmp(C_U16CON, b)
case C_U31CON:
return cmp(C_U16CON, b)
case C_U32CON:
return cmp(C_U31CON, b)
case C_S32CON:
return cmp(C_U31CON, b) || cmp(C_S16CON, b)
case C_32CON:
return cmp(C_S32CON, b) || cmp(C_U32CON, b)
case C_S34CON:
return cmp(C_32CON, b)
case C_64CON:
@ -1160,9 +1158,6 @@ func cmp(a int, b int) bool {
case C_LACON:
return cmp(C_SACON, b)
case C_LBRA:
return cmp(C_SBRA, b)
case C_SOREG:
return cmp(C_ZOREG, b)
@ -2541,34 +2536,26 @@ func asmout(c *ctxt9, p *obj.Prog, o *Optab, out *[5]uint32) {
}
o1 = AOP_RRR(c.oprrr(p.As), uint32(p.To.Reg), uint32(r), uint32(p.From.Reg))
case 3: /* mov $soreg/addcon/andcon/ucon, r ==> addis/oris/addi/ori $i,reg',r */
case 3: /* mov $soreg/16con, r ==> addi/ori $i,reg',r */
d := c.vregoff(&p.From)
v := int32(d)
r := int(p.From.Reg)
// p.From may be a constant value or an offset(reg) type argument.
isZeroOrR0 := r&0x1f == 0
if r0iszero != 0 /*TypeKind(100016)*/ && p.To.Reg == 0 && (r != 0 || v != 0) {
c.ctxt.Diag("literal operation on R0\n%v", p)
}
a := OP_ADDI
if int64(int16(d)) != d {
// Operand is 16 bit value with sign bit set
if o.a1 == C_ANDCON {
// Needs unsigned 16 bit so use ORI
if isZeroOrR0 {
o1 = LOP_IRR(uint32(OP_ORI), uint32(p.To.Reg), uint32(0), uint32(v))
break
}
// With ADDCON, needs signed 16 bit value, fall through to use ADDI
} else if o.a1 != C_ADDCON {
log.Fatalf("invalid handling of %v", p)
if int64(int16(d)) == d {
// MOVD $int16, Ry or MOVD $offset(Rx), Ry
o1 = AOP_IRR(uint32(OP_ADDI), uint32(p.To.Reg), uint32(r), uint32(v))
} else {
// MOVD $uint16, Ry
if int64(uint16(d)) != d || (r != 0 && r != REGZERO) {
c.ctxt.Diag("Rule expects a uint16 constant load. got:\n%v", p)
}
o1 = LOP_IRR(uint32(OP_ORI), uint32(p.To.Reg), uint32(0), uint32(v))
}
o1 = AOP_IRR(uint32(a), uint32(p.To.Reg), uint32(r), uint32(v))
case 4: /* add/mul $scon,[r1],r2 */
v := c.regoff(&p.From)
@ -2654,7 +2641,7 @@ func asmout(c *ctxt9, p *obj.Prog, o *Optab, out *[5]uint32) {
}
o1 = AOP_RRR(c.oprrr(p.As), uint32(p.To.Reg), uint32(p.From.Reg), uint32(r))
case 11: /* br/bl lbra */
case 11: /* br/bl bra */
v := int32(0)
if p.To.Target() != nil {
@ -2688,7 +2675,7 @@ func asmout(c *ctxt9, p *obj.Prog, o *Optab, out *[5]uint32) {
case 13: /* mov[bhwd]{z,} r,r */
// This needs to handle "MOV* $0, Rx". This shows up because $0 also
// matches C_REG if r0iszero. This happens because C_REG sorts before C_ANDCON
// matches C_REG if r0iszero. This happens because C_REG sorts before C_U16CON
// TODO: fix the above behavior and cleanup this exception.
if p.From.Type == obj.TYPE_CONST {
o1 = LOP_IRR(OP_ADDI, REGZERO, uint32(p.To.Reg), 0)
@ -2776,8 +2763,7 @@ func asmout(c *ctxt9, p *obj.Prog, o *Optab, out *[5]uint32) {
c.ctxt.Diag("unexpected op in rldc case\n%v", p)
}
case 17, /* bc bo,bi,lbra (same for now) */
16: /* bc bo,bi,sbra */
case 16: /* bc bo,bi,bra */
a := 0
r := int(p.Reg)
@ -2921,8 +2907,8 @@ func asmout(c *ctxt9, p *obj.Prog, o *Optab, out *[5]uint32) {
r = int(p.To.Reg)
}
// With ADDCON operand, generate 2 instructions using ADDI for signed value,
// with LCON operand generate 3 instructions.
// With S16CON operand, generate 2 instructions using ADDI for signed value,
// with 32CON operand generate 3 instructions.
if o.size == 8 {
o1 = LOP_IRR(OP_ADDI, REGZERO, REGTMP, uint32(int32(d)))
o2 = LOP_RRR(c.oprrr(p.As), uint32(p.To.Reg), REGTMP, uint32(r))

View file

@ -516,17 +516,19 @@ func TestAddrClassifier(t *testing.T) {
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 32}, C_U8CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 14}, C_U15CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 15}, C_U16CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 + 1<<16}, C_U32CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 + 1<<16}, C_U31CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 31}, C_U32CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 32}, C_S34CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 1 << 33}, C_64CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -1}, C_S16CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -0x10001}, C_S32CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: 0x10001}, C_U31CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 33)}, C_S34CON},
{obj.Addr{Type: obj.TYPE_CONST, Name: obj.NAME_NONE, Offset: -(1 << 34)}, C_64CON},
// Branch like arguments
{obj.Addr{Type: obj.TYPE_BRANCH, Sym: &obj.LSym{Type: objabi.SDATA}}, cmplx{C_SBRA, C_LBRAPIC, C_LBRAPIC, C_SBRA}},
{obj.Addr{Type: obj.TYPE_BRANCH}, C_SBRA},
{obj.Addr{Type: obj.TYPE_BRANCH, Sym: &obj.LSym{Type: objabi.SDATA}}, cmplx{C_BRA, C_BRAPIC, C_BRAPIC, C_BRA}},
{obj.Addr{Type: obj.TYPE_BRANCH}, C_BRA},
}
pic_ctxt9 := ctxt9{ctxt: &obj.Link{Flag_shared: true, Arch: &Linkppc64}, autosize: 0}

View file

@ -69,8 +69,8 @@ func (ex *Examiner) Populate(rdr *dwarf.Reader) error {
return nil
}
func (e *Examiner) DIEs() []*dwarf.Entry {
return e.dies
func (ex *Examiner) DIEs() []*dwarf.Entry {
return ex.dies
}
func indent(ilevel int) {

View file

@ -45,6 +45,7 @@ import (
"fmt"
"internal/abi"
"log"
"math/rand"
"os"
"sort"
"strconv"
@ -122,10 +123,11 @@ func trampoline(ctxt *Link, s loader.Sym) {
}
if ldr.SymValue(rs) == 0 && ldr.SymType(rs) != sym.SDYNIMPORT && ldr.SymType(rs) != sym.SUNDEFEXT {
// Symbols in the same package are laid out together.
// Symbols in the same package are laid out together (if we
// don't randomize the function order).
// Except that if SymPkg(s) == "", it is a host object symbol
// which may call an external symbol via PLT.
if ldr.SymPkg(s) != "" && ldr.SymPkg(rs) == ldr.SymPkg(s) {
if ldr.SymPkg(s) != "" && ldr.SymPkg(rs) == ldr.SymPkg(s) && *flagRandLayout == 0 {
// RISC-V is only able to reach +/-1MiB via a JAL instruction.
// We need to generate a trampoline when an address is
// currently unknown.
@ -134,7 +136,7 @@ func trampoline(ctxt *Link, s loader.Sym) {
}
}
// Runtime packages are laid out together.
if isRuntimeDepPkg(ldr.SymPkg(s)) && isRuntimeDepPkg(ldr.SymPkg(rs)) {
if isRuntimeDepPkg(ldr.SymPkg(s)) && isRuntimeDepPkg(ldr.SymPkg(rs)) && *flagRandLayout == 0 {
continue
}
}
@ -2397,6 +2399,26 @@ func (ctxt *Link) textaddress() {
ldr := ctxt.loader
if *flagRandLayout != 0 {
r := rand.New(rand.NewSource(*flagRandLayout))
textp := ctxt.Textp
i := 0
// don't move the buildid symbol
if len(textp) > 0 && ldr.SymName(textp[0]) == "go:buildid" {
i++
}
// Skip over C symbols, as functions in a (C object) section must stay together.
// TODO: maybe we can move a section as a whole.
// Note: we load C symbols before Go symbols, so we can scan from the start.
for i < len(textp) && (ldr.SubSym(textp[i]) != 0 || ldr.AttrSubSymbol(textp[i])) {
i++
}
textp = textp[i:]
r.Shuffle(len(textp), func(i, j int) {
textp[i], textp[j] = textp[j], textp[i]
})
}
text := ctxt.xdefine("runtime.text", sym.STEXT, 0)
etext := ctxt.xdefine("runtime.etext", sym.STEXT, 0)
ldr.SetSymSect(text, sect)

View file

@ -201,7 +201,7 @@ func (d *deadcodePass) flood() {
rs := r.Sym()
if d.ldr.IsItab(rs) {
// This relocation can also point at an itab, in which case it
// means "the _type field of that itab".
// means "the Type field of that itab".
rs = decodeItabType(d.ldr, d.ctxt.Arch, rs)
}
if !d.ldr.IsGoType(rs) && !d.ctxt.linkShared {

View file

@ -301,8 +301,8 @@ func decodetypeGcprogShlib(ctxt *Link, data []byte) uint64 {
return decodeInuxi(ctxt.Arch, data[2*int32(ctxt.Arch.PtrSize)+8+1*int32(ctxt.Arch.PtrSize):], ctxt.Arch.PtrSize)
}
// decodeItabType returns the itab._type field from an itab.
// decodeItabType returns the itab.Type field from an itab.
func decodeItabType(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym) loader.Sym {
relocs := ldr.Relocs(symIdx)
return decodeRelocSym(ldr, symIdx, &relocs, int32(arch.PtrSize))
return decodeRelocSym(ldr, symIdx, &relocs, int32(abi.ITabTypeOff(arch.PtrSize)))
}

View file

@ -1786,7 +1786,7 @@ func dwarfGenerateDebugInfo(ctxt *Link) {
"type:internal/abi.SliceType",
"type:internal/abi.StructType",
"type:internal/abi.InterfaceType",
"type:runtime.itab",
"type:internal/abi.ITab",
"type:internal/abi.Imethod"} {
d.defgotype(d.lookupOrDiag(typ))
}

View file

@ -65,7 +65,7 @@ func TestRuntimeTypesPresent(t *testing.T) {
"internal/abi.SliceType": true,
"internal/abi.StructType": true,
"internal/abi.InterfaceType": true,
"runtime.itab": true,
"internal/abi.ITab": true,
}
found := findTypes(t, dwarf, want)

View file

@ -478,13 +478,11 @@ func (ctxt *Link) domacho() {
if ctxt.LinkMode == LinkInternal && machoPlatform == PLATFORM_MACOS {
var version uint32
switch ctxt.Arch.Family {
case sys.AMD64:
case sys.ARM64, sys.AMD64:
// This must be fairly recent for Apple signing (go.dev/issue/30488).
// Having too old a version here was also implicated in some problems
// calling into macOS libraries (go.dev/issue/56784).
// In general this can be the most recent supported macOS version.
version = 10<<16 | 13<<8 | 0<<0 // 10.13.0
case sys.ARM64:
version = 11<<16 | 0<<8 | 0<<0 // 11.0.0
}
ml := newMachoLoad(ctxt.Arch, LC_BUILD_VERSION, 4)

View file

@ -102,6 +102,7 @@ var (
FlagTextAddr = flag.Int64("T", -1, "set the start address of text symbols")
flagEntrySymbol = flag.String("E", "", "set `entry` symbol name")
flagPruneWeakMap = flag.Bool("pruneweakmap", true, "prune weak mapinit refs")
flagRandLayout = flag.Int64("randlayout", 0, "randomize function layout")
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
memprofile = flag.String("memprofile", "", "write memory profile to `file`")
memprofilerate = flag.Int64("memprofilerate", 0, "set runtime.MemProfileRate to `rate`")

View file

@ -827,9 +827,8 @@ func expandGoroot(s string) string {
}
const (
BUCKETSIZE = 256 * abi.MINFUNC
SUBBUCKETS = 16
SUBBUCKETSIZE = BUCKETSIZE / SUBBUCKETS
SUBBUCKETSIZE = abi.FuncTabBucketSize / SUBBUCKETS
NOIDX = 0x7fffffff
)
@ -847,7 +846,7 @@ func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) {
// that map to that subbucket.
n := int32((max - min + SUBBUCKETSIZE - 1) / SUBBUCKETSIZE)
nbuckets := int32((max - min + BUCKETSIZE - 1) / BUCKETSIZE)
nbuckets := int32((max - min + abi.FuncTabBucketSize - 1) / abi.FuncTabBucketSize)
size := 4*int64(nbuckets) + int64(n)
@ -878,7 +877,7 @@ func (ctxt *Link) findfunctab(state *pclntab, container loader.Bitmap) {
q = ldr.SymValue(e)
}
//print("%d: [%lld %lld] %s\n", idx, p, q, s->name);
//fmt.Printf("%d: [%x %x] %s\n", idx, p, q, ldr.SymName(s))
for ; p < q; p += SUBBUCKETSIZE {
i = int((p - min) / SUBBUCKETSIZE)
if indexes[i] > idx {

View file

@ -242,10 +242,6 @@ func parseArmAttributes(e binary.ByteOrder, data []byte) (found bool, ehdrFlags
// object, and the returned ehdrFlags contains what this Load function computes.
// TODO: find a better place for this logic.
func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader, pkg string, length int64, pn string, initEhdrFlags uint32) (textp []loader.Sym, ehdrFlags uint32, err error) {
newSym := func(name string, version int) loader.Sym {
return l.CreateStaticSym(name)
}
lookup := l.LookupOrCreateCgoExport
errorf := func(str string, args ...interface{}) ([]loader.Sym, uint32, error) {
return nil, 0, fmt.Errorf("loadelf: %s: %v", pn, fmt.Sprintf(str, args...))
}
@ -515,7 +511,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader,
}
sectsymNames[name] = true
sb := l.MakeSymbolUpdater(lookup(name, localSymVersion))
sb := l.MakeSymbolUpdater(l.LookupOrCreateCgoExport(name, localSymVersion))
switch sect.flags & (elf.SHF_ALLOC | elf.SHF_WRITE | elf.SHF_EXECINSTR) {
default:
@ -556,7 +552,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader,
for i := 1; i < elfobj.nsymtab; i++ {
var elfsym ElfSym
if err := readelfsym(newSym, lookup, l, arch, elfobj, i, &elfsym, 1, localSymVersion); err != nil {
if err := readelfsym(l, arch, elfobj, i, &elfsym, 1, localSymVersion); err != nil {
return errorf("%s: malformed elf file: %v", pn, err)
}
symbols[i] = elfsym.sym
@ -770,7 +766,7 @@ func Load(l *loader.Loader, arch *sys.Arch, localSymVersion int, f *bio.Reader,
rSym = 0
} else {
var elfsym ElfSym
if err := readelfsym(newSym, lookup, l, arch, elfobj, int(symIdx), &elfsym, 0, 0); err != nil {
if err := readelfsym(l, arch, elfobj, int(symIdx), &elfsym, 0, 0); err != nil {
return errorf("malformed elf file: %v", err)
}
elfsym.sym = symbols[symIdx]
@ -847,7 +843,7 @@ func elfmap(elfobj *ElfObj, sect *ElfSect) (err error) {
return nil
}
func readelfsym(newSym, lookup func(string, int) loader.Sym, l *loader.Loader, arch *sys.Arch, elfobj *ElfObj, i int, elfsym *ElfSym, needSym int, localSymVersion int) (err error) {
func readelfsym(l *loader.Loader, arch *sys.Arch, elfobj *ElfObj, i int, elfsym *ElfSym, needSym int, localSymVersion int) (err error) {
if i >= elfobj.nsymtab || i < 0 {
err = fmt.Errorf("invalid elf symbol index")
return err
@ -898,7 +894,7 @@ func readelfsym(newSym, lookup func(string, int) loader.Sym, l *loader.Loader, a
switch elfsym.bind {
case elf.STB_GLOBAL:
if needSym != 0 {
s = lookup(elfsym.name, 0)
s = l.LookupOrCreateCgoExport(elfsym.name, 0)
// for global scoped hidden symbols we should insert it into
// symbol hash table, but mark them as hidden.
@ -927,7 +923,7 @@ func readelfsym(newSym, lookup func(string, int) loader.Sym, l *loader.Loader, a
// We need to be able to look this up,
// so put it in the hash table.
if needSym != 0 {
s = lookup(elfsym.name, localSymVersion)
s = l.LookupOrCreateCgoExport(elfsym.name, localSymVersion)
l.SetAttrVisibilityHidden(s, true)
}
break
@ -940,13 +936,13 @@ func readelfsym(newSym, lookup func(string, int) loader.Sym, l *loader.Loader, a
// FIXME: pass empty string here for name? This would
// reduce mem use, but also (possibly) make it harder
// to debug problems.
s = newSym(elfsym.name, localSymVersion)
s = l.CreateStaticSym(elfsym.name)
l.SetAttrVisibilityHidden(s, true)
}
case elf.STB_WEAK:
if needSym != 0 {
s = lookup(elfsym.name, 0)
s = l.LookupOrCreateCgoExport(elfsym.name, 0)
if elfsym.other == 2 {
l.SetAttrVisibilityHidden(s, true)
}

View file

@ -348,7 +348,7 @@ func TestXFlag(t *testing.T) {
}
}
var testMachOBuildVersionSrc = `
var trivialSrc = `
package main
func main() { }
`
@ -361,7 +361,7 @@ func TestMachOBuildVersion(t *testing.T) {
tmpdir := t.TempDir()
src := filepath.Join(tmpdir, "main.go")
err := os.WriteFile(src, []byte(testMachOBuildVersionSrc), 0666)
err := os.WriteFile(src, []byte(trivialSrc), 0666)
if err != nil {
t.Fatal(err)
}
@ -388,9 +388,9 @@ func TestMachOBuildVersion(t *testing.T) {
found := false
const LC_BUILD_VERSION = 0x32
checkMin := func(ver uint32) {
major, minor := (ver>>16)&0xff, (ver>>8)&0xff
if major != 10 || minor < 9 {
t.Errorf("LC_BUILD_VERSION version %d.%d < 10.9", major, minor)
major, minor, patch := (ver>>16)&0xff, (ver>>8)&0xff, (ver>>0)&0xff
if major < 11 {
t.Errorf("LC_BUILD_VERSION version %d.%d.%d < 11.0.0", major, minor, patch)
}
}
for _, cmd := range exem.Loads {
@ -1375,3 +1375,43 @@ func TestFlagS(t *testing.T) {
}
}
}
func TestRandLayout(t *testing.T) {
// Test that the -randlayout flag randomizes function order and
// generates a working binary.
testenv.MustHaveGoBuild(t)
t.Parallel()
tmpdir := t.TempDir()
src := filepath.Join(tmpdir, "hello.go")
err := os.WriteFile(src, []byte(trivialSrc), 0666)
if err != nil {
t.Fatal(err)
}
var syms [2]string
for i, seed := range []string{"123", "456"} {
exe := filepath.Join(tmpdir, "hello"+seed+".exe")
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-randlayout="+seed, "-o", exe, src)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("build failed: %v\n%s", err, out)
}
cmd = testenv.Command(t, exe)
err = cmd.Run()
if err != nil {
t.Fatalf("executable failed to run: %v\n%s", err, out)
}
cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", exe)
out, err = cmd.CombinedOutput()
if err != nil {
t.Fatalf("fail to run \"go tool nm\": %v\n%s", err, out)
}
syms[i] = string(out)
}
if syms[0] == syms[1] {
t.Errorf("randlayout with different seeds produced same layout:\n%s\n===\n\n%s", syms[0], syms[1])
}
}

187
src/cmd/preprofile/main.go Normal file
View file

@ -0,0 +1,187 @@
// Copyright 2023 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.
// Preprofile handles pprof files.
//
// Usage:
//
// go tool preprofile [-v] [-o output] [-i (pprof)input]
//
//
package main
import (
"bufio"
"flag"
"fmt"
"internal/profile"
"log"
"os"
"path/filepath"
"strconv"
)
// The current Go Compiler consumes significantly long compilation time when the PGO
// is enabled. To optimize the existing flow and reduce build time of multiple Go
// services, we create a standalone tool, PGO preprocessor, to extract information
// from collected profiling files and to cache the WeightedCallGraph in one time
// fashion. By adding the new tool to the Go compiler, it will reduce the time
// of repeated profiling file parsing and avoid WeightedCallGraph reconstruction
// in current Go Compiler.
// The format of the pre-processed output is as follows.
//
// Header
// caller_name
// callee_name
// "call site offset" "call edge weight"
// ...
// caller_name
// callee_name
// "call site offset" "call edge weight"
func usage() {
fmt.Fprintf(os.Stderr, "MUST have (pprof) input file \n")
fmt.Fprintf(os.Stderr, "usage: go tool preprofile [-v] [-o output] [-i (pprof)input] \n\n")
flag.PrintDefaults()
os.Exit(2)
}
type NodeMapKey struct {
CallerName string
CalleeName string
CallSiteOffset int // Line offset from function start line.
}
func readPprofFile(profileFile string, outputFile string, verbose bool) bool {
// open the pprof profile file
f, err := os.Open(profileFile)
if err != nil {
log.Fatal("failed to open file " + profileFile)
return false
}
defer f.Close()
p, err := profile.Parse(f)
if err != nil {
log.Fatal("failed to Parse profile file.")
return false
}
if len(p.Sample) == 0 {
// We accept empty profiles, but there is nothing to do.
return false
}
valueIndex := -1
for i, s := range p.SampleType {
// Samples count is the raw data collected, and CPU nanoseconds is just
// a scaled version of it, so either one we can find is fine.
if (s.Type == "samples" && s.Unit == "count") ||
(s.Type == "cpu" && s.Unit == "nanoseconds") {
valueIndex = i
break
}
}
if valueIndex == -1 {
log.Fatal("failed to find CPU samples count or CPU nanoseconds value-types in profile.")
return false
}
// The processing here is equivalent to cmd/compile/internal/pgo.createNamedEdgeMap.
g := profile.NewGraph(p, &profile.Options{
SampleValue: func(v []int64) int64 { return v[valueIndex] },
})
TotalEdgeWeight := int64(0)
NodeMap := make(map[NodeMapKey]int64)
for _, n := range g.Nodes {
canonicalName := n.Info.Name
// Create the key to the nodeMapKey.
nodeinfo := NodeMapKey{
CallerName: canonicalName,
CallSiteOffset: n.Info.Lineno - n.Info.StartLine,
}
if n.Info.StartLine == 0 {
if verbose {
log.Println("[PGO] warning: " + canonicalName + " relative line number is missing from the profile")
}
}
for _, e := range n.Out {
TotalEdgeWeight += e.WeightValue()
nodeinfo.CalleeName = e.Dest.Info.Name
if w, ok := NodeMap[nodeinfo]; ok {
w += e.WeightValue()
} else {
w = e.WeightValue()
NodeMap[nodeinfo] = w
}
}
}
var fNodeMap *os.File
if outputFile == "" {
fNodeMap = os.Stdout
} else {
dirPath := filepath.Dir(outputFile)
_, err := os.Stat(dirPath)
if err != nil {
log.Fatal("Directory does not exist: ", dirPath)
}
base := filepath.Base(outputFile)
outputFile = filepath.Join(dirPath, base)
// write out NodeMap to a file
fNodeMap, err = os.Create(outputFile)
if err != nil {
log.Fatal("Error creating output file:", err)
return false
}
defer fNodeMap.Close() // Close the file when done writing
}
w := bufio.NewWriter(fNodeMap)
w.WriteString("GO PREPROFILE V1\n")
count := 1
separator := " "
for key, element := range NodeMap {
line := key.CallerName + "\n"
w.WriteString(line)
line = key.CalleeName + "\n"
w.WriteString(line)
line = strconv.Itoa(key.CallSiteOffset)
line = line + separator + strconv.FormatInt(element, 10) + "\n"
w.WriteString(line)
w.Flush()
count += 1
}
if TotalEdgeWeight == 0 {
return false
}
return true
}
var dumpCode = flag.String("o", "", "dump output file ")
var input = flag.String("i", "", "input pprof file ")
var verbose = flag.Bool("v", false, "verbose log")
func main() {
log.SetFlags(0)
log.SetPrefix("preprofile: ")
flag.Usage = usage
flag.Parse()
if *input == "" {
usage()
} else {
readPprofFile(*input, *dumpCode, *verbose)
}
}

View file

@ -32,7 +32,7 @@ func TestCheckAPIFragments(t *testing.T) {
docFS := os.DirFS(filepath.Join(root, "doc", "next"))
// Check that each api/next file has a corresponding release note fragment.
for _, apiFile := range files {
if err := relnote.CheckAPIFile(rootFS, apiFile, docFS); err != nil {
if err := relnote.CheckAPIFile(rootFS, apiFile, docFS, "doc/next"); err != nil {
t.Errorf("%s: %v", apiFile, err)
}
}

View file

@ -17,7 +17,6 @@ import (
"net/http"
"slices"
"sort"
"strconv"
"strings"
"time"
)
@ -25,31 +24,23 @@ import (
// GoroutinesHandlerFunc returns a HandlerFunc that serves list of goroutine groups.
func GoroutinesHandlerFunc(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// goroutineGroup describes a group of goroutines grouped by start PC.
// goroutineGroup describes a group of goroutines grouped by name.
type goroutineGroup struct {
ID uint64 // Unique identifier (PC).
Name string // Start function.
N int // Total number of goroutines in this group.
ExecTime time.Duration // Total execution time of all goroutines in this group.
}
// Accumulate groups by PC.
groupsByPC := make(map[uint64]goroutineGroup)
// Accumulate groups by Name.
groupsByName := make(map[string]goroutineGroup)
for _, summary := range summaries {
group := groupsByPC[summary.PC]
group.ID = summary.PC
group := groupsByName[summary.Name]
group.Name = summary.Name
group.N++
group.ExecTime += summary.ExecTime
groupsByPC[summary.PC] = group
groupsByName[summary.Name] = group
}
var groups []goroutineGroup
for pc, group := range groupsByPC {
group.ID = pc
// If goroutine didn't run during the trace (no sampled PC),
// the v.ID and v.Name will be zero value.
if group.ID == 0 && group.Name == "" {
group.Name = "(Inactive, no stack trace sampled)"
}
for _, group := range groupsByName {
groups = append(groups, group)
}
slices.SortFunc(groups, func(a, b goroutineGroup) int {
@ -92,7 +83,7 @@ Click a start location to view more details about that group.<br>
</tr>
{{range $}}
<tr>
<td><code><a href="/goroutine?id={{.ID}}">{{.Name}}</a></code></td>
<td><code><a href="/goroutine?name={{.Name}}">{{or .Name "(Inactive, no stack trace sampled)"}}</a></code></td>
<td>{{.N}}</td>
<td>{{.ExecTime}}</td>
</tr>
@ -106,11 +97,7 @@ Click a start location to view more details about that group.<br>
// goroutines in a particular group.
func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
pc, err := strconv.ParseUint(r.FormValue("id"), 10, 64)
if err != nil {
http.Error(w, fmt.Sprintf("failed to parse id parameter '%v': %v", r.FormValue("id"), err), http.StatusInternalServerError)
return
}
goroutineName := r.FormValue("name")
type goroutine struct {
*trace.GoroutineSummary
@ -130,7 +117,7 @@ func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.H
for _, summary := range summaries {
totalExecTime += summary.ExecTime
if summary.PC != pc {
if summary.Name != goroutineName {
continue
}
nonOverlappingStats := summary.NonOverlappingStats()
@ -198,9 +185,8 @@ func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.H
}
sort.Strings(allRangeStats)
err = templGoroutine.Execute(w, struct {
err := templGoroutine.Execute(w, struct {
Name string
PC uint64
N int
ExecTimePercent string
MaxTotal time.Duration
@ -209,7 +195,6 @@ func GoroutineHandler(summaries map[tracev2.GoID]*trace.GoroutineSummary) http.H
RangeStats []string
}{
Name: name,
PC: pc,
N: len(goroutines),
ExecTimePercent: execTimePercent,
MaxTotal: maxTotalTime,
@ -339,19 +324,19 @@ Table of contents
</tr>
<tr>
<td>Network wait profile:</td>
<td> <a href="/io?id={{.PC}}">graph</a> <a href="/io?id={{.PC}}&raw=1" download="io.profile">(download)</a></td>
<td> <a href="/io?name={{.Name}}">graph</a> <a href="/io?name={{.Name}}&raw=1" download="io.profile">(download)</a></td>
</tr>
<tr>
<td>Sync block profile:</td>
<td> <a href="/block?id={{.PC}}">graph</a> <a href="/block?id={{.PC}}&raw=1" download="block.profile">(download)</a></td>
<td> <a href="/block?name={{.Name}}">graph</a> <a href="/block?name={{.Name}}&raw=1" download="block.profile">(download)</a></td>
</tr>
<tr>
<td>Syscall profile:</td>
<td> <a href="/syscall?id={{.PC}}">graph</a> <a href="/syscall?id={{.PC}}&raw=1" download="syscall.profile">(download)</a></td>
<td> <a href="/syscall?name={{.Name}}">graph</a> <a href="/syscall?name={{.Name}}&raw=1" download="syscall.profile">(download)</a></td>
</tr>
<tr>
<td>Scheduler wait profile:</td>
<td> <a href="/sched?id={{.PC}}">graph</a> <a href="/sched?id={{.PC}}&raw=1" download="sched.profile">(download)</a></td>
<td> <a href="/sched?name={{.Name}}">graph</a> <a href="/sched?name={{.Name}}&raw=1" download="sched.profile">(download)</a></td>
</tr>
</table>

View file

@ -167,8 +167,8 @@ func checkNetworkUnblock(t *testing.T, data format.Data) {
if netBlockEv == nil {
t.Error("failed to find a network unblock")
}
if count == 0 || count > 2 {
t.Errorf("found too many network block events: want 1 or 2, found %d", count)
if count == 0 {
t.Errorf("found zero network block events, want at least one")
}
// TODO(mknyszek): Check for the flow of this event to some slice event of a goroutine running.
}

View file

@ -14,15 +14,14 @@ import (
tracev2 "internal/trace/v2"
"net/http"
"slices"
"strconv"
"strings"
"time"
)
func pprofByGoroutine(compute computePprofFunc, t *parsedTrace) traceviewer.ProfileFunc {
return func(r *http.Request) ([]traceviewer.ProfileRecord, error) {
id := r.FormValue("id")
gToIntervals, err := pprofMatchingGoroutines(id, t)
name := r.FormValue("name")
gToIntervals, err := pprofMatchingGoroutines(name, t)
if err != nil {
return nil, err
}
@ -44,20 +43,12 @@ func pprofByRegion(compute computePprofFunc, t *parsedTrace) traceviewer.Profile
}
}
// pprofMatchingGoroutines parses the goroutine type id string (i.e. pc)
// and returns the ids of goroutines of the matching type and its interval.
// pprofMatchingGoroutines returns the ids of goroutines of the matching name and its interval.
// If the id string is empty, returns nil without an error.
func pprofMatchingGoroutines(id string, t *parsedTrace) (map[tracev2.GoID][]interval, error) {
if id == "" {
return nil, nil
}
pc, err := strconv.ParseUint(id, 10, 64) // id is string
if err != nil {
return nil, fmt.Errorf("invalid goroutine type: %v", id)
}
func pprofMatchingGoroutines(name string, t *parsedTrace) (map[tracev2.GoID][]interval, error) {
res := make(map[tracev2.GoID][]interval)
for _, g := range t.summary.Goroutines {
if g.PC != pc {
if g.Name != name {
continue
}
endTime := g.EndTime
@ -66,8 +57,8 @@ func pprofMatchingGoroutines(id string, t *parsedTrace) (map[tracev2.GoID][]inte
}
res[g.ID] = []interval{{start: g.StartTime, end: endTime}}
}
if len(res) == 0 && id != "" {
return nil, fmt.Errorf("failed to find matching goroutines for ID: %s", id)
if len(res) == 0 {
return nil, fmt.Errorf("failed to find matching goroutines for name: %s", name)
}
return res, nil
}

File diff suppressed because it is too large Load diff

View file

@ -186,9 +186,10 @@ type ObjFile interface {
// A Frame describes a single line in a source file.
type Frame struct {
Func string // name of function
File string // source file name
Line int // line in file
Func string // name of function
File string // source file name
Line int // line in file
Column int // column in file
}
// A Sym describes a single symbol in an object file.

View file

@ -129,6 +129,7 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
}
linenumber := 0
columnnumber := 0
// The llvm-symbolizer outputs the <file_name>:<line_number>:<column_number>.
// When it cannot identify the source code location, it outputs "??:0:0".
// Older versions output just the filename and line number, so we check for
@ -137,22 +138,27 @@ func (d *llvmSymbolizer) readFrame() (plugin.Frame, bool) {
fileline = ""
} else {
switch split := strings.Split(fileline, ":"); len(split) {
case 1:
// filename
fileline = split[0]
case 2, 3:
// filename:line , or
// filename:line:disc , or
fileline = split[0]
case 3:
// filename:line:column
if col, err := strconv.Atoi(split[2]); err == nil {
columnnumber = col
}
fallthrough
case 2:
// filename:line
if line, err := strconv.Atoi(split[1]); err == nil {
linenumber = line
}
fallthrough
case 1:
// filename
fileline = split[0]
default:
// Unrecognized, ignore
}
}
return plugin.Frame{Func: funcname, File: fileline, Line: linenumber}, false
return plugin.Frame{Func: funcname, File: fileline, Line: linenumber, Column: columnnumber}, false
}
// addrInfo returns the stack frame information for a specific program

Some files were not shown because too many files have changed in this diff Show more