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