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