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