]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/test/inl_test.go
cmd/compile/internal/inline: score call sites exposed by inlines
[gostls13.git] / src / cmd / compile / internal / test / inl_test.go
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.
4
5 package test
6
7 import (
8         "bufio"
9         "internal/goexperiment"
10         "internal/testenv"
11         "io"
12         "math/bits"
13         "regexp"
14         "runtime"
15         "strings"
16         "testing"
17 )
18
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")
25         }
26         testenv.MustHaveGoRun(t)
27         t.Parallel()
28
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{
33                 "runtime": {
34                         "add",
35                         "acquirem",
36                         "add1",
37                         "addb",
38                         "adjustpanics",
39                         "adjustpointer",
40                         "alignDown",
41                         "alignUp",
42                         "bucketMask",
43                         "bucketShift",
44                         "chanbuf",
45                         "evacuated",
46                         "fastlog2",
47                         "fastrand",
48                         "float64bits",
49                         "funcspdelta",
50                         "getm",
51                         "getMCache",
52                         "isDirectIface",
53                         "itabHashFunc",
54                         "nextslicecap",
55                         "noescape",
56                         "pcvalueCacheKey",
57                         "readUnaligned32",
58                         "readUnaligned64",
59                         "releasem",
60                         "roundupsize",
61                         "stackmapdata",
62                         "stringStructOf",
63                         "subtract1",
64                         "subtractb",
65                         "tophash",
66                         "(*bmap).keys",
67                         "(*bmap).overflow",
68                         "(*waitq).enqueue",
69                         "funcInfo.entry",
70
71                         // GC-related ones
72                         "cgoInRange",
73                         "gclinkptr.ptr",
74                         "guintptr.ptr",
75                         "writeHeapBitsForAddr",
76                         "heapBitsSlice",
77                         "markBits.isMarked",
78                         "muintptr.ptr",
79                         "puintptr.ptr",
80                         "spanOf",
81                         "spanOfUnchecked",
82                         "typePointers.nextFast",
83                         "(*gcWork).putFast",
84                         "(*gcWork).tryGetFast",
85                         "(*guintptr).set",
86                         "(*markBits).advance",
87                         "(*mspan).allocBitsForIndex",
88                         "(*mspan).base",
89                         "(*mspan).markBitsForBase",
90                         "(*mspan).markBitsForIndex",
91                         "(*mspan).writeUserArenaHeapBits",
92                         "(*muintptr).set",
93                         "(*puintptr).set",
94                         "(*wbBuf).get1",
95                         "(*wbBuf).get2",
96
97                         // Trace-related ones.
98                         "traceLocker.ok",
99                         "traceEnabled",
100                 },
101                 "runtime/internal/sys": {},
102                 "runtime/internal/math": {
103                         "MulUintptr",
104                 },
105                 "bytes": {
106                         "(*Buffer).Bytes",
107                         "(*Buffer).Cap",
108                         "(*Buffer).Len",
109                         "(*Buffer).Grow",
110                         "(*Buffer).Next",
111                         "(*Buffer).Read",
112                         "(*Buffer).ReadByte",
113                         "(*Buffer).Reset",
114                         "(*Buffer).String",
115                         "(*Buffer).UnreadByte",
116                         "(*Buffer).tryGrowByReslice",
117                 },
118                 "internal/abi": {
119                         "UseInterfaceSwitchCache",
120                 },
121                 "compress/flate": {
122                         "byLiteral.Len",
123                         "byLiteral.Less",
124                         "byLiteral.Swap",
125                         "(*dictDecoder).tryWriteCopy",
126                 },
127                 "encoding/base64": {
128                         "assemble32",
129                         "assemble64",
130                 },
131                 "unicode/utf8": {
132                         "FullRune",
133                         "FullRuneInString",
134                         "RuneLen",
135                         "AppendRune",
136                         "ValidRune",
137                 },
138                 "unicode/utf16": {
139                         "Decode",
140                 },
141                 "reflect": {
142                         "Value.Bool",
143                         "Value.Bytes",
144                         "Value.CanAddr",
145                         "Value.CanComplex",
146                         "Value.CanFloat",
147                         "Value.CanInt",
148                         "Value.CanInterface",
149                         "Value.CanSet",
150                         "Value.CanUint",
151                         "Value.Cap",
152                         "Value.Complex",
153                         "Value.Float",
154                         "Value.Int",
155                         "Value.Interface",
156                         "Value.IsNil",
157                         "Value.IsValid",
158                         "Value.Kind",
159                         "Value.Len",
160                         "Value.MapRange",
161                         "Value.OverflowComplex",
162                         "Value.OverflowFloat",
163                         "Value.OverflowInt",
164                         "Value.OverflowUint",
165                         "Value.String",
166                         "Value.Type",
167                         "Value.Uint",
168                         "Value.UnsafeAddr",
169                         "Value.pointer",
170                         "add",
171                         "align",
172                         "flag.mustBe",
173                         "flag.mustBeAssignable",
174                         "flag.mustBeExported",
175                         "flag.kind",
176                         "flag.ro",
177                 },
178                 "regexp": {
179                         "(*bitState).push",
180                 },
181                 "math/big": {
182                         "bigEndianWord",
183                         // The following functions require the math_big_pure_go build tag.
184                         "addVW",
185                         "subVW",
186                 },
187                 "math/rand": {
188                         "(*rngSource).Int63",
189                         "(*rngSource).Uint64",
190                 },
191                 "net": {
192                         "(*UDPConn).ReadFromUDP",
193                 },
194                 "sync": {
195                         // Both OnceFunc and its returned closure need to be inlinable so
196                         // that the returned closure can be inlined into the caller of OnceFunc.
197                         "OnceFunc",
198                         "OnceFunc.func2", // The returned closure.
199                         // TODO(austin): It would be good to check OnceValue and OnceValues,
200                         // too, but currently they aren't reported because they have type
201                         // parameters and aren't instantiated in sync.
202                 },
203                 "sync/atomic": {
204                         // (*Bool).CompareAndSwap handled below.
205                         "(*Bool).Load",
206                         "(*Bool).Store",
207                         "(*Bool).Swap",
208                         "(*Int32).Add",
209                         "(*Int32).CompareAndSwap",
210                         "(*Int32).Load",
211                         "(*Int32).Store",
212                         "(*Int32).Swap",
213                         "(*Int64).Add",
214                         "(*Int64).CompareAndSwap",
215                         "(*Int64).Load",
216                         "(*Int64).Store",
217                         "(*Int64).Swap",
218                         "(*Uint32).Add",
219                         "(*Uint32).CompareAndSwap",
220                         "(*Uint32).Load",
221                         "(*Uint32).Store",
222                         "(*Uint32).Swap",
223                         "(*Uint64).Add",
224                         "(*Uint64).CompareAndSwap",
225                         "(*Uint64).Load",
226                         "(*Uint64).Store",
227                         "(*Uint64).Swap",
228                         "(*Uintptr).Add",
229                         "(*Uintptr).CompareAndSwap",
230                         "(*Uintptr).Load",
231                         "(*Uintptr).Store",
232                         "(*Uintptr).Swap",
233                         "(*Pointer[go.shape.int]).CompareAndSwap",
234                         "(*Pointer[go.shape.int]).Load",
235                         "(*Pointer[go.shape.int]).Store",
236                         "(*Pointer[go.shape.int]).Swap",
237                 },
238         }
239
240         if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
241                 // nextFreeFast calls sys.TrailingZeros64, which on 386 is implemented in asm and is not inlinable.
242                 // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
243                 // On loong64, mips64x and riscv64, TrailingZeros64 is not intrinsified and causes nextFreeFast
244                 // too expensive to inline (Issue 22239).
245                 want["runtime"] = append(want["runtime"], "nextFreeFast")
246                 // Same behavior for heapBits.nextFast.
247                 want["runtime"] = append(want["runtime"], "heapBits.nextFast")
248         }
249         if runtime.GOARCH != "386" {
250                 // As explained above, TrailingZeros64 and TrailingZeros32 are not Go code on 386.
251                 // The same applies to Bswap32.
252                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros64")
253                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros32")
254                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
255         }
256         if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "mips" || runtime.GOARCH == "mips64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
257                 // runtime/internal/atomic.Loaduintptr is only intrinsified on these platforms.
258                 want["runtime"] = append(want["runtime"], "traceAcquire")
259         }
260         if bits.UintSize == 64 {
261                 // mix is only defined on 64-bit architectures
262                 want["runtime"] = append(want["runtime"], "mix")
263                 // (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
264                 want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
265         }
266
267         switch runtime.GOARCH {
268         case "386", "wasm", "arm":
269         default:
270                 // TODO(mvdan): As explained in /test/inline_sync.go, some
271                 // architectures don't have atomic intrinsics, so these go over
272                 // the inlining budget. Move back to the main table once that
273                 // problem is solved.
274                 want["sync"] = []string{
275                         "(*Mutex).Lock",
276                         "(*Mutex).Unlock",
277                         "(*RWMutex).RLock",
278                         "(*RWMutex).RUnlock",
279                         "(*Once).Do",
280                 }
281         }
282
283         // Functions that must actually be inlined; they must have actual callers.
284         must := map[string]bool{
285                 "compress/flate.byLiteral.Len":  true,
286                 "compress/flate.byLiteral.Less": true,
287                 "compress/flate.byLiteral.Swap": true,
288         }
289
290         notInlinedReason := make(map[string]string)
291         pkgs := make([]string, 0, len(want))
292         for pname, fnames := range want {
293                 pkgs = append(pkgs, pname)
294                 for _, fname := range fnames {
295                         fullName := pname + "." + fname
296                         if _, ok := notInlinedReason[fullName]; ok {
297                                 t.Errorf("duplicate func: %s", fullName)
298                         }
299                         notInlinedReason[fullName] = "unknown reason"
300                 }
301         }
302
303         args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
304         cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), args...))
305         pr, pw := io.Pipe()
306         cmd.Stdout = pw
307         cmd.Stderr = pw
308         cmdErr := make(chan error, 1)
309         go func() {
310                 cmdErr <- cmd.Run()
311                 pw.Close()
312         }()
313         scanner := bufio.NewScanner(pr)
314         curPkg := ""
315         canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
316         haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
317         cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
318         for scanner.Scan() {
319                 line := scanner.Text()
320                 if strings.HasPrefix(line, "# ") {
321                         curPkg = line[2:]
322                         continue
323                 }
324                 if m := haveInlined.FindStringSubmatch(line); m != nil {
325                         fname := m[1]
326                         delete(notInlinedReason, curPkg+"."+fname)
327                         continue
328                 }
329                 if m := canInline.FindStringSubmatch(line); m != nil {
330                         fname := m[1]
331                         fullname := curPkg + "." + fname
332                         // If function must be inlined somewhere, being inlinable is not enough
333                         if _, ok := must[fullname]; !ok {
334                                 delete(notInlinedReason, fullname)
335                                 continue
336                         }
337                 }
338                 if m := cannotInline.FindStringSubmatch(line); m != nil {
339                         fname, reason := m[1], m[2]
340                         fullName := curPkg + "." + fname
341                         if _, ok := notInlinedReason[fullName]; ok {
342                                 // cmd/compile gave us a reason why
343                                 notInlinedReason[fullName] = reason
344                         }
345                         continue
346                 }
347         }
348         if err := <-cmdErr; err != nil {
349                 t.Fatal(err)
350         }
351         if err := scanner.Err(); err != nil {
352                 t.Fatal(err)
353         }
354         for fullName, reason := range notInlinedReason {
355                 t.Errorf("%s was not inlined: %s", fullName, reason)
356         }
357 }
358
359 func collectInlCands(msgs string) map[string]struct{} {
360         rv := make(map[string]struct{})
361         lines := strings.Split(msgs, "\n")
362         re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
363         for _, line := range lines {
364                 m := re.FindStringSubmatch(line)
365                 if m != nil {
366                         rv[m[1]] = struct{}{}
367                 }
368         }
369         return rv
370 }
371
372 func TestIssue56044(t *testing.T) {
373         if testing.Short() {
374                 t.Skipf("skipping test: too long for short mode")
375         }
376         if !goexperiment.CoverageRedesign {
377                 t.Skipf("skipping new coverage tests (experiment not enabled)")
378         }
379
380         testenv.MustHaveGoBuild(t)
381
382         modes := []string{"-covermode=set", "-covermode=atomic"}
383
384         for _, mode := range modes {
385                 // Build the Go runtime with "-m", capturing output.
386                 args := []string{"build", "-gcflags=runtime=-m", "runtime"}
387                 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
388                 b, err := cmd.CombinedOutput()
389                 if err != nil {
390                         t.Fatalf("build failed (%v): %s", err, b)
391                 }
392                 mbase := collectInlCands(string(b))
393
394                 // Redo the build with -cover, also with "-m".
395                 args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
396                 cmd = testenv.Command(t, testenv.GoToolPath(t), args...)
397                 b, err = cmd.CombinedOutput()
398                 if err != nil {
399                         t.Fatalf("build failed (%v): %s", err, b)
400                 }
401                 mcov := collectInlCands(string(b))
402
403                 // Make sure that there aren't any functions that are marked
404                 // as inline candidates at base but not with coverage.
405                 for k := range mbase {
406                         if _, ok := mcov[k]; !ok {
407                                 t.Errorf("error: did not find %s in coverage -m output", k)
408                         }
409                 }
410         }
411 }