1 // Copyright 2017 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
19 // TestIntendedInlining tests that specific functions are inlined.
20 // This allows refactoring for code clarity and re-use without fear that
21 // changes to the compiler will cause silent performance regressions.
22 func TestIntendedInlining(t *testing.T) {
23 if testing.Short() && testenv.Builder() == "" {
24 t.Skip("skipping in short mode")
26 testenv.MustHaveGoRun(t)
29 // want is the list of function names (by package) that should
30 // be inlinable. If they have no callers in their packages, they
31 // might not actually be inlined anywhere.
32 want := map[string][]string{
77 "heapBits.morePointers",
86 "(*gcWork).tryGetFast",
88 "(*markBits).advance",
89 "(*mspan).allocBitsForIndex",
91 "(*mspan).markBitsForBase",
92 "(*mspan).markBitsForIndex",
96 "runtime/internal/sys": {},
97 "runtime/internal/math": {
107 "(*Buffer).ReadByte",
110 "(*Buffer).UnreadByte",
111 "(*Buffer).tryGrowByReslice",
117 "(*dictDecoder).tryWriteCopy",
137 "Value.CanInterface",
150 "Value.OverflowComplex",
151 "Value.OverflowFloat",
153 "Value.OverflowUint",
162 "flag.mustBeAssignable",
163 "flag.mustBeExported",
172 // The following functions require the math_big_pure_go build tag.
177 "(*rngSource).Int63",
178 "(*rngSource).Uint64",
181 "(*UDPConn).ReadFromUDP",
184 // (*Bool).CompareAndSwap handled below.
189 "(*Int32).CompareAndSwap",
194 "(*Int64).CompareAndSwap",
199 "(*Uint32).CompareAndSwap",
204 "(*Uint64).CompareAndSwap",
209 "(*Uintptr).CompareAndSwap",
213 // TODO(rsc): Why are these not reported as inlined?
214 // "(*Pointer[T]).CompareAndSwap",
215 // "(*Pointer[T]).Load",
216 // "(*Pointer[T]).Store",
217 // "(*Pointer[T]).Swap",
221 if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
222 // nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
223 // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
224 // On mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive
225 // to inline (Issue 22239).
226 want["runtime"] = append(want["runtime"], "nextFreeFast")
228 if runtime.GOARCH != "386" {
229 // As explained above, Ctz64 and Ctz32 are not Go code on 386.
230 // The same applies to Bswap32.
231 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
232 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
233 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
235 if bits.UintSize == 64 {
236 // mix is only defined on 64-bit architectures
237 want["runtime"] = append(want["runtime"], "mix")
238 // (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
239 want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
242 switch runtime.GOARCH {
243 case "386", "wasm", "arm":
245 // TODO(mvdan): As explained in /test/inline_sync.go, some
246 // architectures don't have atomic intrinsics, so these go over
247 // the inlining budget. Move back to the main table once that
248 // problem is solved.
249 want["sync"] = []string{
253 "(*RWMutex).RUnlock",
258 // Functions that must actually be inlined; they must have actual callers.
259 must := map[string]bool{
260 "compress/flate.byLiteral.Len": true,
261 "compress/flate.byLiteral.Less": true,
262 "compress/flate.byLiteral.Swap": true,
265 notInlinedReason := make(map[string]string)
266 pkgs := make([]string, 0, len(want))
267 for pname, fnames := range want {
268 pkgs = append(pkgs, pname)
269 for _, fname := range fnames {
270 fullName := pname + "." + fname
271 if _, ok := notInlinedReason[fullName]; ok {
272 t.Errorf("duplicate func: %s", fullName)
274 notInlinedReason[fullName] = "unknown reason"
278 args := append([]string{"build", "-a", "-gcflags=all=-m -m", "-tags=math_big_pure_go"}, pkgs...)
279 cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
283 cmdErr := make(chan error, 1)
288 scanner := bufio.NewScanner(pr)
290 canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
291 haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
292 cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
294 line := scanner.Text()
295 if strings.HasPrefix(line, "# ") {
299 if m := haveInlined.FindStringSubmatch(line); m != nil {
301 delete(notInlinedReason, curPkg+"."+fname)
304 if m := canInline.FindStringSubmatch(line); m != nil {
306 fullname := curPkg + "." + fname
307 // If function must be inlined somewhere, being inlinable is not enough
308 if _, ok := must[fullname]; !ok {
309 delete(notInlinedReason, fullname)
313 if m := cannotInline.FindStringSubmatch(line); m != nil {
314 fname, reason := m[1], m[2]
315 fullName := curPkg + "." + fname
316 if _, ok := notInlinedReason[fullName]; ok {
317 // cmd/compile gave us a reason why
318 notInlinedReason[fullName] = reason
323 if err := <-cmdErr; err != nil {
326 if err := scanner.Err(); err != nil {
329 for fullName, reason := range notInlinedReason {
330 t.Errorf("%s was not inlined: %s", fullName, reason)