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