diff --git a/.github/ISSUE_TEMPLATE/00-bug.md b/.github/ISSUE_TEMPLATE/00-bug.md deleted file mode 100644 index f056dab7dd..0000000000 --- a/.github/ISSUE_TEMPLATE/00-bug.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: Bugs -about: The go command, standard library, or anything else -title: "affected/package: " ---- - - - -### What version of Go are you using (`go version`)? - -
-$ go version - -- -### Does this issue reproduce with the latest release? - - - -### What operating system and processor architecture are you using (`go env`)? - -
go env Output-$ go env - -
-$ go version - -- -### Does this issue reproduce at the latest version of golang.org/x/vuln? - - - -### What operating system and processor architecture are you using (`go env`)? - -
go env Output-$ go env - -
+The PCALIGN pseudo-instruction is used to indicate that the next instruction should be aligned
+to a specified boundary by padding with no-op instructions.
+
+It is currently supported on arm64, amd64, ppc64, loong64 and riscv64.
+
+For example, the start of the MOVD instruction below is aligned to 32 bytes:
+
+PCALIGN $32 +MOVD $2, R0 ++ +
diff --git a/doc/go1.17_spec.html b/doc/go1.17_spec.html index 15e73c3867..c87d9aff3c 100644 --- a/doc/go1.17_spec.html +++ b/doc/go1.17_spec.html @@ -7,8 +7,11 @@
-This is a reference manual for the Go programming language. For -more information and other documents, see golang.org. +This is the reference manual for the Go programming language as it was for +language version 1.17, in October 2021, before the introduction of generics. +It is provided for historical interest. +The current reference manual can be found here. +For more information and other documents, see go.dev.
diff --git a/doc/go1.22.html b/doc/go1.22.html index 287ee77bb5..44d783e1bd 100644 --- a/doc/go1.22.html +++ b/doc/go1.22.html @@ -26,41 +26,390 @@ Do not send CLs removing the interior tags from such phrases.
- TODO: complete this section + +Go 1.22 makes two changes to "for" loops. +
+package main
+
+import "fmt"
+
+func main() {
+ for i := range 10 {
+ fmt.Println(10 - i)
+ }
+ fmt.Println("go1.22 has lift-off!")
+}
+
+ See the spec for details.
+
+ Go 1.22 includes a preview of a language change we are considering
+ for a future version of Go: range-over-function iterators.
+ Building with GOEXPERIMENT=rangefunc enables this feature.
- TODO: complete this section, or delete if not needed
+ Commands in workspaces can now
+ use a vendor directory containing the dependencies of the
+ workspace. The directory is created by
+ go work vendor,
+ and used by build commands when the -mod flag is set to
+ vendor, which is the default when a workspace vendor
+ directory is present.
+
+ Note that the vendor directory's contents for a workspace are different
+ from those of a single module: if the directory at the root of a workspace also
+ contains one of the modules in the workspace, its vendor directory
+ can contain the dependencies of either the workspace or of the module,
+ but not both.
+ go get is no longer supported outside of a module in the
+ legacy GOPATH mode (that is, with GO111MODULE=off).
+ Other build commands, such as go build and
+ go test, will continue to work indefinitely
+ for legacy GOPATH programs.
+
+ go mod init no longer attempts to import
+ module requirements from configuration files for other vendoring tools
+ (such as Gopkg.lock).
+
+go test -cover now prints coverage summaries for covered
+packages that do not have their own test files. Prior to Go 1.22 a
+go test -cover run for such a package would report
+
+ ? mymod/mypack [no test files]
+
+ and now with Go 1.22, functions in the package are treated as uncovered: +
+ +
+ mymod/mypack coverage: 0.0% of statements
+
+ The trace tool's web UI has been gently refreshed as part of the
+ work to support the new tracer, resolving several issues and improving the
+ readability of various sub-pages.
+ The web UI now supports exploring traces in a thread-oriented view.
+ The trace viewer also now displays the full duration of all system calls.
+
+ These improvements only apply for viewing traces produced by programs built with
+ Go 1.22 or newer.
+ A future release will bring some of these improvements to traces produced by older
+ version of Go.
+
+ The behavior of the vet tool has changed to match
+ the new semantics (see above) of loop variables in Go 1.22.
+ When analyzing a file that requires Go 1.22 or newer
+ (due to its go.mod file or a per-file build constraint),
+ vetcode> no longer reports references to
+ loop variables from within a function literal that
+ might outlive the iteration of the loop.
+ In Go 1.22, loop variables are created anew for each iteration,
+ so such references are no longer at risk of using a variable
+ after it has been updated by the loop.
+
+ The vet tool now reports calls to
+ append that pass
+ no values to be appended to the slice, such as slice = append(slice).
+ Such a statement has no effect, and experience has shown that is nearly always a mistake.
+
time.Since
+ The vet tool now reports a non-deferred call to
+ time.Since(t) within a defer statement.
+ This is equivalent to calling time.Now().Sub(t) before the defer statement,
+ not when the deferred function is called. In nearly all cases, the correct code
+ requires deferring the time.Since call. For example:
+
+t := time.Now()
+defer log.Println(time.Since(t)) // non-deferred call to time.Since
+tmp := time.Since(t); defer log.Println(tmp) // equivalent to the previous defer
+
+defer func() {
+ log.Println(time.Since(t)) // a correctly deferred call to time.Since
+}()
+
+
+log/slog calls
+ The vet tool now reports invalid arguments in calls to functions and methods
+ in the structured logging package, log/slog,
+ that accept alternating key/value pairs.
+ It reports calls where an argument in a key position is neither a
+ string nor a slog.Attr, and where a final key is missing its value.
+
+ The runtime now keeps type-based garbage collection metadata nearer to each + heap object, improving the CPU performance (latency or throughput) of Go programs + by 1–3%. + This change also reduces the memory overhead of the majority Go programs by + approximately 1% by deduplicating redundant metadata. + Some programs may see a smaller improvement because this change adjusts the size + class boundaries of the memory allocator, so some objects may be moved up a size + class. +
- TODO: complete this section, or delete if not needed
+ A consequence of this change is that some objects' addresses that were previously
+ always aligned to a 16 byte (or higher) boundary will now only be aligned to an 8
+ byte boundary.
+ Some programs that use assembly instructions that require memory addresses to be
+ more than 8-byte aligned and rely on the memory allocator's previous alignment behavior
+ may break, but we expect such programs to be rare.
+ Such programs may be built with GOEXPERIMENT=noallocheaders to revert
+ to the old metadata layout and restore the previous alignment behavior, but package
+ owners should update their assembly code to avoid the alignment assumption, as this
+ workaround will be removed in a future release.
+
+ On the windows/amd64 port, programs linking or loading Go libraries built with
+ -buildmode=c-archive or -buildmode=c-shared can now use
+ the SetUnhandledExceptionFilter Win32 function to catch exceptions not handled
+ by the Go runtime. Note that this was already supported on the windows/386 port.
- TODO: complete this section, or delete if not needed +
+ Profile-guided Optimization (PGO) builds + can now devirtualize a higher proportion of calls than previously possible. + Most programs from a representative set of Go programs now see between 2 and + 14% improvement from enabling PGO. +
+ ++ The compiler now interleaves devirtualization and inlining, so interface + method calls are better optimized. +
+ +
+ Go 1.22 also includes a preview of an enhanced implementation of the compiler's inlining phase that uses heuristics to boost inlinability at call sites deemed "important" (for example, in loops) and discourage inlining at call sites deemed "unimportant" (for example, on panic paths).
+ Building with GOEXPERIMENT=newinliner enables the new call-site
+ heuristics; see issue #61502 for
+ more info and to provide feedback.
+ The linker's -s and -w flags are now behave more
+ consistently across all platforms.
+ The -w flag suppresses DWARF debug information generation.
+ The -s flag suppresses symbol table generation.
+ The -s flag also implies the -w flag, which can be
+ negated with -w=0.
+ That is, -s -w=0 will generate a binary with DWARF
+ debug information generation but without the symbol table.
+
+ On ELF platforms, the -B linker flag now accepts a special form:
+ with -B gobuildid, the linker will generate a GNU
+ build ID (the ELF NT_GNU_BUILD_ID note) derived from the Go
+ build ID.
+
+ On Windows, when building with -linkmode=internal, the linker now
+ preserves SEH information from C object files by copying the .pdata
+ and .xdata sections into the final binary.
+ This helps with debugging and profiling binaries using native tools, such as WinDbg.
+ Note that until now, C functions' SEH exception handlers were not being honored,
+ so this change may cause some programs to behave differently.
+ -linkmode=external is not affected by this change, as external linkers
+ already preserve SEH information.
+
- TODO: complete this section, or delete if not needed + As mentioned in the Go 1.20 release notes, Go 1.22 now requires + the final point release of Go 1.20 or later for bootstrap. + We expect that Go 1.24 will require the final point release of Go 1.22 or later for bootstrap.
+ Go 1.22 includes the first “v2” package in the standard library,
+ math/rand/v2.
+ The changes compared to math/rand are
+ detailed in proposal #61716. The most important changes are:
+
Read method, deprecated in math/rand,
+was not carried forward for math/rand/v2.
+(It remains available in math/rand.)
+The vast majority of calls to Read should use
+crypto/rand’s Read instead.
+Otherwise a custom Read can be constructed using the Uint64 method.
+
+Source
+interface now has a single Uint64 method;
+there is no Source64 interface.
+
+math/rand
+because they changed the output streams.
+
+Intn,
+Int31,
+Int31n,
+Int63,
+and
+Int64n
+top-level functions and methods from math/rand
+are spelled more idiomatically in math/rand/v2:
+IntN,
+Int32,
+Int32N,
+Int64,
+and
+Int64N.
+There are also new top-level functions and methods
+Uint32,
+Uint32N,
+Uint64,
+Uint64N,
+Uint,
+and
+UintN.
+
+N
+is like
+Int64N or
+Uint64N
+but works for any integer type.
+For example a random duration from 0 up to 5 minutes is
+rand.N(5*time.Minute).
+
+math/rand’s Source
+has been replaced by two more modern pseudo-random generator sources:
+ChaCha8
+PCG.
+ChaCha8 is a new, cryptographically strong random number generator
+roughly similar to PCG in efficiency.
+ChaCha8 is the algorithm used for the top-level functions in math/rand/v2.
+As of Go 1.22, math/rand's top-level functions (when not explicitly seeded)
+and the Go runtime also use ChaCha8 for randomness.
++We plan to include an API migration tool in a future release, likely Go 1.23. +
+ +
+ HTTP routing in the standard library is now more expressive.
+ The patterns used by net/http.ServeMux have been enhanced to accept methods and wildcards.
+
+ Registering a handler with a method, like "POST /items/create", restricts
+ invocations of the handler to requests with the given method. A pattern with a method takes precedence over a matching pattern without one.
+ As a special case, registering a handler with "GET" also registers it with "HEAD".
+
+ Wildcards in patterns, like /items/{id}, match segments of the URL path.
+ The actual segment value may be accessed by calling the Request.PathValue method.
+ A wildcard ending in "...", like /files/{path...}, must occur at the end of a pattern and matches all the remaining segments.
+
+ A pattern that ends in "/" matches all paths that have it as a prefix, as always.
+ To match the exact pattern including the trailing slash, end it with {$},
+ as in /exact/match/{$}.
+
+ If two patterns overlap in the requests that they match, then the more specific pattern takes precedence. + If neither is more specific, the patterns conflict. + This rule generalizes the original precedence rules and maintains the property that the order in which + patterns are registered does not matter. +
+ +
+ This change breaks backwards compatibility in small ways, some obvious—patterns with "{" and "}" behave differently—
+ and some less so—treatment of escaped paths has been improved.
+ The change is controlled by a GODEBUG field named httpmuxgo121.
+ Set httpmuxgo121=1 to restore the old behavior.
+
@@ -70,9 +419,95 @@ Do not send CLs removing the interior tags from such phrases. There are also various performance improvements, not enumerated here.
-- TODO: complete this section -
+ + + + +
+ The new method Writer.AddFS adds all of the files from an fs.FS to the archive.
+
+ If the argument to FileInfoHeader implements the new FileInfoNames interface, then the interface methods will be used to set the UID/GID of the file header. This allows applications to override the default UID/GID resolution.
+
+ The new method Writer.AddFS adds all of the files from an fs.FS to the archive.
+
+ When a SplitFunc returns ErrFinalToken with a nil token, Scanner will now stop immediately.
+ Previously, it would report a final empty token before stopping, which was usually not desired.
+ Callers that do want to report a final empty token can do so by returning []byte{} rather than nil.
+
+ The new function Or returns the first in a sequence of values that is not the zero value.
+
+ ConnectionState.ExportKeyingMaterial will now
+ return an error unless TLS 1.3 is in use, or the extended_master_secret extension is supported by both the server and
+ client. crypto/tls has supported this extension since Go 1.20. This can be disabled with the
+ tlsunsafeekm=1 GODEBUG setting.
+
+ By default, the minimum version offered by crypto/tls servers is now TLS 1.2 if not specified with
+ config.MinimumVersion, matching the behavior of crypto/tls
+ clients. This change can be reverted with the tls10server=1 GODEBUG setting.
+
+ By default, cipher suites without ECDHE support are no longer offered by either clients or servers during pre-TLS 1.3
+ handshakes. This change can be reverted with the tlsrsakex=1 GODEBUG setting.
+
+ The new CertPool.AddCertWithConstraint
+ method can be used to add customized constraints to root certificates to be applied during chain building.
+
+ On Android, root certificates will now be loaded from /data/misc/keychain/certs-added as well as /system/etc/security/cacerts.
+
+ A new type, OID, supports ASN.1 Object Identifiers with individual
+ components larger than 31 bits. A new field which uses this type, Policies,
+ is added to the Certificate struct, and is now populated during parsing. Any OIDs which cannot be represented
+ using a asn1.ObjectIdentifier will appear in Policies,
+ but not in the old PolicyIdentifiers field.
+
+ When calling CreateCertificate, the Policies field is ignored, and
+ policies are taken from the PolicyIdentifiers field. Using the x509usepolicies=1 GODEBUG setting inverts this,
+ populating certificate policies from the Policies field, and ignoring the PolicyIdentifiers field. We may change the
+ default value of x509usepolicies in Go 1.23, making Policies the default field for marshaling.
+
+ Constant R_MIPS_PC32 is defined for use with MIPS64 systems.
+
+ Additional R_LARCH_* constants are defined for use with LoongArch systems.
+
+ The new methods AppendEncode and AppendDecode added to
+ each of the Encoding types in the packages
+ encoding/base32,
+ encoding/base64, and
+ encoding/hex
+ simplify encoding and decoding from and to byte slices by taking care of byte slice buffer management.
+
+ The methods
+ base32.Encoding.WithPadding and
+ base64.Encoding.WithPadding
+ now panic if the padding argument is a negative value other than
+ NoPadding.
+
+ Marshaling and encoding functionality now escapes
+ '\b' and '\f' characters as
+ \b and \f instead of
+ \u0008 and \u000c.
+
+ The following declarations related to
+ syntactic identifier resolution
+ are now deprecated:
+ Ident.Obj,
+ Object,
+ Scope,
+ File.Scope,
+ File.Unresolved,
+ Importer,
+ Package,
+ NewPackage.
+
+ In general, identifiers cannot be accurately resolved without type information.
+ Consider, for example, the identifier K
+ in T{K: ""}: it could be the name of a local variable
+ if T is a map type, or the name of a field if T is a struct type.
+
+ New programs should use the go/types
+ package to resolve identifiers; see
+ Object,
+ Info.Uses, and
+ Info.Defs for details.
+
+ The new ast.Unparen
+ function removes any enclosing
+ parentheses from
+ an expression.
+
+ The new Alias type represents type aliases.
+ Previously, type aliases were not represented explicitly, so a reference to a type alias was equivalent
+ to spelling out the aliased type, and the name of the alias was lost.
+ The new representation retains the intermediate Alias.
+ This enables improved error reporting (the name of a type alias can be reported), and allows for better handling
+ of cyclic type declarations involving type aliases.
+ In a future release, Alias types will also carry type parameter information.
+ The new function Unalias returns the actual type denoted by an
+ Alias type (or any other Type for that matter).
+
+ Because Alias types may break existing type switches that do not know to check for them,
+ this functionality is controlled by a GODEBUG field named gotypesalias.
+ With gotypesalias=0, everything behaves as before, and Alias types are never created.
+ With gotypesalias=1, Alias types are created and clients must expect them.
+ The default is gotypesalias=0.
+ In a future release, the default will be changed to gotypesalias=1.
+ Clients of go/types are urged to adjust their code as soon as possible
+ to work with gotypesalias=1 to eliminate problems early.
+
+ The Info struct now exports the
+ FileVersions map
+ which provides per-file Go version information.
+
+ The new helper method PkgNameOf returns the local package name
+ for the given import declaration.
+
+ The implementation of SizesFor has been adjusted to compute
+ the same type sizes as the compiler when the compiler argument for SizesFor is "gc".
+ The default Sizes implementation used by the type checker is now
+ types.SizesFor("gc", "amd64").
+
+ The start position (Pos)
+ of the lexical environment block (Scope)
+ that represents a function body has changed:
+ it used to start at the opening curly brace of the function body,
+ but now starts at the function's func token.
+
+ The new go/version package implements functions
+ for validating and comparing Go version strings.
+
+ Javascript template literals may now contain Go template actions, and parsing a template containing one will
+ no longer return ErrJSTemplate. Similarly the GODEBUG setting jstmpllitinterp no
+ longer has any effect.
+
+ The new SectionReader.Outer method returns the ReaderAt, offset, and size passed to NewSectionReader.
+
+ The new SetLogLoggerLevel function
+ controls the level for the bridge between the `slog` and `log` packages. It sets the minimum level
+ for calls to the top-level `slog` logging functions, and it sets the level for calls to `log.Logger`
+ that go through `slog`.
+
+ The new method Rat.FloatPrec computes the number of fractional decimal digits + required to represent a rational number accurately as a floating-point number, and whether accurate decimal representation + is possible in the first place. +
+
+ When io.Copy copies
+ from a TCPConn to a UnixConn,
+ it will now use Linux's splice(2) system call if possible,
+ using the new method TCPConn.WriteTo.
+
+ The Go DNS Resolver, used when building with "-tags=netgo",
+ now searches for a matching name in the Windows hosts file,
+ located at %SystemRoot%\System32\drivers\etc\hosts,
+ before making a DNS query.
+
+ The new functions
+ ServeFileFS,
+ FileServerFS, and
+ NewFileTransportFS
+ are versions of the existing
+ ServeFile, FileServer, and NewFileTransport,
+ operating on an fs.FS.
+
+ The HTTP server and client now reject requests and responses containing
+ an invalid empty Content-Length header.
+ The previous behavior may be restored by setting
+ GODEBUG field httplaxcontentlength=1.
+
+ On Windows, the Stat function now follows all reparse points
+ that link to another named entity in the system.
+ It was previously only following IO_REPARSE_TAG_SYMLINK and
+ IO_REPARSE_TAG_MOUNT_POINT reparse points.
+
+ On Windows, passing O_SYNC to OpenFile now causes write operations to go directly to disk, equivalent to O_SYNC on Unix platforms.
+
+ On Windows, the ReadDir,
+ File.ReadDir,
+ File.Readdir,
+ and File.Readdirnames functions
+ now read directory entries in batches to reduce the number of system calls,
+ improving performance up to 30%.
+
+ When io.Copy copies
+ from a File to a net.UnixConn,
+ it will now use Linux's sendfile(2) system call if possible,
+ using the new method File.WriteTo.
+
+ On Windows, LookPath now
+ ignores empty entries in %PATH%, and returns
+ ErrNotFound (instead of ErrNotExist) if
+ no executable file extension is found to resolve an otherwise-unambiguous
+ name.
+
+ On Windows, Command and
+ Cmd.Start no
+ longer call LookPath if the path to the executable is already
+ absolute and has an executable file extension. In addition,
+ Cmd.Start no longer writes the resolved extension back to
+ the Path field,
+ so it is now safe to call the String method concurrently
+ with a call to Start.
+
@@ -94,12 +827,189 @@ Do not send CLs removing the interior tags from such phrases.
These changes make IsZero consistent with comparing
a value to zero using the language == operator.
+ The PtrTo function is deprecated,
+ in favor of PointerTo.
+
+ The new function TypeFor
+ returns the Type that represents
+ the type argument T.
+ Previously, to get the reflect.Type value for a type, one had to use
+ reflect.TypeOf((*T)(nil)).Elem().
+ This may now be written as reflect.TypeFor[T]().
+
+ Four new histogram metrics
+ /sched/pauses/stopping/gc:seconds,
+ /sched/pauses/stopping/other:seconds,
+ /sched/pauses/total/gc:seconds, and
+ /sched/pauses/total/other:seconds provide additional details
+ about stop-the-world pauses.
+ The "stopping" metrics report the time taken from deciding to stop the
+ world until all goroutines are stopped.
+ The "total" metrics report the time taken from deciding to stop the world
+ until it is started again.
+
+ The /gc/pauses:seconds metric is deprecated, as it is
+ equivalent to the new /sched/pauses/total/gc:seconds metric.
+
+ /sync/mutex/wait/total:seconds now includes contention on
+ runtime-internal locks in addition to
+ sync.Mutex and
+ sync.RWMutex.
+
+ Mutex profiles now scale contention by the number of goroutines blocked on the mutex. + This provides a more accurate representation of the degree to which a mutex is a bottleneck in + a Go program. + For instance, if 100 goroutines are blocked on a mutex for 10 milliseconds, a mutex profile will + now record 1 second of delay instead of 10 milliseconds of delay. +
+ +
+ Mutex profiles also now include contention on runtime-internal locks in addition to
+ sync.Mutex and
+ sync.RWMutex.
+ Contention on runtime-internal locks is always reported at runtime._LostContendedRuntimeLock.
+ A future release will add complete stack traces in these cases.
+
+ CPU profiles on Darwin platforms now contain the process's memory map, enabling the disassembly + view in the pprof tool. +
++ The execution tracer has been completely overhauled in this release, resolving several long-standing + issues and paving the way for new use-cases for execution traces. +
++ Execution traces now use the operating system's clock on most platforms (Windows excluded) so + it is possible to correlate them with traces produced by lower-level components. + Execution traces no longer depend on the reliability of the platform's clock to produce a correct trace. + Execution traces are now partitioned regularly on-the-fly and as a result may be processed in a + streamable way. + Execution traces now contain complete durations for all system calls. + Execution traces now contain information about the operating system threads that goroutines executed on. + The latency impact of starting and stopping execution traces has been dramatically reduced. + Execution traces may now begin or end during the garbage collection mark phase. +
++ To allow Go developers to take advantage of these improvements, an experimental + trace reading package is available at golang.org/x/exp/trace. + Note that this package only works on traces produced by programs built with Go 1.22 at the moment. + Please try out the package and provide feedback on + the corresponding proposal issue. +
+
+ If you experience any issues with the new execution tracer implementation, you may switch back to the
+ old implementation by building your Go program with GOEXPERIMENT=noexectracer2.
+ If you do, please file an issue, otherwise this option will be removed in a future release.
+
+ The new function Concat concatenates multiple slices.
+
+ Functions that shrink the size of a slice (Delete, DeleteFunc, Compact, CompactFunc, and Replace) now zero the elements between the new length and the old length.
+
+ Insert now always panics if the argument i is out of range. Previously it did not panic in this situation if there were no elements to be inserted.
+
+ The syscall package has been frozen since Go 1.4 and was marked as deprecated in Go 1.11, causing many editors to warn about any use of the package.
+ However, some non-deprecated functionality requires use of the syscall package, such as the os/exec.Cmd.SysProcAttr field.
+ To avoid unnecessary complaints on such code, the syscall package is no longer marked as deprecated.
+ The package remains frozen to most new functionality, and new code remains encouraged to use golang.org/x/sys/unix or golang.org/x/sys/windows where possible.
+
+ On Linux, the new SysProcAttr.PidFD field allows obtaining a PID FD when starting a child process via StartProcess or os/exec.
+
+ On Windows, passing O_SYNC to Open now causes write operations to go directly to disk, equivalent to O_SYNC on Unix platforms.
+
+ The new Run function uses sub-tests to run test cases,
+ providing finer-grained control.
+
- TODO: complete this section, or delete if not needed +
+ On macOS on 64-bit x86 architecture (the darwin/amd64 port),
+ the Go toolchain now generates position-independent executables (PIE) by default.
+ Non-PIE binaries can be generated by specifying the -buildmode=exe
+ build flag.
+ On 64-bit ARM-based macOS (the darwin/arm64 port),
+ the Go toolchain already generates PIE by default.
+
+ Go 1.22 is the last release that will run on macOS 10.15 Catalina. Go 1.23 will require macOS 11 Big Sur or later.
+
+ The GOARM environment variable now allows you to select whether to use software or hardware floating point.
+ Previously, valid GOARM values were 5, 6, or 7. Now those same values can
+ be optionally followed by ,softfloat or ,hardfloat to select the floating-point implementation.
+
+ This new option defaults to softfloat for version 5 and hardfloat for versions
+ 6 and 7.
+
+ The loong64 port now supports passing function arguments and results using registers.
+
+ The linux/loong64 port now supports the address sanitizer, memory sanitizer, new-style linker relocations, and the plugin build mode.
+
+ Go 1.22 adds an experimental port to OpenBSD on big-endian 64-bit PowerPC
+ (openbsd/ppc64).
+
@@ -70,6 +70,14 @@ enumerations or code snippets that are not further specified. The character
+A link of the form [Go 1.xx] indicates that a described
+language feature (or some aspect of it) was changed or added with language version 1.xx and
+thus requires at minimum that language version to build.
+For details, see the linked section
+in the appendix.
+
@@ -263,7 +271,8 @@ continue for import return var
The following character sequences represent operators
-(including assignment operators) and punctuation:
+(including assignment operators) and punctuation
+[Go 1.18]:
Source code representation
+ & += &= && == != ( )
@@ -281,7 +290,8 @@ An integer literal is a sequence of digits representing an
integer constant.
An optional prefix sets a non-decimal base:
0b or 0B
for binary, 0, 0o, or 0O for octal,
-and 0x or 0X for hexadecimal.
+and 0x or 0X for hexadecimal
+[Go 1.13].
A single 0 is considered a decimal zero.
In hexadecimal literals, letters a through f
and A through F represent values 10 through 15.
@@ -347,7 +357,8 @@ prefix, an integer part (hexadecimal digits), a radix point, a fractional part (
and an exponent part (p or P followed by an optional sign and decimal digits).
One of the integer part or the fractional part may be elided; the radix point may be elided as well,
but the exponent part is required. (This syntax matches the one given in IEEE 754-2008 §5.12.3.)
-An exponent value exp scales the mantissa (integer and fractional part) by 2exp.
+An exponent value exp scales the mantissa (integer and fractional part) by 2exp
+[Go 1.13].
@@ -411,7 +422,8 @@ It consists of an integer or
floating-point literal
followed by the lowercase letter i.
The value of an imaginary literal is the value of the respective
-integer or floating-point literal multiplied by the imaginary unit i.
+integer or floating-point literal multiplied by the imaginary unit i
+[Go 1.13]
@@ -1340,6 +1352,7 @@ interface{}
For convenience, the predeclared type any is an alias for the empty interface.
+[Go 1.18]
@@ -1375,13 +1388,15 @@ as the File interface.
In a slightly more general form
an interface T may use a (possibly qualified) interface type
name E as an interface element. This is called
-embedding interface E in T.
+embedding interface E in T
+[Go 1.14].
The type set of T is the intersection of the type sets
defined by T's explicitly declared methods and the type sets
of T’s embedded interfaces.
In other words, the type set of T is the set of all types that implement all the
explicitly declared methods of T and also all the methods of
-E.
+E
+[Go 1.18].
@@ -1420,7 +1435,8 @@ type ReadCloser interface {
In their most general form, an interface element may also be an arbitrary type term
T, or a term of the form ~T specifying the underlying type T,
-or a union of terms t1|t2|…|tn.
+or a union of terms t1|t2|…|tn
+[Go 1.18].
Together with method specifications, these elements enable the precise
definition of an interface's type set as follows:
@@ -2303,7 +2319,9 @@ as an operand, and in a
The following identifiers are implicitly declared in the
-universe block:
+universe block
+[Go 1.18]
+[Go 1.21]:
Types:
@@ -2487,7 +2505,8 @@ TypeSpec = AliasDecl | TypeDef .
Alias declarations
-An alias declaration binds an identifier to the given type.
+An alias declaration binds an identifier to the given type
+[Go 1.9].
@@ -2636,7 +2655,8 @@ func (l *List[T]) Len() int { … }
A type parameter list declares the type parameters of a generic function or type declaration.
The type parameter list looks like an ordinary function parameter list
except that the type parameter names must all be present and the list is enclosed
-in square brackets rather than parentheses.
+in square brackets rather than parentheses
+[Go 1.18].
@@ -2719,7 +2739,8 @@ type T6[P int] struct{ f *T6[P] } // ok: reference to T6 is not in type para
A type constraint is an interface that defines the
set of permissible type arguments for the respective type parameter and controls the
-operations supported by values of that type parameter.
+operations supported by values of that type parameter
+[Go 1.18].
@@ -2749,7 +2770,8 @@ other interfaces based on their type sets. But this should get us going for now.
The predeclared
interface type comparable
denotes the set of all non-interface types that are
-strictly comparable.
+strictly comparable
+[Go 1.18].
@@ -2782,7 +2804,8 @@ if T is an element of the type set defined by C; i.e.,
if T implements C.
As an exception, a strictly comparable
type constraint may also be satisfied by a comparable
-(not necessarily strictly comparable) type argument.
+(not necessarily strictly comparable) type argument
+[Go 1.20].
More precisely:
@@ -4306,7 +4329,7 @@ with the same underlying array.
A generic function or type is instantiated by substituting type arguments
-for the type parameters.
+for the type parameters [Go 1.18].
Instantiation proceeds in two steps:
@@ -4759,6 +4782,7 @@ to the type of the other operand.
The right operand in a shift expression must have integer type
+[Go 1.13]
or be an untyped constant representable by a
value of type uint.
If the left operand of a non-constant shift expression is an untyped constant,
@@ -5426,7 +5450,8 @@ in any of these cases:
x is a string and T is a slice of bytes or runes.
- x is a slice, T is an array or a pointer to an array,
+ x is a slice, T is an array [Go 1.20]
+ or a pointer to an array [Go 1.17],
and the slice and array types have identical element types.
@@ -6516,7 +6541,6 @@ additionally it may specify an init
and a post statement, such as an assignment,
an increment or decrement statement. The init statement may be a
short variable declaration, but the post statement must not.
-Variables declared by the init statement are re-used in each iteration.
@@ -6548,12 +6572,54 @@ for cond { S() } is the same as for ; cond ; { S() }
for { S() } is the same as for true { S() }
+
+Each iteration has its own separate declared variable (or variables)
+[Go 1.22].
+The variable used by the first iteration is declared by the init statement.
+The variable used by each subsequent iteration is declared implicitly before
+executing the post statement and initialized to the value of the previous
+iteration's variable at that moment.
+
+
+
+var prints []func()
+for i := 0; i < 5; i++ {
+ prints = append(prints, func() { println(i) })
+ i++
+}
+for _, p := range prints {
+ p()
+}
+
+
+
+prints
+
+
+
+1
+3
+5
+
+
+
+Prior to [Go 1.22], iterations share one set of variables
+instead of having their own separate variables.
+In that case, the example above prints
+
+
+
+6
+6
+6
+
+
For statements with range clause
A "for" statement with a "range" clause
iterates through all entries of an array, slice, string or map, values received on
-a channel, or integer values from zero to an upper limit.
+a channel, or integer values from zero to an upper limit [Go 1.22].
For each entry it assigns iteration values
to corresponding iteration variables if present and then executes the block.
@@ -6652,9 +6718,10 @@ The iteration variables may be declared by the "range" clause using a form of
short variable declaration
(:=).
In this case their types are set to the types of the respective iteration values
-and their scope is the block of the "for"
-statement; they are re-used in each iteration.
-If the iteration variables are declared outside the "for" statement,
+and their scope is the block of the "for" statement;
+each iteration has its own separate variables [Go 1.22]
+(see also "for" statements with a ForClause).
+If the iteration variables are declared outside the “for” statement,
after execution their values will be those of the last iteration.
@@ -7249,7 +7316,8 @@ n3 := copy(b, "Hello, World!") // n3 == 5, b is []byte("Hello")
The built-in function clear takes an argument of map,
slice, or type parameter type,
-and deletes or zeroes out all elements.
+and deletes or zeroes out all elements
+[Go 1.21].
@@ -7516,7 +7584,8 @@ The precise behavior is implementation-dependent.
The built-in functions min and max compute the
smallest—or largest, respectively—value of a fixed number of
arguments of ordered types.
-There must be at least one argument.
+There must be at least one argument
+[Go 1.21].
@@ -8232,8 +8301,8 @@ of if the general conversion rules take care of this.
A Pointer is a pointer type but a Pointer
value may not be dereferenced.
-Any pointer or value of underlying type uintptr can be
-converted to a type of underlying type Pointer and vice versa.
+Any pointer or value of core type uintptr can be
+converted to a type of core type Pointer and vice versa.
The effect of converting between Pointer and uintptr is implementation-defined.
@@ -8244,6 +8313,10 @@ bits = *(*uint64)(unsafe.Pointer(&f))
type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))
+func f[P ~*B, B any](p P) uintptr {
+ return uintptr(unsafe.Pointer(p))
+}
+
var p ptr = nil
@@ -8292,7 +8365,8 @@ of constant size.
The function Add adds len to ptr
-and returns the updated pointer unsafe.Pointer(uintptr(ptr) + uintptr(len)).
+and returns the updated pointer unsafe.Pointer(uintptr(ptr) + uintptr(len))
+[Go 1.17].
The len argument must be of integer type or an untyped constant.
A constant len argument must be representable by a value of type int;
if it is an untyped constant it is given type int.
@@ -8312,7 +8386,8 @@ and whose length and capacity are len.
except that, as a special case, if ptr
is nil and len is zero,
-Slice returns nil.
+Slice returns nil
+[Go 1.17].
@@ -8321,14 +8396,16 @@ A constant len argument must be non-negative and run-time panic occurs.
+a run-time panic occurs
+[Go 1.17].
The function SliceData returns a pointer to the underlying array of the slice argument.
If the slice's capacity cap(slice) is not zero, that pointer is &slice[:1][0].
If slice is nil, the result is nil.
-Otherwise it is a non-nil pointer to an unspecified memory address.
+Otherwise it is a non-nil pointer to an unspecified memory address
+[Go 1.20].
@@ -8337,12 +8414,14 @@ The function String returns a string value whose under
The same requirements apply to the ptr and len argument as in the function
Slice. If len is zero, the result is the empty string "".
Since Go strings are immutable, the bytes passed to String must not be modified afterwards.
+[Go 1.20]
The function StringData returns a pointer to the underlying bytes of the str argument.
For an empty string the return value is unspecified, and may be nil.
-Since Go strings are immutable, the bytes returned by StringData must not be modified.
+Since Go strings are immutable, the bytes returned by StringData must not be modified
+[Go 1.20].
Size and alignment guarantees
@@ -8383,6 +8462,145 @@ A struct or array type has size zero if it contains no fields (or elements, resp
Appendix
+Language versions
+
+
+The Go 1 compatibility guarantee ensures that
+programs written to the Go 1 specification will continue to compile and run
+correctly, unchanged, over the lifetime of that specification.
+More generally, as adjustments are made and features added to the language,
+the compatibility guarantee ensures that a Go program that works with a
+specific Go language version will continue to work with any subsequent version.
+
+
+
+For instance, the ability to use the prefix 0b for binary
+integer literals was introduced with Go 1.13, indicated
+by [Go 1.13] in the section on
+integer literals.
+Source code containing an integer literal such as 0b1011
+will be rejected if the implied or required language version used by
+the compiler is older than Go 1.13.
+
+
+
+The following table describes the minimum language version required for
+features introduced after Go 1.
+
+
+Go 1.9
+0b, 0B, 0o,
+and 0O for binary, and octal literals, respectively.
+0x and 0X.
+i may be used with any (binary, decimal, hexadecimal)
+integer or floating-point literal, not just decimal literals.
+_.
+unsafe includes the new functions
+Add and Slice.
++The 1.18 release adds polymorphic functions and types ("generics") to the language. +Specifically: +
+~.
+~T type elements.
+any and comparable.
+unsafe includes the new functions
+SliceData, String, and StringData.
+comparable constraints, even if the type arguments are not strictly comparable.
+min, max, and clear.
+
diff --git a/doc/godebug.md b/doc/godebug.md
index 9235635bdd..a7619c9a3d 100644
--- a/doc/godebug.md
+++ b/doc/godebug.md
@@ -159,6 +159,41 @@ Go 1.22 changed the default TLS cipher suites used by clients and servers when
not explicitly configured, removing the cipher suites which used RSA based key
exchange. The default can be revert using the [`tlsrsakex` setting](/pkg/crypto/tls/#Config).
+Go 1.22 disabled
+[`ConnectionState.ExportKeyingMaterial`](/pkg/crypto/tls/#ConnectionState.ExportKeyingMaterial)
+when the connection supports neither TLS 1.3 nor Extended Master Secret
+(implemented in Go 1.21). It can be reenabled with the [`tlsunsafeekm`
+setting](/pkg/crypto/tls/#ConnectionState.ExportKeyingMaterial).
+
+Go 1.22 changed how the runtime interacts with transparent huge pages on Linux.
+In particular, a common default Linux kernel configuration can result in
+significant memory overheads, and Go 1.22 no longer works around this default.
+To work around this issue without adjusting kernel settings, transparent huge
+pages can be disabled for Go memory with the
+[`disablethp` setting](/pkg/runtime#hdr-Environment_Variable).
+This behavior was backported to Go 1.21.1, but the setting is only available
+starting with Go 1.21.6.
+This setting may be removed in a future release, and users impacted by this issue
+should adjust their Linux configuration according to the recommendations in the
+[GC guide](/doc/gc-guide#Linux_transparent_huge_pages), or switch to a Linux
+distribution that disables transparent huge pages altogether.
+
+Go 1.22 added contention on runtime-internal locks to the [`mutex`
+profile](/pkg/runtime/pprof#Profile). Contention on these locks is always
+reported at `runtime._LostContendedRuntimeLock`. Complete stack traces of
+runtime locks can be enabled with the [`runtimecontentionstacks`
+setting](/pkg/runtime#hdr-Environment_Variable). These stack traces have
+non-standard semantics, see setting documentation for details.
+
+Go 1.22 added a new [`crypto/x509.Certificate`](/pkg/crypto/x509/#Certificate)
+field, [`Policies`](/pkg/crypto/x509/#Certificate.Policies), which supports
+certificate policy OIDs with components larger than 31 bits. By default this
+field is only used during parsing, when it is populated with policy OIDs, but
+not used during marshaling. It can be used to marshal these larger OIDs, instead
+of the existing PolicyIdentifiers field, by using the
+[`x509usepolicies` setting.](/pkg/crypto/x509/#CreateCertificate).
+
+
### Go 1.21
Go 1.21 made it a run-time error to call `panic` with a nil interface value,
diff --git a/lib/time/update.bash b/lib/time/update.bash
index 605afa76d3..e72850079f 100755
--- a/lib/time/update.bash
+++ b/lib/time/update.bash
@@ -24,8 +24,8 @@
# in the CL match the update.bash in the CL.
# Versions to use.
-CODE=2023c
-DATA=2023c
+CODE=2023d
+DATA=2023d
set -e
diff --git a/lib/time/zoneinfo.zip b/lib/time/zoneinfo.zip
index 417ee2b194..7cf689f830 100644
Binary files a/lib/time/zoneinfo.zip and b/lib/time/zoneinfo.zip differ
diff --git a/misc/wasm/go_wasip1_wasm_exec b/misc/wasm/go_wasip1_wasm_exec
index dc110327af..cd16b96ea7 100755
--- a/misc/wasm/go_wasip1_wasm_exec
+++ b/misc/wasm/go_wasip1_wasm_exec
@@ -14,8 +14,15 @@ case "$GOWASIRUNTIME" in
exec wazero run -mount /:/ -env-inherit -cachedir "${TMPDIR:-/tmp}"/wazero ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
;;
"wasmtime" | "")
- # TODO(go.dev/issue/63718): Switch to the new CLI offered in the major version 14 of Wasmtime.
- exec env WASMTIME_NEW_CLI=0 wasmtime run --dir=/ --env PWD="$PWD" --env PATH="$PATH" --max-wasm-stack 1048576 ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
+ # Match the major version in "wasmtime-cli 14.0.0". For versions before 14
+ # we need to use the old CLI. This requires Bash v3.0 and above.
+ # TODO(johanbrandhorst): Remove this condition once 1.22 is released.
+ # From 1.23 onwards we'll only support the new wasmtime CLI.
+ if [[ "$(wasmtime --version)" =~ wasmtime-cli[[:space:]]([0-9]+)\.[0-9]+\.[0-9]+ && "${BASH_REMATCH[1]}" -lt 14 ]]; then
+ exec wasmtime run --dir=/ --env PWD="$PWD" --env PATH="$PATH" --max-wasm-stack 1048576 ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
+ else
+ exec wasmtime run --dir=/ --env PWD="$PWD" --env PATH="$PATH" -W max-wasm-stack=1048576 ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
+ fi
;;
*)
echo "Unknown Go WASI runtime specified: $GOWASIRUNTIME"
diff --git a/src/builtin/builtin.go b/src/builtin/builtin.go
index da0ace1498..668c799ca7 100644
--- a/src/builtin/builtin.go
+++ b/src/builtin/builtin.go
@@ -284,9 +284,10 @@ func panic(v any)
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
-// panicking, or if the argument supplied to panic was nil, recover returns
-// nil. Thus the return value from recover reports whether the goroutine is
-// panicking.
+// panicking, recover returns nil.
+//
+// Prior to Go 1.21, recover would also return nil if panic is called with
+// a nil argument. See [panic] for details.
func recover() any
// The print built-in function formats its arguments in an
diff --git a/src/bytes/boundary_test.go b/src/bytes/boundary_test.go
index f9855fcb05..67f377e089 100644
--- a/src/bytes/boundary_test.go
+++ b/src/bytes/boundary_test.go
@@ -98,3 +98,18 @@ func TestIndexNearPageBoundary(t *testing.T) {
}
q[len(q)-1] = 0
}
+
+func TestCountNearPageBoundary(t *testing.T) {
+ t.Parallel()
+ b := dangerousSlice(t)
+ for i := range b {
+ c := Count(b[i:], []byte{1})
+ if c != 0 {
+ t.Fatalf("Count(b[%d:], {1})=%d, want 0\n", i, c)
+ }
+ c = Count(b[:i], []byte{0})
+ if c != i {
+ t.Fatalf("Count(b[:%d], {0})=%d, want %d\n", i, c, i)
+ }
+ }
+}
diff --git a/src/cmd/api/api_test.go b/src/cmd/api/api_test.go
index 910e046f12..ba358d364d 100644
--- a/src/cmd/api/api_test.go
+++ b/src/cmd/api/api_test.go
@@ -285,6 +285,25 @@ func TestIssue41358(t *testing.T) {
}
}
+func TestIssue64958(t *testing.T) {
+ defer func() {
+ if x := recover(); x != nil {
+ t.Errorf("expected no panic; recovered %v", x)
+ }
+ }()
+
+ testenv.MustHaveGoBuild(t)
+
+ for _, context := range contexts {
+ w := NewWalker(context, "testdata/src/issue64958")
+ pkg, err := w.importFrom("p", "", 0)
+ if err != nil {
+ t.Errorf("expected no error importing; got %T", err)
+ }
+ w.export(pkg)
+ }
+}
+
func TestCheck(t *testing.T) {
if !*flagCheck {
t.Skip("-check not specified")
diff --git a/src/cmd/api/main_test.go b/src/cmd/api/main_test.go
index 94e159e7d8..7985055b5c 100644
--- a/src/cmd/api/main_test.go
+++ b/src/cmd/api/main_test.go
@@ -957,17 +957,17 @@ func (w *Walker) emitType(obj *types.TypeName) {
if w.isDeprecated(obj) {
w.emitf("type %s //deprecated", name)
}
+ typ := obj.Type()
+ if obj.IsAlias() {
+ w.emitf("type %s = %s", name, w.typeString(typ))
+ return
+ }
if tparams := obj.Type().(*types.Named).TypeParams(); tparams != nil {
var buf bytes.Buffer
buf.WriteString(name)
w.writeTypeParams(&buf, tparams, true)
name = buf.String()
}
- typ := obj.Type()
- if obj.IsAlias() {
- w.emitf("type %s = %s", name, w.typeString(typ))
- return
- }
switch typ := typ.Underlying().(type) {
case *types.Struct:
w.emitStructType(name, typ)
diff --git a/src/cmd/api/testdata/src/issue64958/p/p.go b/src/cmd/api/testdata/src/issue64958/p/p.go
new file mode 100644
index 0000000000..feba86797f
--- /dev/null
+++ b/src/cmd/api/testdata/src/issue64958/p/p.go
@@ -0,0 +1,3 @@
+package p
+
+type BasicAlias = uint8
diff --git a/src/cmd/asm/internal/asm/endtoend_test.go b/src/cmd/asm/internal/asm/endtoend_test.go
index a2de63685c..6e1aa1cd95 100644
--- a/src/cmd/asm/internal/asm/endtoend_test.go
+++ b/src/cmd/asm/internal/asm/endtoend_test.go
@@ -141,11 +141,17 @@ Diff:
// Turn relative (PC) into absolute (PC) automatically,
// so that most branch instructions don't need comments
// giving the absolute form.
- if len(f) > 0 && strings.HasSuffix(printed, "(PC)") {
- last := f[len(f)-1]
- n, err := strconv.Atoi(last[:len(last)-len("(PC)")])
+ if len(f) > 0 && strings.Contains(printed, "(PC)") {
+ index := len(f) - 1
+ suf := "(PC)"
+ for !strings.HasSuffix(f[index], suf) {
+ index--
+ suf = "(PC),"
+ }
+ str := f[index]
+ n, err := strconv.Atoi(str[:len(str)-len(suf)])
if err == nil {
- f[len(f)-1] = fmt.Sprintf("%d(PC)", seq+n)
+ f[index] = fmt.Sprintf("%d%s", seq+n, suf)
}
}
@@ -372,10 +378,10 @@ func Test386EndToEnd(t *testing.T) {
}
func TestARMEndToEnd(t *testing.T) {
- defer func(old int) { buildcfg.GOARM = old }(buildcfg.GOARM)
+ defer func(old int) { buildcfg.GOARM.Version = old }(buildcfg.GOARM.Version)
for _, goarm := range []int{5, 6, 7} {
t.Logf("GOARM=%d", goarm)
- buildcfg.GOARM = goarm
+ buildcfg.GOARM.Version = goarm
testEndToEnd(t, "arm", "arm")
if goarm == 6 {
testEndToEnd(t, "arm", "armv6")
diff --git a/src/cmd/asm/internal/asm/testdata/arm.s b/src/cmd/asm/internal/asm/testdata/arm.s
index 2ba22c71de..93edc8854e 100644
--- a/src/cmd/asm/internal/asm/testdata/arm.s
+++ b/src/cmd/asm/internal/asm/testdata/arm.s
@@ -870,10 +870,13 @@ jmp_label_3:
BIC.S R0@>R1, R2 // 7021d2e1
// SRL
+ SRL $0, R5, R6 // 0560a0e1
+ SRL $1, R5, R6 // a560a0e1
SRL $14, R5, R6 // 2567a0e1
SRL $15, R5, R6 // a567a0e1
SRL $30, R5, R6 // 256fa0e1
SRL $31, R5, R6 // a56fa0e1
+ SRL $32, R5, R6 // 2560a0e1
SRL.S $14, R5, R6 // 2567b0e1
SRL.S $15, R5, R6 // a567b0e1
SRL.S $30, R5, R6 // 256fb0e1
@@ -892,10 +895,13 @@ jmp_label_3:
SRL.S R5, R7 // 3775b0e1
// SRA
+ SRA $0, R5, R6 // 0560a0e1
+ SRA $1, R5, R6 // c560a0e1
SRA $14, R5, R6 // 4567a0e1
SRA $15, R5, R6 // c567a0e1
SRA $30, R5, R6 // 456fa0e1
SRA $31, R5, R6 // c56fa0e1
+ SRA $32, R5, R6 // 4560a0e1
SRA.S $14, R5, R6 // 4567b0e1
SRA.S $15, R5, R6 // c567b0e1
SRA.S $30, R5, R6 // 456fb0e1
@@ -914,6 +920,8 @@ jmp_label_3:
SRA.S R5, R7 // 5775b0e1
// SLL
+ SLL $0, R5, R6 // 0560a0e1
+ SLL $1, R5, R6 // 8560a0e1
SLL $14, R5, R6 // 0567a0e1
SLL $15, R5, R6 // 8567a0e1
SLL $30, R5, R6 // 056fa0e1
@@ -935,6 +943,20 @@ jmp_label_3:
SLL R5, R7 // 1775a0e1
SLL.S R5, R7 // 1775b0e1
+// Ops with zero shifts should encode as left shifts
+ ADD R0<<0, R1, R2 // 002081e0
+ ADD R0>>0, R1, R2 // 002081e0
+ ADD R0->0, R1, R2 // 002081e0
+ ADD R0@>0, R1, R2 // 002081e0
+ MOVW R0<<0(R1), R2 // 002091e7
+ MOVW R0>>0(R1), R2 // 002091e7
+ MOVW R0->0(R1), R2 // 002091e7
+ MOVW R0@>0(R1), R2 // 002091e7
+ MOVW R0, R1<<0(R2) // 010082e7
+ MOVW R0, R1>>0(R2) // 010082e7
+ MOVW R0, R1->0(R2) // 010082e7
+ MOVW R0, R1@>0(R2) // 010082e7
+
// MULA / MULS
MULAWT R1, R2, R3, R4 // c23124e1
MULAWB R1, R2, R3, R4 // 823124e1
diff --git a/src/cmd/asm/internal/asm/testdata/arm64.s b/src/cmd/asm/internal/asm/testdata/arm64.s
index 46ea6645af..ecad08b37a 100644
--- a/src/cmd/asm/internal/asm/testdata/arm64.s
+++ b/src/cmd/asm/internal/asm/testdata/arm64.s
@@ -981,6 +981,14 @@ again:
ADR next, R11 // ADR R11 // 2b000010
next:
NOP
+ ADR -2(PC), R10 // 0a000010
+ ADR 2(PC), R16 // 10000010
+ ADR -26(PC), R1 // 01000010
+ ADR 12(PC), R2 // 02000010
+ ADRP -2(PC), R10 // 0a000090
+ ADRP 2(PC), R16 // 10000090
+ ADRP -26(PC), R1 // 01000090
+ ADRP 12(PC), R2 // 02000090
// LDP/STP
LDP (R0), (R0, R1) // 000440a9
@@ -1003,6 +1011,7 @@ next:
LDP -8(R0), (R1, R2) // 01887fa9
LDP x(SB), (R1, R2)
LDP x+8(SB), (R1, R2)
+ LDP 8(R1), (ZR, R2) // 3f8840a9
LDPW -5(R0), (R1, R2) // 1b1400d1610b4029
LDPW (R0), (R1, R2) // 01084029
LDPW 4(R0), (R1, R2) // 01884029
@@ -1020,6 +1029,7 @@ next:
LDPW 1024(RSP), (R1, R2) // fb031091610b4029
LDPW x(SB), (R1, R2)
LDPW x+8(SB), (R1, R2)
+ LDPW 8(R1), (ZR, R2) // 3f084129
LDPSW (R0), (R1, R2) // 01084069
LDPSW 4(R0), (R1, R2) // 01884069
LDPSW -4(R0), (R1, R2) // 01887f69
@@ -1036,6 +1046,7 @@ next:
LDPSW 1024(RSP), (R1, R2) // fb031091610b4069
LDPSW x(SB), (R1, R2)
LDPSW x+8(SB), (R1, R2)
+ LDPSW 8(R1), (ZR, R2) // 3f084169
STP (R3, R4), (R5) // a31000a9
STP (R3, R4), 8(R5) // a39000a9
STP.W (R3, R4), 8(R5) // a39080a9
diff --git a/src/cmd/asm/internal/asm/testdata/arm64error.s b/src/cmd/asm/internal/asm/testdata/arm64error.s
index e1eafa2b46..3ac8788424 100644
--- a/src/cmd/asm/internal/asm/testdata/arm64error.s
+++ b/src/cmd/asm/internal/asm/testdata/arm64error.s
@@ -66,7 +66,6 @@ TEXT errors(SB),$0
LDP.W 8(R3), (R2, R3) // ERROR "constrained unpredictable behavior"
LDP (R1), (R2, R2) // ERROR "constrained unpredictable behavior"
LDP (R0), (F0, F1) // ERROR "invalid register pair"
- LDP (R0), (R3, ZR) // ERROR "invalid register pair"
LDXPW (RSP), (R2, R2) // ERROR "constrained unpredictable behavior"
LDAXPW (R5), (R2, R2) // ERROR "constrained unpredictable behavior"
MOVD.P 300(R2), R3 // ERROR "offset out of range [-256,255]"
diff --git a/src/cmd/asm/internal/asm/testdata/s390x.s b/src/cmd/asm/internal/asm/testdata/s390x.s
index 82aa445356..977190678f 100644
--- a/src/cmd/asm/internal/asm/testdata/s390x.s
+++ b/src/cmd/asm/internal/asm/testdata/s390x.s
@@ -419,9 +419,9 @@ TEXT main·foo(SB),DUPOK|NOSPLIT,$16-0 // TEXT main.foo(SB), DUPOK|NOSPLIT, $16-
KMC R2, R6 // b92f0026
KLMD R2, R8 // b93f0028
KIMD R0, R4 // b93e0004
- KDSA R0, R8 // b93a0008
- KMA R6, R2, R4 // b9296024
- KMCTR R6, R2, R4 // b92d6024
+ KDSA R0, R8 // b93a0008
+ KMA R2, R6, R4 // b9296024
+ KMCTR R2, R6, R4 // b92d6024
// vector add and sub instructions
VAB V3, V4, V4 // e743400000f3
diff --git a/src/cmd/cgo/internal/test/callback_windows.go b/src/cmd/cgo/internal/test/callback_windows.go
index 95e97c9af9..77bdfa4dd3 100644
--- a/src/cmd/cgo/internal/test/callback_windows.go
+++ b/src/cmd/cgo/internal/test/callback_windows.go
@@ -29,7 +29,7 @@ USHORT backtrace(ULONG FramesToCapture, PVOID *BackTrace) {
}
ControlPc = context.Rip;
- // Check if we left the user range.
+ // Check if we left the user range.
if (ControlPc < 0x10000) {
break;
}
@@ -65,32 +65,17 @@ func testCallbackCallersSEH(t *testing.T) {
// TODO: support SEH on other architectures.
t.Skip("skipping on non-amd64")
}
- const cgoexpPrefix = "_cgoexp_"
+ // Only frames in the test package are checked.
want := []string{
- "runtime.asmcgocall_landingpad",
- "runtime.asmcgocall",
- "runtime.cgocall",
"test._Cfunc_backtrace",
"test.testCallbackCallersSEH.func1.1",
"test.testCallbackCallersSEH.func1",
"test.goCallback",
- cgoexpPrefix + "goCallback",
- "runtime.cgocallbackg1",
- "runtime.cgocallbackg",
- "runtime.cgocallbackg",
- "runtime.cgocallback",
- "crosscall2",
- "runtime.asmcgocall_landingpad",
- "runtime.asmcgocall",
- "runtime.cgocall",
"test._Cfunc_callback",
"test.nestedCall.func1",
"test.nestedCall",
"test.testCallbackCallersSEH",
"test.TestCallbackCallersSEH",
- "testing.tRunner",
- "testing.(*T).Run.gowrap1",
- "runtime.goexit",
}
pc := make([]uintptr, 100)
n := 0
@@ -105,26 +90,17 @@ func testCallbackCallersSEH(t *testing.T) {
}
fname := f.Name()
switch fname {
- case "goCallback", "callback":
- // TODO(qmuntal): investigate why these functions don't appear
+ case "goCallback":
+ // TODO(qmuntal): investigate why this function doesn't appear
// when using the external linker.
continue
}
- // Skip cgo-generated functions, the runtime might not know about them,
- // depending on the link mode.
- if strings.HasPrefix(fname, "_cgo_") {
- continue
- }
- // Remove the cgo-generated random prefix.
- if strings.HasPrefix(fname, cgoexpPrefix) {
- idx := strings.Index(fname[len(cgoexpPrefix):], "_")
- if idx >= 0 {
- fname = cgoexpPrefix + fname[len(cgoexpPrefix)+idx+1:]
- }
- }
// In module mode, this package has a fully-qualified import path.
// Remove it if present.
fname = strings.TrimPrefix(fname, "cmd/cgo/internal/")
+ if !strings.HasPrefix(fname, "test.") {
+ continue
+ }
got = append(got, fname)
}
if !reflect.DeepEqual(want, got) {
diff --git a/src/cmd/cgo/internal/test/issue9400/asm_mips64x.s b/src/cmd/cgo/internal/test/issue9400/asm_mips64x.s
index 1f492eafe9..3edba3dd82 100644
--- a/src/cmd/cgo/internal/test/issue9400/asm_mips64x.s
+++ b/src/cmd/cgo/internal/test/issue9400/asm_mips64x.s
@@ -1,4 +1,4 @@
-// Copyright 2016 The Go Authors. All rights reserved.
+// Copyright 2016 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.
diff --git a/src/cmd/cgo/internal/test/issue9400/asm_riscv64.s b/src/cmd/cgo/internal/test/issue9400/asm_riscv64.s
index fa34f6bd37..0f10e3a326 100644
--- a/src/cmd/cgo/internal/test/issue9400/asm_riscv64.s
+++ b/src/cmd/cgo/internal/test/issue9400/asm_riscv64.s
@@ -1,4 +1,4 @@
-// Copyright 2020 The Go Authors. All rights reserved.
+// Copyright 2020 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.
diff --git a/src/cmd/cgo/internal/testgodefs/testdata/fieldtypedef.go b/src/cmd/cgo/internal/testgodefs/testdata/fieldtypedef.go
index b0c507477f..d3ab1902c1 100644
--- a/src/cmd/cgo/internal/testgodefs/testdata/fieldtypedef.go
+++ b/src/cmd/cgo/internal/testgodefs/testdata/fieldtypedef.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The Go Authors. All rights reserve d.
+// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
diff --git a/src/cmd/cgo/internal/testsanitizers/libfuzzer_test.go b/src/cmd/cgo/internal/testsanitizers/libfuzzer_test.go
index f84c9f37ae..3f5b1d91c7 100644
--- a/src/cmd/cgo/internal/testsanitizers/libfuzzer_test.go
+++ b/src/cmd/cgo/internal/testsanitizers/libfuzzer_test.go
@@ -7,11 +7,14 @@
package sanitizers_test
import (
+ "internal/testenv"
"strings"
"testing"
)
func TestLibFuzzer(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ testenv.MustHaveCGO(t)
goos, err := goEnv("GOOS")
if err != nil {
t.Fatal(err)
diff --git a/src/cmd/cgo/internal/testsanitizers/msan_test.go b/src/cmd/cgo/internal/testsanitizers/msan_test.go
index 1a22b5246c..83d66f6660 100644
--- a/src/cmd/cgo/internal/testsanitizers/msan_test.go
+++ b/src/cmd/cgo/internal/testsanitizers/msan_test.go
@@ -8,11 +8,14 @@ package sanitizers_test
import (
"internal/platform"
+ "internal/testenv"
"strings"
"testing"
)
func TestMSAN(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ testenv.MustHaveCGO(t)
goos, err := goEnv("GOOS")
if err != nil {
t.Fatal(err)
diff --git a/src/cmd/compile/abi-internal.md b/src/cmd/compile/abi-internal.md
index 43dc39689b..eae230dc07 100644
--- a/src/cmd/compile/abi-internal.md
+++ b/src/cmd/compile/abi-internal.md
@@ -633,6 +633,56 @@ modifying or saving the FPCR.
Functions are allowed to modify it between calls (as long as they
restore it), but as of this writing Go code never does.
+### loong64 architecture
+
+The loong64 architecture uses R4 – R19 for integer arguments and integer results.
+
+It uses F0 – F15 for floating-point arguments and results.
+
+Registers R20 - R21, R23 – R28, R30 - R31, F16 – F31 are permanent scratch registers.
+
+Register R2 is reserved and never used.
+
+Register R20, R21 is Used by runtime.duffcopy, runtime.duffzero.
+
+Special-purpose registers used within Go generated code and Go assembly code
+are as follows:
+
+| Register | Call meaning | Return meaning | Body meaning |
+| --- | --- | --- | --- |
+| R0 | Zero value | Same | Same |
+| R1 | Link register | Link register | Scratch |
+| R3 | Stack pointer | Same | Same |
+| R20,R21 | Scratch | Scratch | Used by duffcopy, duffzero |
+| R22 | Current goroutine | Same | Same |
+| R29 | Closure context pointer | Same | Same |
+| R30, R31 | used by the assembler | Same | Same |
+
+*Rationale*: These register meanings are compatible with Go’s stack-based
+calling convention.
+
+#### Stack layout
+
+The stack pointer, R3, grows down and is aligned to 8 bytes.
+
+A function's stack frame, after the frame is created, is laid out as
+follows:
+
+ +------------------------------+
+ | ... locals ... |
+ | ... outgoing arguments ... |
+ | return PC | ← R3 points to
+ +------------------------------+ ↓ lower addresses
+
+This stack layout is used by both register-based (ABIInternal) and
+stack-based (ABI0) calling conventions.
+
+The "return PC" is loaded to the link register, R1, as part of the
+loong64 `JAL` operation.
+
+#### Flags
+All bits in CSR are system flags and are not modified by Go.
+
### ppc64 architecture
The ppc64 architecture uses R3 – R10 and R14 – R17 for integer arguments
diff --git a/src/cmd/compile/default.pgo b/src/cmd/compile/default.pgo
index 2ba79688d4..0f925ec69c 100644
Binary files a/src/cmd/compile/default.pgo and b/src/cmd/compile/default.pgo differ
diff --git a/src/cmd/compile/internal/arm/galign.go b/src/cmd/compile/internal/arm/galign.go
index 23e52bacbf..43d811832e 100644
--- a/src/cmd/compile/internal/arm/galign.go
+++ b/src/cmd/compile/internal/arm/galign.go
@@ -15,7 +15,7 @@ func Init(arch *ssagen.ArchInfo) {
arch.LinkArch = &arm.Linkarm
arch.REGSP = arm.REGSP
arch.MAXWIDTH = (1 << 32) - 1
- arch.SoftFloat = buildcfg.GOARM == 5
+ arch.SoftFloat = buildcfg.GOARM.SoftFloat
arch.ZeroRange = zerorange
arch.Ginsnop = ginsnop
diff --git a/src/cmd/compile/internal/arm/ssa.go b/src/cmd/compile/internal/arm/ssa.go
index 7fcbb4d024..638ed3ed4e 100644
--- a/src/cmd/compile/internal/arm/ssa.go
+++ b/src/cmd/compile/internal/arm/ssa.go
@@ -289,7 +289,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
case ssa.OpARMANDconst, ssa.OpARMBICconst:
// try to optimize ANDconst and BICconst to BFC, which saves bytes and ticks
// BFC is only available on ARMv7, and its result and source are in the same register
- if buildcfg.GOARM == 7 && v.Reg() == v.Args[0].Reg() {
+ if buildcfg.GOARM.Version == 7 && v.Reg() == v.Args[0].Reg() {
var val uint32
if v.Op == ssa.OpARMANDconst {
val = ^uint32(v.AuxInt)
@@ -646,7 +646,7 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
default:
}
}
- if buildcfg.GOARM >= 6 {
+ if buildcfg.GOARM.Version >= 6 {
// generate more efficient "MOVB/MOVBU/MOVH/MOVHU Reg@>0, Reg" on ARMv6 & ARMv7
genshift(s, v, v.Op.Asm(), 0, v.Args[0].Reg(), v.Reg(), arm.SHIFT_RR, 0)
return
diff --git a/src/cmd/compile/internal/base/debug.go b/src/cmd/compile/internal/base/debug.go
index a85f0139fc..aadd950a0a 100644
--- a/src/cmd/compile/internal/base/debug.go
+++ b/src/cmd/compile/internal/base/debug.go
@@ -36,7 +36,6 @@ type DebugFlags struct {
Gossahash string `help:"hash value for use in debugging the compiler"`
InlFuncsWithClosures int `help:"allow functions with closures to be inlined" concurrent:"ok"`
InlStaticInit int `help:"allow static initialization of inlined calls" concurrent:"ok"`
- InterfaceCycles int `help:"allow anonymous interface cycles"`
Libfuzzer int `help:"enable coverage instrumentation for libfuzzer"`
LoopVar int `help:"shared (0, default), 1 (private loop variables), 2, private + log"`
LoopVarHash string `help:"for debugging changes in loop behavior. Overrides experiment and loopvar flag."`
diff --git a/src/cmd/compile/internal/base/hashdebug.go b/src/cmd/compile/internal/base/hashdebug.go
index de7f01f09e..8342a5b9d9 100644
--- a/src/cmd/compile/internal/base/hashdebug.go
+++ b/src/cmd/compile/internal/base/hashdebug.go
@@ -204,7 +204,6 @@ func NewHashDebug(ev, s string, file io.Writer) *HashDebug {
i++
}
return hd
-
}
// TODO: Delete when we switch to bisect-only.
diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go
index 7b3a869d8e..5d1b952627 100644
--- a/src/cmd/compile/internal/devirtualize/devirtualize.go
+++ b/src/cmd/compile/internal/devirtualize/devirtualize.go
@@ -18,39 +18,27 @@ import (
"cmd/compile/internal/types"
)
-// Static devirtualizes calls within fn where possible when the concrete callee
+// StaticCall devirtualizes the given call if possible when the concrete callee
// is available statically.
-func Static(fn *ir.Func) {
- ir.CurFunc = fn
+func StaticCall(call *ir.CallExpr) {
+ // For promoted methods (including value-receiver methods promoted
+ // to pointer-receivers), the interface method wrapper may contain
+ // expressions that can panic (e.g., ODEREF, ODOTPTR,
+ // ODOTINTER). Devirtualization involves inlining these expressions
+ // (and possible panics) to the call site. This normally isn't a
+ // problem, but for go/defer statements it can move the panic from
+ // when/where the call executes to the go/defer statement itself,
+ // which is a visible change in semantics (e.g., #52072). To prevent
+ // this, we skip devirtualizing calls within go/defer statements
+ // altogether.
+ if call.GoDefer {
+ return
+ }
- // For promoted methods (including value-receiver methods promoted to pointer-receivers),
- // the interface method wrapper may contain expressions that can panic (e.g., ODEREF, ODOTPTR, ODOTINTER).
- // Devirtualization involves inlining these expressions (and possible panics) to the call site.
- // This normally isn't a problem, but for go/defer statements it can move the panic from when/where
- // the call executes to the go/defer statement itself, which is a visible change in semantics (e.g., #52072).
- // To prevent this, we skip devirtualizing calls within go/defer statements altogether.
- goDeferCall := make(map[*ir.CallExpr]bool)
- ir.VisitList(fn.Body, func(n ir.Node) {
- switch n := n.(type) {
- case *ir.GoDeferStmt:
- if call, ok := n.Call.(*ir.CallExpr); ok {
- goDeferCall[call] = true
- }
- return
- case *ir.CallExpr:
- if !goDeferCall[n] {
- staticCall(n)
- }
- }
- })
-}
-
-// staticCall devirtualizes the given call if possible when the concrete callee
-// is available statically.
-func staticCall(call *ir.CallExpr) {
if call.Op() != ir.OCALLINTER {
return
}
+
sel := call.Fun.(*ir.SelectorExpr)
r := ir.StaticValue(sel.X)
if r.Op() != ir.OCONVIFACE {
@@ -70,7 +58,7 @@ func staticCall(call *ir.CallExpr) {
return
}
- // If typ *has* a shape type, then it's an shaped, instantiated
+ // If typ *has* a shape type, then it's a shaped, instantiated
// type like T[go.shape.int], and its methods (may) have an extra
// dictionary parameter. We could devirtualize this call if we
// could derive an appropriate dictionary argument.
diff --git a/src/cmd/compile/internal/devirtualize/pgo.go b/src/cmd/compile/internal/devirtualize/pgo.go
index 05b37d6be6..170bf74673 100644
--- a/src/cmd/compile/internal/devirtualize/pgo.go
+++ b/src/cmd/compile/internal/devirtualize/pgo.go
@@ -107,9 +107,6 @@ func ProfileGuided(fn *ir.Func, p *pgo.Profile) {
name := ir.LinkFuncName(fn)
- // Can't devirtualize go/defer calls. See comment in Static.
- goDeferCall := make(map[*ir.CallExpr]bool)
-
var jsonW *json.Encoder
if base.Debug.PGODebug >= 3 {
jsonW = json.NewEncoder(os.Stdout)
@@ -121,12 +118,6 @@ func ProfileGuided(fn *ir.Func, p *pgo.Profile) {
return n
}
- if gds, ok := n.(*ir.GoDeferStmt); ok {
- if call, ok := gds.Call.(*ir.CallExpr); ok {
- goDeferCall[call] = true
- }
- }
-
ir.EditChildren(n, edit)
call, ok := n.(*ir.CallExpr)
@@ -156,7 +147,7 @@ func ProfileGuided(fn *ir.Func, p *pgo.Profile) {
fmt.Printf("%v: PGO devirtualize considering call %v\n", ir.Line(call), call)
}
- if goDeferCall[call] {
+ if call.GoDefer {
if base.Debug.PGODebug >= 2 {
fmt.Printf("%v: can't PGO devirtualize go/defer call %v\n", ir.Line(call), call)
}
diff --git a/src/cmd/compile/internal/escape/call.go b/src/cmd/compile/internal/escape/call.go
index bf40de0544..4a3753ada9 100644
--- a/src/cmd/compile/internal/escape/call.go
+++ b/src/cmd/compile/internal/escape/call.go
@@ -155,10 +155,17 @@ func (e *escape) call(ks []hole, call ir.Node) {
e.discard(call.X)
e.discard(call.Y)
- case ir.ODELETE, ir.OMAX, ir.OMIN, ir.OPRINT, ir.OPRINTLN, ir.ORECOVERFP:
+ case ir.ODELETE, ir.OPRINT, ir.OPRINTLN, ir.ORECOVERFP:
call := call.(*ir.CallExpr)
- for i := range call.Args {
- e.discard(call.Args[i])
+ for _, arg := range call.Args {
+ e.discard(arg)
+ }
+ e.discard(call.RType)
+
+ case ir.OMIN, ir.OMAX:
+ call := call.(*ir.CallExpr)
+ for _, arg := range call.Args {
+ argument(ks[0], arg)
}
e.discard(call.RType)
diff --git a/src/cmd/compile/internal/escape/graph.go b/src/cmd/compile/internal/escape/graph.go
index f3baa67223..75e2546a7b 100644
--- a/src/cmd/compile/internal/escape/graph.go
+++ b/src/cmd/compile/internal/escape/graph.go
@@ -38,7 +38,7 @@ import (
// e.value(k, n.Left)
// }
-// An location represents an abstract location that stores a Go
+// A location represents an abstract location that stores a Go
// variable.
type location struct {
n ir.Node // represented variable or expression, if any
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index a19962dabb..7e5069fced 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -9,10 +9,10 @@ import (
"bytes"
"cmd/compile/internal/base"
"cmd/compile/internal/coverage"
- "cmd/compile/internal/devirtualize"
"cmd/compile/internal/dwarfgen"
"cmd/compile/internal/escape"
"cmd/compile/internal/inline"
+ "cmd/compile/internal/inline/interleaved"
"cmd/compile/internal/ir"
"cmd/compile/internal/logopt"
"cmd/compile/internal/loopvar"
@@ -224,30 +224,15 @@ func Main(archInit func(*ssagen.ArchInfo)) {
}
}
- base.Timer.Start("fe", "pgo-devirtualization")
- if profile != nil && base.Debug.PGODevirtualize > 0 {
- // TODO(prattmic): No need to use bottom-up visit order. This
- // is mirroring the PGO IRGraph visit order, which also need
- // not be bottom-up.
- ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) {
- for _, fn := range list {
- devirtualize.ProfileGuided(fn, profile)
- }
- })
- ir.CurFunc = nil
- }
+ // Interleaved devirtualization and inlining.
+ base.Timer.Start("fe", "devirtualize-and-inline")
+ interleaved.DevirtualizeAndInlinePackage(typecheck.Target, profile)
- // Inlining
- base.Timer.Start("fe", "inlining")
- if base.Flag.LowerL != 0 {
- inline.InlinePackage(profile)
- }
noder.MakeWrappers(typecheck.Target) // must happen after inlining
- // Devirtualize and get variable capture right in for loops
+ // Get variable capture right in for loops.
var transformed []loopvar.VarAndLoop
for _, fn := range typecheck.Target.Funcs {
- devirtualize.Static(fn)
transformed = append(transformed, loopvar.ForCapture(fn)...)
}
ir.CurFunc = nil
diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go
index 2677ae3086..b365008c76 100644
--- a/src/cmd/compile/internal/inline/inl.go
+++ b/src/cmd/compile/internal/inline/inl.go
@@ -29,6 +29,7 @@ package inline
import (
"fmt"
"go/constant"
+ "internal/buildcfg"
"strconv"
"cmd/compile/internal/base"
@@ -76,8 +77,8 @@ var (
inlineHotMaxBudget int32 = 2000
)
-// pgoInlinePrologue records the hot callsites from ir-graph.
-func pgoInlinePrologue(p *pgo.Profile, funcs []*ir.Func) {
+// PGOInlinePrologue records the hot callsites from ir-graph.
+func PGOInlinePrologue(p *pgo.Profile, funcs []*ir.Func) {
if base.Debug.PGOInlineCDFThreshold != "" {
if s, err := strconv.ParseFloat(base.Debug.PGOInlineCDFThreshold, 64); err == nil && s >= 0 && s <= 100 {
inlineCDFHotCallSiteThresholdPercent = s
@@ -134,79 +135,52 @@ func hotNodesFromCDF(p *pgo.Profile) (float64, []pgo.NamedCallEdge) {
return 0, p.NamedEdgeMap.ByWeight
}
-// InlinePackage finds functions that can be inlined and clones them before walk expands them.
-func InlinePackage(p *pgo.Profile) {
- if base.Debug.PGOInline == 0 {
- p = nil
+// CanInlineFuncs computes whether a batch of functions are inlinable.
+func CanInlineFuncs(funcs []*ir.Func, profile *pgo.Profile) {
+ if profile != nil {
+ PGOInlinePrologue(profile, funcs)
}
- inlheur.SetupScoreAdjustments()
-
- InlineDecls(p, typecheck.Target.Funcs, true)
-
- // Perform a garbage collection of hidden closures functions that
- // are no longer reachable from top-level functions following
- // inlining. See #59404 and #59638 for more context.
- garbageCollectUnreferencedHiddenClosures()
-
- if base.Debug.DumpInlFuncProps != "" {
- inlheur.DumpFuncProps(nil, base.Debug.DumpInlFuncProps)
- }
- if inlheur.Enabled() {
- postProcessCallSites(p)
- inlheur.TearDown()
- }
+ ir.VisitFuncsBottomUp(funcs, func(list []*ir.Func, recursive bool) {
+ CanInlineSCC(list, recursive, profile)
+ })
}
-// InlineDecls applies inlining to the given batch of declarations.
-func InlineDecls(p *pgo.Profile, funcs []*ir.Func, doInline bool) {
- if p != nil {
- pgoInlinePrologue(p, funcs)
+// 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
}
- doCanInline := func(n *ir.Func, recursive bool, numfns int) {
+ 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(n, p)
+ CanInline(fn, profile)
} else {
- if base.Flag.LowerM > 1 && n.OClosure == nil {
- fmt.Printf("%v: cannot inline %v: recursive\n", ir.Line(n), n.Nname)
+ 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(n, p)
+ analyzeFuncProps(fn, profile)
}
}
-
- ir.VisitFuncsBottomUp(funcs, func(list []*ir.Func, recursive bool) {
- numfns := numNonClosures(list)
- // 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 ...
- for _, n := range list {
- doCanInline(n, recursive, numfns)
- }
- // ... then make a second pass to do inlining of calls.
- if doInline {
- for _, n := range list {
- InlineCalls(n, p)
- }
- }
- })
}
-// garbageCollectUnreferencedHiddenClosures makes a pass over all the
+// GarbageCollectUnreferencedHiddenClosures makes a pass over all the
// top-level (non-hidden-closure) functions looking for nested closure
// functions that are reachable, then sweeps through the Target.Decls
// list and marks any non-reachable hidden closure function as dead.
// See issues #59404 and #59638 for more context.
-func garbageCollectUnreferencedHiddenClosures() {
+func GarbageCollectUnreferencedHiddenClosures() {
liveFuncs := make(map[*ir.Func]bool)
@@ -336,7 +310,7 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
visitor := hairyVisitor{
curFunc: fn,
- isBigFunc: isBigFunc(fn),
+ isBigFunc: IsBigFunc(fn),
budget: budget,
maxBudget: budget,
extraCallCost: cc,
@@ -354,14 +328,22 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
CanDelayResults: canDelayResults(fn),
}
+ if base.Flag.LowerM != 0 || logopt.Enabled() {
+ noteInlinableFunc(n, fn, budget-visitor.budget)
+ }
+}
+// noteInlinableFunc issues a message to the user that the specified
+// function is inlinable.
+func noteInlinableFunc(n *ir.Name, fn *ir.Func, cost int32) {
if base.Flag.LowerM > 1 {
- fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, budget-visitor.budget, fn.Type(), ir.Nodes(fn.Body))
+ fmt.Printf("%v: can inline %v with cost %d as: %v { %v }\n", ir.Line(fn), n, cost, fn.Type(), ir.Nodes(fn.Body))
} else if base.Flag.LowerM != 0 {
fmt.Printf("%v: can inline %v\n", ir.Line(fn), n)
}
+ // JSON optimization log output.
if logopt.Enabled() {
- logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", budget-visitor.budget))
+ logopt.LogOpt(fn.Pos(), "canInlineFunction", "inline", ir.FuncName(fn), fmt.Sprintf("cost: %d", cost))
}
}
@@ -585,7 +567,7 @@ opSwitch:
// Check whether we'd actually inline this call. Set
// log == false since we aren't actually doing inlining
// yet.
- if canInlineCallExpr(v.curFunc, n, callee, v.isBigFunc, false) {
+ if ok, _ := canInlineCallExpr(v.curFunc, n, callee, v.isBigFunc, false); ok {
// mkinlcall would inline this call [1], so use
// the cost of the inline body as the cost of
// the call, as that is what will actually
@@ -732,14 +714,16 @@ opSwitch:
// particular, to avoid breaking the existing inlinability regress
// tests), we need to compensate for this here.
//
- // See also identical logic in isBigFunc.
- if init := n.Rhs[0].Init(); len(init) == 1 {
- if _, ok := init[0].(*ir.AssignListStmt); ok {
- // 4 for each value, because each temporary variable now
- // appears 3 times (DCL, LHS, RHS), plus an extra DCL node.
- //
- // 1 for the extra "tmp1, tmp2 = f()" assignment statement.
- v.budget += 4*int32(len(n.Lhs)) + 1
+ // See also identical logic in IsBigFunc.
+ if len(n.Rhs) > 0 {
+ if init := n.Rhs[0].Init(); len(init) == 1 {
+ if _, ok := init[0].(*ir.AssignListStmt); ok {
+ // 4 for each value, because each temporary variable now
+ // appears 3 times (DCL, LHS, RHS), plus an extra DCL node.
+ //
+ // 1 for the extra "tmp1, tmp2 = f()" assignment statement.
+ v.budget += 4*int32(len(n.Lhs)) + 1
+ }
}
}
@@ -771,12 +755,15 @@ opSwitch:
return ir.DoChildren(n, v.do)
}
-func isBigFunc(fn *ir.Func) bool {
+// IsBigFunc reports whether fn is a "big" function.
+//
+// Note: The criteria for "big" is heuristic and subject to change.
+func IsBigFunc(fn *ir.Func) bool {
budget := inlineBigFunctionNodes
return ir.Any(fn, func(n ir.Node) bool {
// See logic in hairyVisitor.doNode, explaining unified IR's
// handling of "a, b = f()" assignments.
- if n, ok := n.(*ir.AssignListStmt); ok && n.Op() == ir.OAS2 {
+ if n, ok := n.(*ir.AssignListStmt); ok && n.Op() == ir.OAS2 && len(n.Rhs) > 0 {
if init := n.Rhs[0].Init(); len(init) == 1 {
if _, ok := init[0].(*ir.AssignListStmt); ok {
budget += 4*len(n.Lhs) + 1
@@ -789,128 +776,40 @@ func isBigFunc(fn *ir.Func) bool {
})
}
-// InlineCalls/inlnode walks fn's statements and expressions and substitutes any
-// calls made to inlineable functions. This is the external entry point.
-func InlineCalls(fn *ir.Func, profile *pgo.Profile) {
- if inlheur.Enabled() && !fn.Wrapper() {
- inlheur.ScoreCalls(fn)
- defer inlheur.ScoreCallsCleanup()
+// TryInlineCall returns an inlined call expression for call, or nil
+// if inlining is not possible.
+func TryInlineCall(callerfn *ir.Func, call *ir.CallExpr, bigCaller bool, profile *pgo.Profile) *ir.InlinedCallExpr {
+ if base.Flag.LowerL == 0 {
+ return nil
}
- if base.Debug.DumpInlFuncProps != "" && !fn.Wrapper() {
- inlheur.DumpFuncProps(fn, base.Debug.DumpInlFuncProps)
+ if call.Op() != ir.OCALLFUNC {
+ return nil
}
- savefn := ir.CurFunc
- ir.CurFunc = fn
- bigCaller := isBigFunc(fn)
- if bigCaller && base.Flag.LowerM > 1 {
- fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn)
- }
- var inlCalls []*ir.InlinedCallExpr
- var edit func(ir.Node) ir.Node
- edit = func(n ir.Node) ir.Node {
- return inlnode(fn, n, bigCaller, &inlCalls, edit, profile)
- }
- ir.EditChildren(fn, edit)
-
- // If we inlined any calls, we want to recursively visit their
- // bodies for further inlining. However, we need to wait until
- // *after* the original function body has been expanded, or else
- // inlCallee can have false positives (e.g., #54632).
- for len(inlCalls) > 0 {
- call := inlCalls[0]
- inlCalls = inlCalls[1:]
- ir.EditChildren(call, edit)
+ if call.GoDefer || call.NoInline {
+ return nil
}
- ir.CurFunc = savefn
-}
-
-// inlnode recurses over the tree to find inlineable calls, which will
-// be turned into OINLCALLs by mkinlcall. When the recursion comes
-// back up will examine left, right, list, rlist, ninit, ntest, nincr,
-// nbody and nelse and use one of the 4 inlconv/glue functions above
-// to turn the OINLCALL into an expression, a statement, or patch it
-// in to this nodes list or rlist as appropriate.
-// NOTE it makes no sense to pass the glue functions down the
-// recursion to the level where the OINLCALL gets created because they
-// have to edit /this/ n, so you'd have to push that one down as well,
-// but then you may as well do it here. so this is cleaner and
-// shorter and less complicated.
-// The result of inlnode MUST be assigned back to n, e.g.
-//
-// n.Left = inlnode(n.Left)
-func inlnode(callerfn *ir.Func, n ir.Node, bigCaller bool, inlCalls *[]*ir.InlinedCallExpr, edit func(ir.Node) ir.Node, profile *pgo.Profile) ir.Node {
- if n == nil {
- return n
- }
-
- switch n.Op() {
- case ir.ODEFER, ir.OGO:
- n := n.(*ir.GoDeferStmt)
- switch call := n.Call; call.Op() {
- case ir.OCALLMETH:
- base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
- case ir.OCALLFUNC:
- call := call.(*ir.CallExpr)
- call.NoInline = true
- }
- case ir.OTAILCALL:
- n := n.(*ir.TailCallStmt)
- n.Call.NoInline = true // Not inline a tail call for now. Maybe we could inline it just like RETURN fn(arg)?
-
- // TODO do them here (or earlier),
- // so escape analysis can avoid more heapmoves.
- case ir.OCLOSURE:
- return n
- case ir.OCALLMETH:
- base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
- case ir.OCALLFUNC:
- n := n.(*ir.CallExpr)
- if n.Fun.Op() == ir.OMETHEXPR {
- // Prevent inlining some reflect.Value methods when using checkptr,
- // even when package reflect was compiled without it (#35073).
- if meth := ir.MethodExprName(n.Fun); meth != nil {
- s := meth.Sym()
- if base.Debug.Checkptr != 0 {
- switch types.ReflectSymName(s) {
- case "Value.UnsafeAddr", "Value.Pointer":
- return n
- }
- }
+ // Prevent inlining some reflect.Value methods when using checkptr,
+ // even when package reflect was compiled without it (#35073).
+ if base.Debug.Checkptr != 0 && call.Fun.Op() == ir.OMETHEXPR {
+ if method := ir.MethodExprName(call.Fun); method != nil {
+ switch types.ReflectSymName(method.Sym()) {
+ case "Value.UnsafeAddr", "Value.Pointer":
+ return nil
}
}
}
- lno := ir.SetPos(n)
-
- ir.EditChildren(n, edit)
-
- // with all the branches out of the way, it is now time to
- // transmogrify this node itself unless inhibited by the
- // switch at the top of this function.
- switch n.Op() {
- case ir.OCALLMETH:
- base.FatalfAt(n.Pos(), "OCALLMETH missed by typecheck")
-
- case ir.OCALLFUNC:
- call := n.(*ir.CallExpr)
- if call.NoInline {
- break
- }
- if base.Flag.LowerM > 3 {
- fmt.Printf("%v:call to func %+v\n", ir.Line(n), call.Fun)
- }
- if ir.IsIntrinsicCall(call) {
- break
- }
- if fn := inlCallee(callerfn, call.Fun, profile); fn != nil && typecheck.HaveInlineBody(fn) {
- n = mkinlcall(callerfn, call, fn, bigCaller, inlCalls)
- }
+ if base.Flag.LowerM > 3 {
+ fmt.Printf("%v:call to func %+v\n", ir.Line(call), call.Fun)
}
-
- base.Pos = lno
-
- return n
+ if ir.IsIntrinsicCall(call) {
+ return nil
+ }
+ if fn := inlCallee(callerfn, call.Fun, profile); fn != nil && typecheck.HaveInlineBody(fn) {
+ return mkinlcall(callerfn, call, fn, bigCaller)
+ }
+ return nil
}
// inlCallee takes a function-typed expression and returns the underlying function ONAME
@@ -961,9 +860,10 @@ var InlineCall = func(callerfn *ir.Func, call *ir.CallExpr, fn *ir.Func, inlInde
// inlineCostOK returns true if call n from caller to callee is cheap enough to
// inline. bigCaller indicates that caller is a big function.
//
-// If inlineCostOK returns false, it also returns the max cost that the callee
-// exceeded.
-func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool, int32) {
+// In addition to the "cost OK" boolean, it also returns the "max
+// cost" limit used to make the decision (which may differ depending
+// on func size), and the score assigned to this specific callsite.
+func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool, int32, int32) {
maxCost := int32(inlineMaxBudget)
if bigCaller {
// We use this to restrict inlining into very big functions.
@@ -977,12 +877,11 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool
if ok {
metric = int32(score)
}
-
}
if metric <= maxCost {
// Simple case. Function is already cheap enough.
- return true, 0
+ return true, 0, metric
}
// We'll also allow inlining of hot functions below inlineHotMaxBudget,
@@ -992,7 +891,7 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool
csi := pgo.CallSiteInfo{LineOffset: lineOffset, Caller: caller}
if _, ok := candHotEdgeMap[csi]; !ok {
// Cold
- return false, maxCost
+ return false, maxCost, metric
}
// Hot
@@ -1001,47 +900,49 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller bool) (bool
if base.Debug.PGODebug > 0 {
fmt.Printf("hot-big check disallows inlining for call %s (cost %d) at %v in big function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller))
}
- return false, maxCost
+ return false, maxCost, metric
}
if metric > inlineHotMaxBudget {
- return false, inlineHotMaxBudget
+ return false, inlineHotMaxBudget, metric
}
if !base.PGOHash.MatchPosWithInfo(n.Pos(), "inline", nil) {
// De-selected by PGO Hash.
- return false, maxCost
+ return false, maxCost, metric
}
if base.Debug.PGODebug > 0 {
fmt.Printf("hot-budget check allows inlining for call %s (cost %d) at %v in function %s\n", ir.PkgFuncName(callee), callee.Inl.Cost, ir.Line(n), ir.PkgFuncName(caller))
}
- return true, 0
+ return true, 0, metric
}
-// canInlineCallsite returns true if the call n from caller to callee can be
-// inlined. bigCaller indicates that caller is a big function. log indicates
-// that the 'cannot inline' reason should be logged.
+// canInlineCallsite 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.
//
// Preconditions: CanInline(callee) has already been called.
-func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCaller bool, log bool) bool {
+func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCaller bool, log bool) (bool, int32) {
if callee.Inl == nil {
// callee is never inlinable.
if log && logopt.Enabled() {
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
fmt.Sprintf("%s cannot be inlined", ir.PkgFuncName(callee)))
}
- return false
+ return false, 0
}
- if ok, maxCost := inlineCostOK(n, callerfn, callee, bigCaller); !ok {
+ ok, maxCost, callSiteScore := inlineCostOK(n, callerfn, callee, bigCaller)
+ if !ok {
// callee cost too high for this call site.
if log && logopt.Enabled() {
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
fmt.Sprintf("cost %d of %s exceeds max caller cost %d", callee.Inl.Cost, ir.PkgFuncName(callee), maxCost))
}
- return false
+ return false, 0
}
if callee == callerfn {
@@ -1049,7 +950,7 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
if log && logopt.Enabled() {
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(callerfn)))
}
- return false
+ return false, 0
}
if base.Flag.Cfg.Instrumenting && types.IsNoInstrumentPkg(callee.Sym().Pkg) {
@@ -1063,7 +964,7 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
fmt.Sprintf("call to runtime function %s in instrumented build", ir.PkgFuncName(callee)))
}
- return false
+ return false, 0
}
if base.Flag.Race && types.IsNoRacePkg(callee.Sym().Pkg) {
@@ -1071,7 +972,7 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(callerfn),
fmt.Sprintf(`call to into "no-race" package function %s in race build`, ir.PkgFuncName(callee)))
}
- return false
+ return false, 0
}
// Check if we've already inlined this function at this particular
@@ -1094,24 +995,24 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
fmt.Sprintf("repeated recursive cycle to %s", ir.PkgFuncName(callee)))
}
}
- return false
+ return false, 0
}
}
- return true
+ return true, callSiteScore
}
-// If n is a OCALLFUNC node, and fn is an ONAME node for a
-// function with an inlinable body, return an OINLCALL node that can replace n.
-// The returned node's Ninit has the parameter assignments, the Nbody is the
-// inlined function body, and (List, Rlist) contain the (input, output)
-// parameters.
+// mkinlcall returns an OINLCALL node that can replace OCALLFUNC n, or
+// nil if it cannot be inlined. callerfn is the function that contains
+// n, and fn is the function being called.
+//
// The result of mkinlcall MUST be assigned back to n, e.g.
//
// n.Left = mkinlcall(n.Left, fn, isddd)
-func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller bool, inlCalls *[]*ir.InlinedCallExpr) ir.Node {
- if !canInlineCallExpr(callerfn, n, fn, bigCaller, true) {
- return n
+func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller bool) *ir.InlinedCallExpr {
+ ok, score := canInlineCallExpr(callerfn, n, fn, bigCaller, true)
+ if !ok {
+ return nil
}
typecheck.AssertFixedCall(n)
@@ -1169,7 +1070,12 @@ func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller bool, i
}
if base.Flag.LowerM != 0 {
- fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn)
+ if buildcfg.Experiment.NewInliner {
+ fmt.Printf("%v: inlining call to %v with score %d\n",
+ ir.Line(n), fn, score)
+ } else {
+ fmt.Printf("%v: inlining call to %v\n", ir.Line(n), fn)
+ }
}
if base.Flag.LowerM > 2 {
fmt.Printf("%v: Before inlining: %+v\n", ir.Line(n), n)
@@ -1189,8 +1095,6 @@ func mkinlcall(callerfn *ir.Func, n *ir.CallExpr, fn *ir.Func, bigCaller bool, i
inlheur.UpdateCallsiteTable(callerfn, n, res)
}
- *inlCalls = append(*inlCalls, res)
-
return res
}
@@ -1294,7 +1198,7 @@ func isAtomicCoverageCounterUpdate(cn *ir.CallExpr) bool {
return v
}
-func postProcessCallSites(profile *pgo.Profile) {
+func PostProcessCallSites(profile *pgo.Profile) {
if base.Debug.DumpInlCallSiteScores != 0 {
budgetCallback := func(fn *ir.Func, prof *pgo.Profile) (int32, bool) {
v := inlineBudget(fn, prof, false, false)
diff --git a/src/cmd/compile/internal/inline/inlheur/analyze.go b/src/cmd/compile/internal/inline/inlheur/analyze.go
index 6c3db92afe..a1b6f358e1 100644
--- a/src/cmd/compile/internal/inline/inlheur/analyze.go
+++ b/src/cmd/compile/internal/inline/inlheur/analyze.go
@@ -98,12 +98,13 @@ func AnalyzeFunc(fn *ir.Func, canInline func(*ir.Func), budgetForFunc func(*ir.F
// inlinable; if it is over the default hairyness 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)
for i := len(funcs) - 1; i >= 0; i-- {
f := funcs[i]
if f.OClosure != nil && !f.InlinabilityChecked() {
canInline(f)
}
- funcProps := analyzeFunc(f, inlineMaxBudget)
+ funcProps := analyzeFunc(f, inlineMaxBudget, nameFinder)
revisitInlinability(f, funcProps, budgetForFunc)
if f.Inl != nil {
f.Inl.Properties = funcProps.SerializeToString()
@@ -122,11 +123,11 @@ func TearDown() {
scoreCallsCache.csl = nil
}
-func analyzeFunc(fn *ir.Func, inlineMaxBudget int) *FuncProps {
+func analyzeFunc(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) *FuncProps {
if funcInlHeur, ok := fpmap[fn]; ok {
return funcInlHeur.props
}
- funcProps, fcstab := computeFuncProps(fn, inlineMaxBudget)
+ funcProps, fcstab := computeFuncProps(fn, inlineMaxBudget, nf)
file, line := fnFileLine(fn)
entry := fnInlHeur{
fname: fn.Sym().Name,
@@ -153,7 +154,7 @@ func revisitInlinability(fn *ir.Func, funcProps *FuncProps, budgetForFunc func(*
if fn.Inl == nil {
return
}
- maxAdj := int32(largestScoreAdjustment(fn, funcProps))
+ maxAdj := int32(LargestNegativeScoreAdjustment(fn, funcProps))
budget := budgetForFunc(fn)
if fn.Inl.Cost+maxAdj > budget {
fn.Inl = nil
@@ -163,7 +164,7 @@ func revisitInlinability(fn *ir.Func, funcProps *FuncProps, budgetForFunc func(*
// computeFuncProps examines the Go function 'fn' and computes for it
// a function "properties" object, to be used to drive inlining
// heuristics. See comments on the FuncProps type for more info.
-func computeFuncProps(fn *ir.Func, inlineMaxBudget int) (*FuncProps, CallSiteTab) {
+func computeFuncProps(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) (*FuncProps, CallSiteTab) {
if debugTrace&debugTraceFuncs != 0 {
fmt.Fprintf(os.Stderr, "=-= starting analysis of func %v:\n%+v\n",
fn, fn)
@@ -171,13 +172,13 @@ func computeFuncProps(fn *ir.Func, inlineMaxBudget int) (*FuncProps, CallSiteTab
funcProps := new(FuncProps)
ffa := makeFuncFlagsAnalyzer(fn)
analyzers := []propAnalyzer{ffa}
- analyzers = addResultsAnalyzer(fn, analyzers, funcProps, inlineMaxBudget)
- analyzers = addParamsAnalyzer(fn, analyzers, funcProps)
+ analyzers = addResultsAnalyzer(fn, analyzers, funcProps, inlineMaxBudget, nf)
+ analyzers = addParamsAnalyzer(fn, analyzers, funcProps, nf)
runAnalyzersOnFunction(fn, analyzers)
for _, a := range analyzers {
a.setResults(funcProps)
}
- cstab := computeCallSiteTable(fn, fn.Body, nil, ffa.panicPathTable(), 0)
+ cstab := computeCallSiteTable(fn, fn.Body, nil, ffa.panicPathTable(), 0, nf)
return funcProps, cstab
}
diff --git a/src/cmd/compile/internal/inline/inlheur/analyze_func_callsites.go b/src/cmd/compile/internal/inline/inlheur/analyze_func_callsites.go
index 3e285d5181..36ebe18b82 100644
--- a/src/cmd/compile/internal/inline/inlheur/analyze_func_callsites.go
+++ b/src/cmd/compile/internal/inline/inlheur/analyze_func_callsites.go
@@ -14,23 +14,37 @@ import (
)
type callSiteAnalyzer struct {
+ fn *ir.Func
+ *nameFinder
+}
+
+type callSiteTableBuilder struct {
+ fn *ir.Func
+ *nameFinder
cstab CallSiteTab
- fn *ir.Func
ptab map[ir.Node]pstate
nstack []ir.Node
loopNest int
isInit bool
}
-func makeCallSiteAnalyzer(fn *ir.Func, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int) *callSiteAnalyzer {
- isInit := fn.IsPackageInit() || strings.HasPrefix(fn.Sym().Name, "init.")
+func makeCallSiteAnalyzer(fn *ir.Func) *callSiteAnalyzer {
return &callSiteAnalyzer{
- fn: fn,
- cstab: cstab,
- ptab: ptab,
- isInit: isInit,
- loopNest: loopNestingLevel,
- nstack: []ir.Node{fn},
+ fn: fn,
+ nameFinder: newNameFinder(fn),
+ }
+}
+
+func makeCallSiteTableBuilder(fn *ir.Func, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int, nf *nameFinder) *callSiteTableBuilder {
+ isInit := fn.IsPackageInit() || strings.HasPrefix(fn.Sym().Name, "init.")
+ return &callSiteTableBuilder{
+ fn: fn,
+ cstab: cstab,
+ ptab: ptab,
+ isInit: isInit,
+ loopNest: loopNestingLevel,
+ nstack: []ir.Node{fn},
+ nameFinder: nf,
}
}
@@ -39,22 +53,22 @@ func makeCallSiteAnalyzer(fn *ir.Func, cstab CallSiteTab, ptab map[ir.Node]pstat
// specific subtree within the AST for a function. The main intended
// use cases are for 'region' to be either A) an entire function body,
// or B) an inlined call expression.
-func computeCallSiteTable(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int) CallSiteTab {
- csa := makeCallSiteAnalyzer(fn, cstab, ptab, loopNestingLevel)
+func computeCallSiteTable(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, ptab map[ir.Node]pstate, loopNestingLevel int, nf *nameFinder) CallSiteTab {
+ cstb := makeCallSiteTableBuilder(fn, cstab, ptab, loopNestingLevel, nf)
var doNode func(ir.Node) bool
doNode = func(n ir.Node) bool {
- csa.nodeVisitPre(n)
+ cstb.nodeVisitPre(n)
ir.DoChildren(n, doNode)
- csa.nodeVisitPost(n)
+ cstb.nodeVisitPost(n)
return false
}
for _, n := range region {
doNode(n)
}
- return csa.cstab
+ return cstb.cstab
}
-func (csa *callSiteAnalyzer) flagsForNode(call *ir.CallExpr) CSPropBits {
+func (cstb *callSiteTableBuilder) flagsForNode(call *ir.CallExpr) CSPropBits {
var r CSPropBits
if debugTrace&debugTraceCalls != 0 {
@@ -63,21 +77,21 @@ func (csa *callSiteAnalyzer) flagsForNode(call *ir.CallExpr) CSPropBits {
}
// Set a bit if this call is within a loop.
- if csa.loopNest > 0 {
+ if cstb.loopNest > 0 {
r |= CallSiteInLoop
}
// Set a bit if the call is within an init function (either
// compiler-generated or user-written).
- if csa.isInit {
+ if cstb.isInit {
r |= CallSiteInInitFunc
}
// Decide whether to apply the panic path heuristic. Hack: don't
// apply this heuristic in the function "main.main" (mostly just
// to avoid annoying users).
- if !isMainMain(csa.fn) {
- r = csa.determinePanicPathBits(call, r)
+ if !isMainMain(cstb.fn) {
+ r = cstb.determinePanicPathBits(call, r)
}
return r
@@ -88,15 +102,15 @@ func (csa *callSiteAnalyzer) flagsForNode(call *ir.CallExpr) CSPropBits {
// panic/exit. Do this by walking back up the node stack to see if we
// can find either A) an enclosing panic, or B) a statement node that
// we've determined leads to a panic/exit.
-func (csa *callSiteAnalyzer) determinePanicPathBits(call ir.Node, r CSPropBits) CSPropBits {
- csa.nstack = append(csa.nstack, call)
+func (cstb *callSiteTableBuilder) determinePanicPathBits(call ir.Node, r CSPropBits) CSPropBits {
+ cstb.nstack = append(cstb.nstack, call)
defer func() {
- csa.nstack = csa.nstack[:len(csa.nstack)-1]
+ cstb.nstack = cstb.nstack[:len(cstb.nstack)-1]
}()
- for ri := range csa.nstack[:len(csa.nstack)-1] {
- i := len(csa.nstack) - ri - 1
- n := csa.nstack[i]
+ for ri := range cstb.nstack[:len(cstb.nstack)-1] {
+ i := len(cstb.nstack) - ri - 1
+ n := cstb.nstack[i]
_, isCallExpr := n.(*ir.CallExpr)
_, isStmt := n.(ir.Stmt)
if isCallExpr {
@@ -104,7 +118,7 @@ func (csa *callSiteAnalyzer) determinePanicPathBits(call ir.Node, r CSPropBits)
}
if debugTrace&debugTraceCalls != 0 {
- ps, inps := csa.ptab[n]
+ ps, inps := cstb.ptab[n]
fmt.Fprintf(os.Stderr, "=-= callpar %d op=%s ps=%s inptab=%v stmt=%v\n", i, n.Op().String(), ps.String(), inps, isStmt)
}
@@ -112,7 +126,7 @@ func (csa *callSiteAnalyzer) determinePanicPathBits(call ir.Node, r CSPropBits)
r |= CallSiteOnPanicPath
break
}
- if v, ok := csa.ptab[n]; ok {
+ if v, ok := cstb.ptab[n]; ok {
if v == psCallsPanic {
r |= CallSiteOnPanicPath
break
@@ -126,16 +140,15 @@ func (csa *callSiteAnalyzer) determinePanicPathBits(call ir.Node, r CSPropBits)
}
// propsForArg returns property bits for a given call argument expression arg.
-func (csa *callSiteAnalyzer) propsForArg(arg ir.Node) ActualExprPropBits {
- _, islit := isLiteral(arg)
- if islit {
+func (cstb *callSiteTableBuilder) propsForArg(arg ir.Node) ActualExprPropBits {
+ if cval := cstb.constValue(arg); cval != nil {
return ActualExprConstant
}
- if isConcreteConvIface(arg) {
+ if cstb.isConcreteConvIface(arg) {
return ActualExprIsConcreteConvIface
}
- fname, isfunc, _ := isFuncName(arg)
- if isfunc {
+ fname := cstb.funcName(arg)
+ if fname != nil {
if fn := fname.Func; fn != nil && typecheck.HaveInlineBody(fn) {
return ActualExprIsInlinableFunc
}
@@ -149,11 +162,11 @@ func (csa *callSiteAnalyzer) propsForArg(arg ir.Node) ActualExprPropBits {
// expression; these will be stored in the CallSite object for a given
// call and then consulted when scoring. If no arg has any interesting
// properties we try to save some space and return a nil slice.
-func (csa *callSiteAnalyzer) argPropsForCall(ce *ir.CallExpr) []ActualExprPropBits {
+func (cstb *callSiteTableBuilder) argPropsForCall(ce *ir.CallExpr) []ActualExprPropBits {
rv := make([]ActualExprPropBits, len(ce.Args))
somethingInteresting := false
for idx := range ce.Args {
- argProp := csa.propsForArg(ce.Args[idx])
+ argProp := cstb.propsForArg(ce.Args[idx])
somethingInteresting = somethingInteresting || (argProp != 0)
rv[idx] = argProp
}
@@ -163,9 +176,9 @@ func (csa *callSiteAnalyzer) argPropsForCall(ce *ir.CallExpr) []ActualExprPropBi
return rv
}
-func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
- flags := csa.flagsForNode(call)
- argProps := csa.argPropsForCall(call)
+func (cstb *callSiteTableBuilder) addCallSite(callee *ir.Func, call *ir.CallExpr) {
+ flags := cstb.flagsForNode(call)
+ argProps := cstb.argPropsForCall(call)
if debugTrace&debugTraceCalls != 0 {
fmt.Fprintf(os.Stderr, "=-= props %+v for call %v\n", argProps, call)
}
@@ -173,12 +186,12 @@ func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
cs := &CallSite{
Call: call,
Callee: callee,
- Assign: csa.containingAssignment(call),
+ Assign: cstb.containingAssignment(call),
ArgProps: argProps,
Flags: flags,
- ID: uint(len(csa.cstab)),
+ ID: uint(len(cstb.cstab)),
}
- if _, ok := csa.cstab[call]; ok {
+ if _, ok := cstb.cstab[call]; ok {
fmt.Fprintf(os.Stderr, "*** cstab duplicate entry at: %s\n",
fmtFullPos(call.Pos()))
fmt.Fprintf(os.Stderr, "*** call: %+v\n", call)
@@ -189,38 +202,38 @@ func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
// on heuristics.
cs.Score = int(callee.Inl.Cost)
- if csa.cstab == nil {
- csa.cstab = make(CallSiteTab)
+ if cstb.cstab == nil {
+ cstb.cstab = make(CallSiteTab)
}
- csa.cstab[call] = cs
+ cstb.cstab[call] = cs
if debugTrace&debugTraceCalls != 0 {
fmt.Fprintf(os.Stderr, "=-= added callsite: caller=%v callee=%v n=%s\n",
- csa.fn, callee, fmtFullPos(call.Pos()))
+ cstb.fn, callee, fmtFullPos(call.Pos()))
}
}
-func (csa *callSiteAnalyzer) nodeVisitPre(n ir.Node) {
+func (cstb *callSiteTableBuilder) nodeVisitPre(n ir.Node) {
switch n.Op() {
case ir.ORANGE, ir.OFOR:
if !hasTopLevelLoopBodyReturnOrBreak(loopBody(n)) {
- csa.loopNest++
+ cstb.loopNest++
}
case ir.OCALLFUNC:
ce := n.(*ir.CallExpr)
callee := pgo.DirectCallee(ce.Fun)
if callee != nil && callee.Inl != nil {
- csa.addCallSite(callee, ce)
+ cstb.addCallSite(callee, ce)
}
}
- csa.nstack = append(csa.nstack, n)
+ cstb.nstack = append(cstb.nstack, n)
}
-func (csa *callSiteAnalyzer) nodeVisitPost(n ir.Node) {
- csa.nstack = csa.nstack[:len(csa.nstack)-1]
+func (cstb *callSiteTableBuilder) nodeVisitPost(n ir.Node) {
+ cstb.nstack = cstb.nstack[:len(cstb.nstack)-1]
switch n.Op() {
case ir.ORANGE, ir.OFOR:
if !hasTopLevelLoopBodyReturnOrBreak(loopBody(n)) {
- csa.loopNest--
+ cstb.loopNest--
}
}
}
@@ -281,8 +294,8 @@ func hasTopLevelLoopBodyReturnOrBreak(loopBody ir.Nodes) bool {
// call to a pair of auto-temps, then the second one assigning the
// auto-temps to the user-visible vars. This helper will return the
// second (outer) of these two.
-func (csa *callSiteAnalyzer) containingAssignment(n ir.Node) ir.Node {
- parent := csa.nstack[len(csa.nstack)-1]
+func (cstb *callSiteTableBuilder) containingAssignment(n ir.Node) ir.Node {
+ parent := cstb.nstack[len(cstb.nstack)-1]
// assignsOnlyAutoTemps returns TRUE of the specified OAS2FUNC
// node assigns only auto-temps.
@@ -315,12 +328,12 @@ func (csa *callSiteAnalyzer) containingAssignment(n ir.Node) ir.Node {
// OAS1({x,y},OCONVNOP(OAS2FUNC({auto1,auto2},OCALLFUNC(bar))))
//
if assignsOnlyAutoTemps(parent) {
- par2 := csa.nstack[len(csa.nstack)-2]
+ par2 := cstb.nstack[len(cstb.nstack)-2]
if par2.Op() == ir.OAS2 {
return par2
}
if par2.Op() == ir.OCONVNOP {
- par3 := csa.nstack[len(csa.nstack)-3]
+ par3 := cstb.nstack[len(cstb.nstack)-3]
if par3.Op() == ir.OAS2 {
return par3
}
@@ -378,18 +391,23 @@ func UpdateCallsiteTable(callerfn *ir.Func, n *ir.CallExpr, ic *ir.InlinedCallEx
loopNestLevel = 1
}
ptab := map[ir.Node]pstate{ic: icp}
- icstab := computeCallSiteTable(callerfn, ic.Body, nil, ptab, loopNestLevel)
+ nf := newNameFinder(nil)
+ icstab := computeCallSiteTable(callerfn, ic.Body, nil, ptab, loopNestLevel, nf)
// Record parent callsite. This is primarily for debug output.
for _, cs := range icstab {
cs.parent = oldcs
}
- // Score the calls in the inlined body. Note the setting of "doCallResults"
- // to false here: at the moment there isn't any easy way to localize
- // or region-ize the work done by "rescoreBasedOnCallResultUses", which
- // currently does a walk over the entire function to look for uses
- // of a given set of results.
+ // Score the calls in the inlined body. Note the setting of
+ // "doCallResults" to false here: at the moment there isn't any
+ // easy way to localize or region-ize the work done by
+ // "rescoreBasedOnCallResultUses", which currently does a walk
+ // over the entire function to look for uses of a given set of
+ // results. Similarly we're passing nil to makeCallSiteAnalyzer,
+ // so as to run name finding without the use of static value &
+ // friends.
+ csa := makeCallSiteAnalyzer(nil)
const doCallResults = false
- scoreCallsRegion(callerfn, ic.Body, icstab, doCallResults, ic)
+ csa.scoreCallsRegion(callerfn, ic.Body, icstab, doCallResults, ic)
}
diff --git a/src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go b/src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go
index 588d2f4f59..b7403a4f8c 100644
--- a/src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go
+++ b/src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go
@@ -66,34 +66,24 @@ func (ffa *funcFlagsAnalyzer) setResults(funcProps *FuncProps) {
funcProps.Flags = rv
}
-func (ffa *funcFlagsAnalyzer) getstate(n ir.Node) pstate {
- val, ok := ffa.nstate[n]
- if !ok {
- base.Fatalf("funcFlagsAnalyzer: fn %q node %s line %s: internal error, no setting for node:\n%+v\n", ffa.fn.Sym().Name, n.Op().String(), ir.Line(n), n)
- }
- return val
+func (ffa *funcFlagsAnalyzer) getState(n ir.Node) pstate {
+ return ffa.nstate[n]
}
-func (ffa *funcFlagsAnalyzer) setstate(n ir.Node, st pstate) {
- if _, ok := ffa.nstate[n]; ok {
- base.Fatalf("funcFlagsAnalyzer: fn %q internal error, existing setting for node:\n%+v\n", ffa.fn.Sym().Name, n)
- } else {
+func (ffa *funcFlagsAnalyzer) setState(n ir.Node, st pstate) {
+ if st != psNoInfo {
ffa.nstate[n] = st
}
}
-func (ffa *funcFlagsAnalyzer) updatestate(n ir.Node, st pstate) {
- if _, ok := ffa.nstate[n]; !ok {
- base.Fatalf("funcFlagsAnalyzer: fn %q internal error, expected existing setting for node:\n%+v\n", ffa.fn.Sym().Name, n)
+func (ffa *funcFlagsAnalyzer) updateState(n ir.Node, st pstate) {
+ if st == psNoInfo {
+ delete(ffa.nstate, n)
} else {
ffa.nstate[n] = st
}
}
-func (ffa *funcFlagsAnalyzer) setstateSoft(n ir.Node, st pstate) {
- ffa.nstate[n] = st
-}
-
func (ffa *funcFlagsAnalyzer) panicPathTable() map[ir.Node]pstate {
return ffa.nstate
}
@@ -164,13 +154,13 @@ func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate {
// line 10 will be on a panic path).
for i := len(list) - 1; i >= 0; i-- {
n := list[i]
- psi := ffa.getstate(n)
+ psi := ffa.getState(n)
if debugTrace&debugTraceFuncFlags != 0 {
fmt.Fprintf(os.Stderr, "=-= %v: stateForList n=%s ps=%s\n",
ir.Line(n), n.Op().String(), psi.String())
}
st = blockCombine(psi, st)
- ffa.updatestate(n, st)
+ ffa.updateState(n, st)
}
if st == psTop {
st = psNoInfo
@@ -237,8 +227,6 @@ func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) {
ir.Line(n), n.Op().String(), shouldVisit(n))
}
if !shouldVisit(n) {
- // invoke soft set, since node may be shared (e.g. ONAME)
- ffa.setstateSoft(n, psNoInfo)
return
}
var st pstate
@@ -361,7 +349,7 @@ func (ffa *funcFlagsAnalyzer) nodeVisitPost(n ir.Node) {
fmt.Fprintf(os.Stderr, "=-= %v: visit n=%s returns %s\n",
ir.Line(n), n.Op().String(), st.String())
}
- ffa.setstate(n, st)
+ ffa.setState(n, st)
}
func (ffa *funcFlagsAnalyzer) nodeVisitPre(n ir.Node) {
diff --git a/src/cmd/compile/internal/inline/inlheur/analyze_func_params.go b/src/cmd/compile/internal/inline/inlheur/analyze_func_params.go
index 0ce0af43a2..d85d73b2ef 100644
--- a/src/cmd/compile/internal/inline/inlheur/analyze_func_params.go
+++ b/src/cmd/compile/internal/inline/inlheur/analyze_func_params.go
@@ -19,6 +19,7 @@ type paramsAnalyzer struct {
params []*ir.Name
top []bool
*condLevelTracker
+ *nameFinder
}
// getParams returns an *ir.Name slice containing all params for the
@@ -34,8 +35,8 @@ func getParams(fn *ir.Func) []*ir.Name {
// new list. If the function in question doesn't have any interesting
// parameters then the analyzer list is returned unchanged, and the
// params flags in "fp" are updated accordingly.
-func addParamsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps) []propAnalyzer {
- pa, props := makeParamsAnalyzer(fn)
+func addParamsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, nf *nameFinder) []propAnalyzer {
+ pa, props := makeParamsAnalyzer(fn, nf)
if pa != nil {
analyzers = append(analyzers, pa)
} else {
@@ -48,7 +49,7 @@ func addParamsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps) []p
// 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.
-func makeParamsAnalyzer(fn *ir.Func) (*paramsAnalyzer, []ParamPropBits) {
+func makeParamsAnalyzer(fn *ir.Func, nf *nameFinder) (*paramsAnalyzer, []ParamPropBits) {
params := getParams(fn) // includes receiver if applicable
if len(params) == 0 {
return nil, nil
@@ -98,6 +99,7 @@ func makeParamsAnalyzer(fn *ir.Func) (*paramsAnalyzer, []ParamPropBits) {
params: params,
top: top,
condLevelTracker: new(condLevelTracker),
+ nameFinder: nf,
}
return pa, nil
}
@@ -162,7 +164,7 @@ func (pa *paramsAnalyzer) callCheckParams(ce *ir.CallExpr) {
return
}
sel := ce.Fun.(*ir.SelectorExpr)
- r := ir.StaticValue(sel.X)
+ r := pa.staticValue(sel.X)
if r.Op() != ir.ONAME {
return
}
@@ -193,8 +195,8 @@ func (pa *paramsAnalyzer) callCheckParams(ce *ir.CallExpr) {
return name == p, false
})
} else {
- cname, isFunc, _ := isFuncName(called)
- if isFunc {
+ cname := pa.funcName(called)
+ if cname != nil {
pa.deriveFlagsFromCallee(ce, cname.Func)
}
}
@@ -238,7 +240,7 @@ func (pa *paramsAnalyzer) deriveFlagsFromCallee(ce *ir.CallExpr, callee *ir.Func
}
// See if one of the caller's parameters is flowing unmodified
// into this actual expression.
- r := ir.StaticValue(arg)
+ r := pa.staticValue(arg)
if r.Op() != ir.ONAME {
return
}
@@ -247,7 +249,13 @@ func (pa *paramsAnalyzer) deriveFlagsFromCallee(ce *ir.CallExpr, callee *ir.Func
return
}
callerParamIdx := pa.findParamIdx(name)
- if callerParamIdx == -1 || pa.params[callerParamIdx] == nil {
+ // note that callerParamIdx may return -1 in the case where
+ // the param belongs not to the current closure func we're
+ // analyzing but to an outer enclosing func.
+ if callerParamIdx == -1 {
+ return
+ }
+ if pa.params[callerParamIdx] == nil {
panic("something went wrong")
}
if !pa.top[callerParamIdx] &&
diff --git a/src/cmd/compile/internal/inline/inlheur/analyze_func_returns.go b/src/cmd/compile/internal/inline/inlheur/analyze_func_returns.go
index 58b0f54697..2aaa68d1b7 100644
--- a/src/cmd/compile/internal/inline/inlheur/analyze_func_returns.go
+++ b/src/cmd/compile/internal/inline/inlheur/analyze_func_returns.go
@@ -20,6 +20,7 @@ type resultsAnalyzer struct {
props []ResultPropBits
values []resultVal
inlineMaxBudget int
+ *nameFinder
}
// resultVal captures information about a specific result returned from
@@ -28,7 +29,7 @@ type resultsAnalyzer struct {
// the same function, etc. This container stores info on a the specific
// scenarios we're looking for.
type resultVal struct {
- lit constant.Value
+ cval constant.Value
fn *ir.Name
fnClo bool
top bool
@@ -40,8 +41,8 @@ type resultVal struct {
// new list. If the function in question doesn't have any returns (or
// any interesting returns) then the analyzer list is left as is, and
// the result flags in "fp" are updated accordingly.
-func addResultsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, inlineMaxBudget int) []propAnalyzer {
- ra, props := makeResultsAnalyzer(fn, inlineMaxBudget)
+func addResultsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, inlineMaxBudget int, nf *nameFinder) []propAnalyzer {
+ ra, props := makeResultsAnalyzer(fn, inlineMaxBudget, nf)
if ra != nil {
analyzers = append(analyzers, ra)
} else {
@@ -54,7 +55,7 @@ func addResultsAnalyzer(fn *ir.Func, analyzers []propAnalyzer, fp *FuncProps, in
// in function fn. If the function doesn't have any interesting
// results, a nil helper is returned along with a set of default
// result flags for the func.
-func makeResultsAnalyzer(fn *ir.Func, inlineMaxBudget int) (*resultsAnalyzer, []ResultPropBits) {
+func makeResultsAnalyzer(fn *ir.Func, inlineMaxBudget int, nf *nameFinder) (*resultsAnalyzer, []ResultPropBits) {
results := fn.Type().Results()
if len(results) == 0 {
return nil, nil
@@ -84,6 +85,7 @@ func makeResultsAnalyzer(fn *ir.Func, inlineMaxBudget int) (*resultsAnalyzer, []
props: props,
values: vals,
inlineMaxBudget: inlineMaxBudget,
+ nameFinder: nf,
}
return ra, nil
}
@@ -143,29 +145,6 @@ func (ra *resultsAnalyzer) nodeVisitPost(n ir.Node) {
}
}
-// isFuncName returns the *ir.Name for the func or method
-// corresponding to node 'n', along with a boolean indicating success,
-// and another boolean indicating whether the func is closure.
-func isFuncName(n ir.Node) (*ir.Name, bool, bool) {
- sv := ir.StaticValue(n)
- if sv.Op() == ir.ONAME {
- name := sv.(*ir.Name)
- if name.Sym() != nil && name.Class == ir.PFUNC {
- return name, true, false
- }
- }
- if sv.Op() == ir.OCLOSURE {
- cloex := sv.(*ir.ClosureExpr)
- return cloex.Func.Nname, true, true
- }
- if sv.Op() == ir.OMETHEXPR {
- if mn := ir.MethodExprName(sv); mn != nil {
- return mn, true, false
- }
- }
- return nil, false, false
-}
-
// analyzeResult examines the expression 'n' being returned as the
// 'ii'th argument in some return statement to see whether has
// interesting characteristics (for example, returns a constant), then
@@ -173,18 +152,22 @@ func isFuncName(n ir.Node) (*ir.Name, bool, bool) {
// previous result (for the given return slot) that we've already
// processed.
func (ra *resultsAnalyzer) analyzeResult(ii int, n ir.Node) {
- isAllocMem := isAllocatedMem(n)
- isConcConvItf := isConcreteConvIface(n)
- lit, isConst := isLiteral(n)
- rfunc, isFunc, isClo := isFuncName(n)
+ isAllocMem := ra.isAllocatedMem(n)
+ isConcConvItf := ra.isConcreteConvIface(n)
+ constVal := ra.constValue(n)
+ isConst := (constVal != nil)
+ isNil := ra.isNil(n)
+ rfunc := ra.funcName(n)
+ isFunc := (rfunc != nil)
+ isClo := (rfunc != nil && rfunc.Func.OClosure != nil)
curp := ra.props[ii]
- dprops, isDerivedFromCall := deriveReturnFlagsFromCallee(n)
+ dprops, isDerivedFromCall := ra.deriveReturnFlagsFromCallee(n)
newp := ResultNoInfo
- var newlit constant.Value
+ var newcval constant.Value
var newfunc *ir.Name
if debugTrace&debugTraceResults != 0 {
- fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult n=%s ismem=%v isconcconv=%v isconst=%v isfunc=%v isclo=%v\n", ir.Line(n), n.Op().String(), isAllocMem, isConcConvItf, isConst, isFunc, isClo)
+ fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult n=%s ismem=%v isconcconv=%v isconst=%v isnil=%v isfunc=%v isclo=%v\n", ir.Line(n), n.Op().String(), isAllocMem, isConcConvItf, isConst, isNil, isFunc, isClo)
}
if ra.values[ii].top {
@@ -201,7 +184,10 @@ func (ra *resultsAnalyzer) analyzeResult(ii int, n ir.Node) {
newfunc = rfunc
case isConst:
newp = ResultAlwaysSameConstant
- newlit = lit
+ newcval = constVal
+ case isNil:
+ newp = ResultAlwaysSameConstant
+ newcval = nil
case isDerivedFromCall:
newp = dprops
ra.values[ii].derived = true
@@ -214,17 +200,20 @@ func (ra *resultsAnalyzer) analyzeResult(ii int, n ir.Node) {
// the previous returns.
switch curp {
case ResultIsAllocatedMem:
- if isAllocatedMem(n) {
+ if isAllocMem {
newp = ResultIsAllocatedMem
}
case ResultIsConcreteTypeConvertedToInterface:
- if isConcreteConvIface(n) {
+ if isConcConvItf {
newp = ResultIsConcreteTypeConvertedToInterface
}
case ResultAlwaysSameConstant:
- if isConst && isSameLiteral(lit, ra.values[ii].lit) {
+ if isNil && ra.values[ii].cval == nil {
newp = ResultAlwaysSameConstant
- newlit = lit
+ newcval = nil
+ } else if isConst && constant.Compare(constVal, token.EQL, ra.values[ii].cval) {
+ newp = ResultAlwaysSameConstant
+ newcval = constVal
}
case ResultAlwaysSameFunc:
if isFunc && isSameFuncName(rfunc, ra.values[ii].fn) {
@@ -236,7 +225,7 @@ func (ra *resultsAnalyzer) analyzeResult(ii int, n ir.Node) {
}
ra.values[ii].fn = newfunc
ra.values[ii].fnClo = isClo
- ra.values[ii].lit = newlit
+ ra.values[ii].cval = newcval
ra.props[ii] = newp
if debugTrace&debugTraceResults != 0 {
@@ -245,15 +234,6 @@ func (ra *resultsAnalyzer) analyzeResult(ii int, n ir.Node) {
}
}
-func isAllocatedMem(n ir.Node) bool {
- sv := ir.StaticValue(n)
- switch sv.Op() {
- case ir.OMAKESLICE, ir.ONEW, ir.OPTRLIT, ir.OSLICELIT:
- return true
- }
- return false
-}
-
// deriveReturnFlagsFromCallee tries to set properties for a given
// return result where we're returning call expression; return value
// is a return property value and a boolean indicating whether the
@@ -270,7 +250,7 @@ func isAllocatedMem(n ir.Node) bool {
// set foo's return property to that of bar. In the case of "two", however,
// even though each return path returns a constant, we don't know
// whether the constants are identical, hence we need to be conservative.
-func deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
+func (ra *resultsAnalyzer) deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
if n.Op() != ir.OCALLFUNC {
return 0, false
}
@@ -282,8 +262,8 @@ func deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
if called.Op() != ir.ONAME {
return 0, false
}
- cname, isFunc, _ := isFuncName(called)
- if !isFunc {
+ cname := ra.funcName(called)
+ if cname == nil {
return 0, false
}
calleeProps := propsForFunc(cname.Func)
@@ -295,41 +275,3 @@ func deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
}
return calleeProps.ResultFlags[0], true
}
-
-func isLiteral(n ir.Node) (constant.Value, bool) {
- sv := ir.StaticValue(n)
- switch sv.Op() {
- case ir.ONIL:
- return nil, true
- case ir.OLITERAL:
- return sv.Val(), true
- }
- return nil, false
-}
-
-// isSameLiteral checks to see if 'v1' and 'v2' correspond to the same
-// literal value, or if they are both nil.
-func isSameLiteral(v1, v2 constant.Value) bool {
- if v1 == nil && v2 == nil {
- return true
- }
- if v1 == nil || v2 == nil {
- return false
- }
- return constant.Compare(v1, token.EQL, v2)
-}
-
-func isConcreteConvIface(n ir.Node) bool {
- sv := ir.StaticValue(n)
- if sv.Op() != ir.OCONVIFACE {
- return false
- }
- return !sv.(*ir.ConvExpr).X.Type().IsInterface()
-}
-
-func isSameFuncName(v1, v2 *ir.Name) bool {
- // NB: there are a few corner cases where pointer equality
- // doesn't work here, but this should be good enough for
- // our purposes here.
- return v1 == v2
-}
diff --git a/src/cmd/compile/internal/inline/inlheur/names.go b/src/cmd/compile/internal/inline/inlheur/names.go
new file mode 100644
index 0000000000..022385087b
--- /dev/null
+++ b/src/cmd/compile/internal/inline/inlheur/names.go
@@ -0,0 +1,129 @@
+// 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.
+
+package inlheur
+
+import (
+ "cmd/compile/internal/ir"
+ "go/constant"
+)
+
+// nameFinder provides a set of "isXXX" query methods for clients to
+// ask whether a given AST node corresponds to a function, a constant
+// value, and so on. These methods use an underlying ir.ReassignOracle
+// to return more precise results in cases where an "interesting"
+// value is assigned to a singly-defined local temp. Example:
+//
+// const q = 101
+// fq := func() int { return q }
+// copyOfConstant := q
+// copyOfFunc := f
+// interestingCall(copyOfConstant, copyOfFunc)
+//
+// A name finder query method invoked on the arguments being passed to
+// "interestingCall" will be able detect that 'copyOfConstant' always
+// evaluates to a constant (even though it is in fact a PAUTO local
+// variable). A given nameFinder can also operate without using
+// ir.ReassignOracle (in cases where it is not practical to look
+// at the entire function); in such cases queries will still work
+// for explicit constant values and functions.
+type nameFinder struct {
+ ro *ir.ReassignOracle
+}
+
+// newNameFinder returns a new nameFinder object with a reassignment
+// oracle initialized based on the function fn, or if fn is nil,
+// without an underlying ReassignOracle.
+func newNameFinder(fn *ir.Func) *nameFinder {
+ var ro *ir.ReassignOracle
+ if fn != nil {
+ ro = &ir.ReassignOracle{}
+ ro.Init(fn)
+ }
+ return &nameFinder{ro: ro}
+}
+
+// funcName returns the *ir.Name for the func or method
+// corresponding to node 'n', or nil if n can't be proven
+// to contain a function value.
+func (nf *nameFinder) funcName(n ir.Node) *ir.Name {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ if name := ir.StaticCalleeName(sv); name != nil {
+ return name
+ }
+ return nil
+}
+
+// isAllocatedMem returns true if node n corresponds to a memory
+// allocation expression (make, new, or equivalent).
+func (nf *nameFinder) isAllocatedMem(n ir.Node) bool {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ switch sv.Op() {
+ case ir.OMAKESLICE, ir.ONEW, ir.OPTRLIT, ir.OSLICELIT:
+ return true
+ }
+ return false
+}
+
+// constValue returns the underlying constant.Value for an AST node n
+// if n is itself a constant value/expr, or if n is a singly assigned
+// local containing constant expr/value (or nil not constant).
+func (nf *nameFinder) constValue(n ir.Node) constant.Value {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ if sv.Op() == ir.OLITERAL {
+ return sv.Val()
+ }
+ return nil
+}
+
+// isNil returns whether n is nil (or singly
+// assigned local containing nil).
+func (nf *nameFinder) isNil(n ir.Node) bool {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ return sv.Op() == ir.ONIL
+}
+
+func (nf *nameFinder) staticValue(n ir.Node) ir.Node {
+ if nf.ro == nil {
+ return n
+ }
+ return nf.ro.StaticValue(n)
+}
+
+func (nf *nameFinder) reassigned(n *ir.Name) bool {
+ if nf.ro == nil {
+ return true
+ }
+ return nf.ro.Reassigned(n)
+}
+
+func (nf *nameFinder) isConcreteConvIface(n ir.Node) bool {
+ sv := n
+ if nf.ro != nil {
+ sv = nf.ro.StaticValue(n)
+ }
+ if sv.Op() != ir.OCONVIFACE {
+ return false
+ }
+ return !sv.(*ir.ConvExpr).X.Type().IsInterface()
+}
+
+func isSameFuncName(v1, v2 *ir.Name) bool {
+ // NB: there are a few corner cases where pointer equality
+ // doesn't work here, but this should be good enough for
+ // our purposes here.
+ return v1 == v2
+}
diff --git a/src/cmd/compile/internal/inline/inlheur/score_callresult_uses.go b/src/cmd/compile/internal/inline/inlheur/score_callresult_uses.go
index 1d31f09ac0..b95ea37d59 100644
--- a/src/cmd/compile/internal/inline/inlheur/score_callresult_uses.go
+++ b/src/cmd/compile/internal/inline/inlheur/score_callresult_uses.go
@@ -46,7 +46,7 @@ type resultUseAnalyzer struct {
// rescoreBasedOnCallResultUses examines how call results are used,
// and tries to update the scores of calls based on how their results
// are used in the function.
-func rescoreBasedOnCallResultUses(fn *ir.Func, resultNameTab map[*ir.Name]resultPropAndCS, cstab CallSiteTab) {
+func (csa *callSiteAnalyzer) rescoreBasedOnCallResultUses(fn *ir.Func, resultNameTab map[*ir.Name]resultPropAndCS, cstab CallSiteTab) {
enableDebugTraceIfEnv()
rua := &resultUseAnalyzer{
resultNameTab: resultNameTab,
@@ -65,7 +65,7 @@ func rescoreBasedOnCallResultUses(fn *ir.Func, resultNameTab map[*ir.Name]result
disableDebugTrace()
}
-func examineCallResults(cs *CallSite, resultNameTab map[*ir.Name]resultPropAndCS) map[*ir.Name]resultPropAndCS {
+func (csa *callSiteAnalyzer) examineCallResults(cs *CallSite, resultNameTab map[*ir.Name]resultPropAndCS) map[*ir.Name]resultPropAndCS {
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= examining call results for %q\n",
EncodeCallSiteKey(cs))
@@ -103,7 +103,7 @@ func examineCallResults(cs *CallSite, resultNameTab map[*ir.Name]resultPropAndCS
if rprop&interesting == 0 {
continue
}
- if ir.Reassigned(n) {
+ if csa.nameFinder.reassigned(n) {
continue
}
if resultNameTab == nil {
diff --git a/src/cmd/compile/internal/inline/inlheur/scoring.go b/src/cmd/compile/internal/inline/inlheur/scoring.go
index 2b210fce8e..623ba8adf0 100644
--- a/src/cmd/compile/internal/inline/inlheur/scoring.go
+++ b/src/cmd/compile/internal/inline/inlheur/scoring.go
@@ -182,13 +182,14 @@ func mustToMay(x scoreAdjustTyp) scoreAdjustTyp {
return 0
}
-// computeCallSiteScore takes a given call site whose ir node is 'call' and
-// callee function is 'callee' and with previously computed call site
-// properties 'csflags', then computes a score for the callsite that
-// combines the size cost of the callee with heuristics based on
-// previously parameter and function properties, then stores the score
-// and the adjustment mask in the appropriate fields in 'cs'
-func (cs *CallSite) computeCallSiteScore(calleeProps *FuncProps) {
+// computeCallSiteScore takes a given call site whose ir node is
+// 'call' and callee function is 'callee' and with previously computed
+// call site properties 'csflags', then computes a score for the
+// callsite that combines the size cost of the callee with heuristics
+// based on previously computed argument and function properties,
+// then stores the score and the adjustment mask in the appropriate
+// fields in 'cs'
+func (cs *CallSite) computeCallSiteScore(csa *callSiteAnalyzer, calleeProps *FuncProps) {
callee := cs.Callee
csflags := cs.Flags
call := cs.Call
@@ -353,7 +354,7 @@ func setupFlagToAdjMaps() {
}
}
-// largestScoreAdjustment tries to estimate the largest possible
+// LargestNegativeScoreAdjustment tries to estimate the largest possible
// negative score adjustment that could be applied to a call of the
// function with the specified props. Example:
//
@@ -372,7 +373,7 @@ func setupFlagToAdjMaps() {
// given call _could_ be rescored down as much as -35 points-- thus if
// the size of "bar" is 100 (for example) then there is at least a
// chance that scoring will enable inlining.
-func largestScoreAdjustment(fn *ir.Func, props *FuncProps) int {
+func LargestNegativeScoreAdjustment(fn *ir.Func, props *FuncProps) int {
if resultFlagToPositiveAdj == nil {
setupFlagToAdjMaps()
}
@@ -397,6 +398,14 @@ func largestScoreAdjustment(fn *ir.Func, props *FuncProps) int {
return score
}
+// LargestPositiveScoreAdjustment tries to estimate the largest possible
+// positive score adjustment that could be applied to a given callsite.
+// At the moment we don't have very many positive score adjustments, so
+// this is just hard-coded, not table-driven.
+func LargestPositiveScoreAdjustment(fn *ir.Func) int {
+ return adjValues[panicPathAdj] + adjValues[initFuncAdj]
+}
+
// callSiteTab contains entries for each call in the function
// currently being processed by InlineCalls; this variable will either
// be set to 'cstabCache' below (for non-inlinable routines) or to the
@@ -438,8 +447,13 @@ type scoreCallsCacheType struct {
// after foo has been analyzed, but it's conceivable that CanInline
// might visit bar before foo for this SCC.
func ScoreCalls(fn *ir.Func) {
+ if len(fn.Body) == 0 {
+ return
+ }
enableDebugTraceIfEnv()
+ nameFinder := newNameFinder(fn)
+
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= ScoreCalls(%v)\n", ir.FuncName(fn))
}
@@ -461,21 +475,25 @@ func ScoreCalls(fn *ir.Func) {
fmt.Fprintf(os.Stderr, "=-= building cstab for non-inl func %s\n",
ir.FuncName(fn))
}
- cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0)
+ cstab = computeCallSiteTable(fn, fn.Body, scoreCallsCache.tab, nil, 0,
+ nameFinder)
}
+ csa := makeCallSiteAnalyzer(fn)
const doCallResults = true
- scoreCallsRegion(fn, fn.Body, cstab, doCallResults, nil)
+ csa.scoreCallsRegion(fn, fn.Body, cstab, doCallResults, nil)
+
+ disableDebugTrace()
}
// scoreCallsRegion assigns numeric scores to each of the callsites in
// region 'region' within function 'fn'. This can be called on
// an entire function, or with 'region' set to a chunk of
// code corresponding to an inlined call.
-func scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallResults bool, ic *ir.InlinedCallExpr) {
+func (csa *callSiteAnalyzer) scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallResults bool, ic *ir.InlinedCallExpr) {
if debugTrace&debugTraceScoring != 0 {
- fmt.Fprintf(os.Stderr, "=-= scoreCallsRegion(%v, %s)\n",
- ir.FuncName(fn), region[0].Op().String())
+ fmt.Fprintf(os.Stderr, "=-= scoreCallsRegion(%v, %s) len(cstab)=%d\n",
+ ir.FuncName(fn), region[0].Op().String(), len(cstab))
}
// Sort callsites to avoid any surprises with non deterministic
@@ -510,13 +528,13 @@ func scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallRes
continue
}
}
- cs.computeCallSiteScore(cprops)
+ cs.computeCallSiteScore(csa, cprops)
if doCallResults {
if debugTrace&debugTraceScoring != 0 {
fmt.Fprintf(os.Stderr, "=-= examineCallResults at %s: flags=%d score=%d funcInlHeur=%v deser=%v\n", fmtFullPos(cs.Call.Pos()), cs.Flags, cs.Score, fihcprops, desercprops)
}
- resultNameTab = examineCallResults(cs, resultNameTab)
+ resultNameTab = csa.examineCallResults(cs, resultNameTab)
}
if debugTrace&debugTraceScoring != 0 {
@@ -525,7 +543,7 @@ func scoreCallsRegion(fn *ir.Func, region ir.Nodes, cstab CallSiteTab, doCallRes
}
if resultNameTab != nil {
- rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
+ csa.rescoreBasedOnCallResultUses(fn, resultNameTab, cstab)
}
disableDebugTrace()
diff --git a/src/cmd/compile/internal/inline/interleaved/interleaved.go b/src/cmd/compile/internal/inline/interleaved/interleaved.go
new file mode 100644
index 0000000000..a6f19d470d
--- /dev/null
+++ b/src/cmd/compile/internal/inline/interleaved/interleaved.go
@@ -0,0 +1,132 @@
+// 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.
+
+// Package interleaved implements the interleaved devirtualization and
+// inlining pass.
+package interleaved
+
+import (
+ "cmd/compile/internal/base"
+ "cmd/compile/internal/devirtualize"
+ "cmd/compile/internal/inline"
+ "cmd/compile/internal/inline/inlheur"
+ "cmd/compile/internal/ir"
+ "cmd/compile/internal/pgo"
+ "cmd/compile/internal/typecheck"
+ "fmt"
+)
+
+// DevirtualizeAndInlinePackage interleaves devirtualization and inlining on
+// all functions within pkg.
+func DevirtualizeAndInlinePackage(pkg *ir.Package, profile *pgo.Profile) {
+ if profile != nil && base.Debug.PGODevirtualize > 0 {
+ // TODO(mdempsky): Integrate into DevirtualizeAndInlineFunc below.
+ ir.VisitFuncsBottomUp(typecheck.Target.Funcs, func(list []*ir.Func, recursive bool) {
+ for _, fn := range list {
+ devirtualize.ProfileGuided(fn, profile)
+ }
+ })
+ ir.CurFunc = nil
+ }
+
+ if base.Flag.LowerL != 0 {
+ inlheur.SetupScoreAdjustments()
+ }
+
+ var inlProfile *pgo.Profile // copy of profile for inlining
+ if base.Debug.PGOInline != 0 {
+ inlProfile = profile
+ }
+ if inlProfile != nil {
+ inline.PGOInlinePrologue(inlProfile, pkg.Funcs)
+ }
+
+ 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
+ // inlining. See #59404 and #59638 for more context.
+ inline.GarbageCollectUnreferencedHiddenClosures()
+
+ if base.Debug.DumpInlFuncProps != "" {
+ inlheur.DumpFuncProps(nil, base.Debug.DumpInlFuncProps)
+ }
+ if inlheur.Enabled() {
+ inline.PostProcessCallSites(inlProfile)
+ inlheur.TearDown()
+ }
+ }
+}
+
+// DevirtualizeAndInlineFunc interleaves devirtualization and inlining
+// on a single function.
+func DevirtualizeAndInlineFunc(fn *ir.Func, profile *pgo.Profile) {
+ ir.WithFunc(fn, func() {
+ if base.Flag.LowerL != 0 {
+ if inlheur.Enabled() && !fn.Wrapper() {
+ inlheur.ScoreCalls(fn)
+ defer inlheur.ScoreCallsCleanup()
+ }
+ if base.Debug.DumpInlFuncProps != "" && !fn.Wrapper() {
+ inlheur.DumpFuncProps(fn, base.Debug.DumpInlFuncProps)
+ }
+ }
+
+ bigCaller := base.Flag.LowerL != 0 && inline.IsBigFunc(fn)
+ if bigCaller && base.Flag.LowerM > 1 {
+ fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn)
+ }
+
+ // Walk fn's body and apply devirtualization and inlining.
+ var inlCalls []*ir.InlinedCallExpr
+ var edit func(ir.Node) ir.Node
+ edit = func(n ir.Node) ir.Node {
+ switch n := n.(type) {
+ case *ir.TailCallStmt:
+ n.Call.NoInline = true // can't inline yet
+ }
+
+ ir.EditChildren(n, edit)
+
+ if call, ok := n.(*ir.CallExpr); ok {
+ devirtualize.StaticCall(call)
+
+ if inlCall := inline.TryInlineCall(fn, call, bigCaller, profile); inlCall != nil {
+ inlCalls = append(inlCalls, inlCall)
+ n = inlCall
+ }
+ }
+
+ return n
+ }
+ ir.EditChildren(fn, edit)
+
+ // If we inlined any calls, we want to recursively visit their
+ // bodies for further devirtualization and inlining. However, we
+ // need to wait until *after* the original function body has been
+ // expanded, or else inlCallee can have false positives (e.g.,
+ // #54632).
+ for len(inlCalls) > 0 {
+ call := inlCalls[0]
+ inlCalls = inlCalls[1:]
+ ir.EditChildren(call, edit)
+ }
+ })
+}
diff --git a/src/cmd/compile/internal/ir/check_reassign_no.go b/src/cmd/compile/internal/ir/check_reassign_no.go
new file mode 100644
index 0000000000..8290a7da7e
--- /dev/null
+++ b/src/cmd/compile/internal/ir/check_reassign_no.go
@@ -0,0 +1,9 @@
+// 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.
+
+//go:build !checknewoldreassignment
+
+package ir
+
+const consistencyCheckEnabled = false
diff --git a/src/cmd/compile/internal/ir/check_reassign_yes.go b/src/cmd/compile/internal/ir/check_reassign_yes.go
new file mode 100644
index 0000000000..30876cca20
--- /dev/null
+++ b/src/cmd/compile/internal/ir/check_reassign_yes.go
@@ -0,0 +1,9 @@
+// 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.
+
+//go:build checknewoldreassignment
+
+package ir
+
+const consistencyCheckEnabled = true
diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go
index ca2a2d5008..da5b437f99 100644
--- a/src/cmd/compile/internal/ir/expr.go
+++ b/src/cmd/compile/internal/ir/expr.go
@@ -190,7 +190,8 @@ type CallExpr struct {
RType Node `mknode:"-"` // see reflectdata/helpers.go
KeepAlive []*Name // vars to be kept alive until call returns
IsDDD bool
- NoInline bool
+ GoDefer bool // whether this call is part of a go or defer statement
+ NoInline bool // whether this call must not be inlined
}
func NewCallExpr(pos src.XPos, op Op, fun Node, args []Node) *CallExpr {
@@ -349,7 +350,7 @@ func NewKeyExpr(pos src.XPos, key, value Node) *KeyExpr {
return n
}
-// A StructKeyExpr is an Field: Value composite literal key.
+// A StructKeyExpr is a Field: Value composite literal key.
type StructKeyExpr struct {
miniExpr
Field *types.Field
@@ -922,6 +923,8 @@ FindRHS:
// NB: global variables are always considered to be re-assigned.
// TODO: handle initial declaration not including an assignment and
// followed by a single assignment?
+// NOTE: any changes made here should also be made in the corresponding
+// code in the ReassignOracle.Init method.
func Reassigned(name *Name) bool {
if name.Op() != ONAME {
base.Fatalf("reassigned %v", name)
diff --git a/src/cmd/compile/internal/ir/reassign_consistency_check.go b/src/cmd/compile/internal/ir/reassign_consistency_check.go
new file mode 100644
index 0000000000..e4d928d132
--- /dev/null
+++ b/src/cmd/compile/internal/ir/reassign_consistency_check.go
@@ -0,0 +1,46 @@
+// 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.
+
+package ir
+
+import (
+ "cmd/compile/internal/base"
+ "cmd/internal/src"
+ "fmt"
+ "path/filepath"
+ "strings"
+)
+
+// checkStaticValueResult compares the result from ReassignOracle.StaticValue
+// with the corresponding result from ir.StaticValue to make sure they agree.
+// This method is called only when turned on via build tag.
+func checkStaticValueResult(n Node, newres Node) {
+ oldres := StaticValue(n)
+ if oldres != newres {
+ base.Fatalf("%s: new/old static value disagreement on %v:\nnew=%v\nold=%v", fmtFullPos(n.Pos()), n, newres, oldres)
+ }
+}
+
+// checkStaticValueResult 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) {
+ origres := Reassigned(n)
+ if newres != origres {
+ base.Fatalf("%s: new/old reassigned disagreement on %v (class %s) newres=%v oldres=%v", fmtFullPos(n.Pos()), n, n.Class.String(), newres, origres)
+ }
+}
+
+// fmtFullPos returns a verbose dump for pos p, including inlines.
+func fmtFullPos(p src.XPos) string {
+ var sb strings.Builder
+ sep := ""
+ base.Ctxt.AllPos(p, func(pos src.Pos) {
+ fmt.Fprintf(&sb, sep)
+ sep = "|"
+ file := filepath.Base(pos.Filename())
+ fmt.Fprintf(&sb, "%s:%d:%d", file, pos.Line(), pos.Col())
+ })
+ return sb.String()
+}
diff --git a/src/cmd/compile/internal/ir/reassignment.go b/src/cmd/compile/internal/ir/reassignment.go
new file mode 100644
index 0000000000..9974292471
--- /dev/null
+++ b/src/cmd/compile/internal/ir/reassignment.go
@@ -0,0 +1,205 @@
+// 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.
+
+package ir
+
+import (
+ "cmd/compile/internal/base"
+)
+
+// A ReassignOracle efficiently answers queries about whether local
+// variables are reassigned. This helper works by looking for function
+// params and short variable declarations (e.g.
+// https://go.dev/ref/spec#Short_variable_declarations) that are
+// neither address taken nor subsequently re-assigned. It is intended
+// to operate much like "ir.StaticValue" and "ir.Reassigned", but in a
+// way that does just a single walk of the containing function (as
+// opposed to a new walk on every call).
+type ReassignOracle struct {
+ fn *Func
+ // maps candidate name to its defining assignment (or for
+ // for params, defining func).
+ singleDef map[*Name]Node
+}
+
+// Init initializes the oracle based on the IR in function fn, laying
+// the groundwork for future calls to the StaticValue and Reassigned
+// methods. If the fn's IR is subsequently modified, Init must be
+// called again.
+func (ro *ReassignOracle) Init(fn *Func) {
+ ro.fn = fn
+
+ // Collect candidate map. Start by adding function parameters
+ // explicitly.
+ ro.singleDef = make(map[*Name]Node)
+ sig := fn.Type()
+ numParams := sig.NumRecvs() + sig.NumParams()
+ for _, param := range fn.Dcl[:numParams] {
+ if IsBlank(param) {
+ continue
+ }
+ // For params, use func itself as defining node.
+ ro.singleDef[param] = fn
+ }
+
+ // Walk the function body to discover any locals assigned
+ // via ":=" syntax (e.g. "a :=