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