]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/test/inl_test.go
cmd/compile: tweak inliners handling of coverage counter updates
[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/buildcfg"
10         "internal/goexperiment"
11         "internal/testenv"
12         "io"
13         "math/bits"
14         "os/exec"
15         "regexp"
16         "runtime"
17         "strings"
18         "testing"
19 )
20
21 // TestIntendedInlining tests that specific functions are inlined.
22 // This allows refactoring for code clarity and re-use without fear that
23 // changes to the compiler will cause silent performance regressions.
24 func TestIntendedInlining(t *testing.T) {
25         if testing.Short() && testenv.Builder() == "" {
26                 t.Skip("skipping in short mode")
27         }
28         testenv.MustHaveGoRun(t)
29         t.Parallel()
30
31         // want is the list of function names (by package) that should
32         // be inlinable. If they have no callers in their packages, they
33         // might not actually be inlined anywhere.
34         want := map[string][]string{
35                 "runtime": {
36                         "add",
37                         "acquirem",
38                         "add1",
39                         "addb",
40                         "adjustpanics",
41                         "adjustpointer",
42                         "alignDown",
43                         "alignUp",
44                         "bucketMask",
45                         "bucketShift",
46                         "chanbuf",
47                         "evacuated",
48                         "fastlog2",
49                         "fastrand",
50                         "float64bits",
51                         "funcspdelta",
52                         "getm",
53                         "getMCache",
54                         "isDirectIface",
55                         "itabHashFunc",
56                         "noescape",
57                         "pcvalueCacheKey",
58                         "readUnaligned32",
59                         "readUnaligned64",
60                         "releasem",
61                         "roundupsize",
62                         "stackmapdata",
63                         "stringStructOf",
64                         "subtract1",
65                         "subtractb",
66                         "tophash",
67                         "(*bmap).keys",
68                         "(*bmap).overflow",
69                         "(*waitq).enqueue",
70                         "funcInfo.entry",
71
72                         // GC-related ones
73                         "cgoInRange",
74                         "gclinkptr.ptr",
75                         "guintptr.ptr",
76                         "writeHeapBitsForAddr",
77                         "markBits.isMarked",
78                         "muintptr.ptr",
79                         "puintptr.ptr",
80                         "spanOf",
81                         "spanOfUnchecked",
82                         "(*gcWork).putFast",
83                         "(*gcWork).tryGetFast",
84                         "(*guintptr).set",
85                         "(*markBits).advance",
86                         "(*mspan).allocBitsForIndex",
87                         "(*mspan).base",
88                         "(*mspan).markBitsForBase",
89                         "(*mspan).markBitsForIndex",
90                         "(*muintptr).set",
91                         "(*puintptr).set",
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                 "reflect": {
128                         "Value.Bool",
129                         "Value.Bytes",
130                         "Value.CanAddr",
131                         "Value.CanComplex",
132                         "Value.CanFloat",
133                         "Value.CanInt",
134                         "Value.CanInterface",
135                         "Value.CanSet",
136                         "Value.CanUint",
137                         "Value.Cap",
138                         "Value.Complex",
139                         "Value.Float",
140                         "Value.Int",
141                         "Value.Interface",
142                         "Value.IsNil",
143                         "Value.IsValid",
144                         "Value.Kind",
145                         "Value.Len",
146                         "Value.MapRange",
147                         "Value.OverflowComplex",
148                         "Value.OverflowFloat",
149                         "Value.OverflowInt",
150                         "Value.OverflowUint",
151                         "Value.String",
152                         "Value.Type",
153                         "Value.Uint",
154                         "Value.UnsafeAddr",
155                         "Value.pointer",
156                         "add",
157                         "align",
158                         "flag.mustBe",
159                         "flag.mustBeAssignable",
160                         "flag.mustBeExported",
161                         "flag.kind",
162                         "flag.ro",
163                 },
164                 "regexp": {
165                         "(*bitState).push",
166                 },
167                 "math/big": {
168                         "bigEndianWord",
169                         // The following functions require the math_big_pure_go build tag.
170                         "addVW",
171                         "subVW",
172                 },
173                 "math/rand": {
174                         "(*rngSource).Int63",
175                         "(*rngSource).Uint64",
176                 },
177                 "net": {
178                         "(*UDPConn).ReadFromUDP",
179                 },
180                 "sync/atomic": {
181                         // (*Bool).CompareAndSwap handled below.
182                         "(*Bool).Load",
183                         "(*Bool).Store",
184                         "(*Bool).Swap",
185                         "(*Int32).Add",
186                         "(*Int32).CompareAndSwap",
187                         "(*Int32).Load",
188                         "(*Int32).Store",
189                         "(*Int32).Swap",
190                         "(*Int64).Add",
191                         "(*Int64).CompareAndSwap",
192                         "(*Int64).Load",
193                         "(*Int64).Store",
194                         "(*Int64).Swap",
195                         "(*Uint32).Add",
196                         "(*Uint32).CompareAndSwap",
197                         "(*Uint32).Load",
198                         "(*Uint32).Store",
199                         "(*Uint32).Swap",
200                         "(*Uint64).Add",
201                         "(*Uint64).CompareAndSwap",
202                         "(*Uint64).Load",
203                         "(*Uint64).Store",
204                         "(*Uint64).Swap",
205                         "(*Uintptr).Add",
206                         "(*Uintptr).CompareAndSwap",
207                         "(*Uintptr).Load",
208                         "(*Uintptr).Store",
209                         "(*Uintptr).Swap",
210                         // (*Pointer[T])'s methods' handled below.
211                 },
212         }
213
214         if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
215                 // nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
216                 // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
217                 // On loong64, mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive
218                 // to inline (Issue 22239).
219                 want["runtime"] = append(want["runtime"], "nextFreeFast")
220                 // Same behavior for heapBits.nextFast.
221                 want["runtime"] = append(want["runtime"], "heapBits.nextFast")
222         }
223         if runtime.GOARCH != "386" {
224                 // As explained above, Ctz64 and Ctz32 are not Go code on 386.
225                 // The same applies to Bswap32.
226                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
227                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
228                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
229         }
230         if bits.UintSize == 64 {
231                 // mix is only defined on 64-bit architectures
232                 want["runtime"] = append(want["runtime"], "mix")
233                 // (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
234                 want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
235         }
236         if buildcfg.Experiment.Unified {
237                 // Non-unified IR does not report "inlining call ..." for atomic.Pointer[T]'s methods.
238                 // TODO(cuonglm): remove once non-unified IR frontend gone.
239                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).CompareAndSwap")
240                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).Load")
241                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).Store")
242                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).Swap")
243         }
244
245         switch runtime.GOARCH {
246         case "386", "wasm", "arm":
247         default:
248                 // TODO(mvdan): As explained in /test/inline_sync.go, some
249                 // architectures don't have atomic intrinsics, so these go over
250                 // the inlining budget. Move back to the main table once that
251                 // problem is solved.
252                 want["sync"] = []string{
253                         "(*Mutex).Lock",
254                         "(*Mutex).Unlock",
255                         "(*RWMutex).RLock",
256                         "(*RWMutex).RUnlock",
257                         "(*Once).Do",
258                 }
259         }
260
261         // Functions that must actually be inlined; they must have actual callers.
262         must := map[string]bool{
263                 "compress/flate.byLiteral.Len":  true,
264                 "compress/flate.byLiteral.Less": true,
265                 "compress/flate.byLiteral.Swap": true,
266         }
267
268         notInlinedReason := make(map[string]string)
269         pkgs := make([]string, 0, len(want))
270         for pname, fnames := range want {
271                 pkgs = append(pkgs, pname)
272                 for _, fname := range fnames {
273                         fullName := pname + "." + fname
274                         if _, ok := notInlinedReason[fullName]; ok {
275                                 t.Errorf("duplicate func: %s", fullName)
276                         }
277                         notInlinedReason[fullName] = "unknown reason"
278                 }
279         }
280
281         args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
282         cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
283         pr, pw := io.Pipe()
284         cmd.Stdout = pw
285         cmd.Stderr = pw
286         cmdErr := make(chan error, 1)
287         go func() {
288                 cmdErr <- cmd.Run()
289                 pw.Close()
290         }()
291         scanner := bufio.NewScanner(pr)
292         curPkg := ""
293         canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
294         haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
295         cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
296         for scanner.Scan() {
297                 line := scanner.Text()
298                 if strings.HasPrefix(line, "# ") {
299                         curPkg = line[2:]
300                         continue
301                 }
302                 if m := haveInlined.FindStringSubmatch(line); m != nil {
303                         fname := m[1]
304                         delete(notInlinedReason, curPkg+"."+fname)
305                         continue
306                 }
307                 if m := canInline.FindStringSubmatch(line); m != nil {
308                         fname := m[1]
309                         fullname := curPkg + "." + fname
310                         // If function must be inlined somewhere, being inlinable is not enough
311                         if _, ok := must[fullname]; !ok {
312                                 delete(notInlinedReason, fullname)
313                                 continue
314                         }
315                 }
316                 if m := cannotInline.FindStringSubmatch(line); m != nil {
317                         fname, reason := m[1], m[2]
318                         fullName := curPkg + "." + fname
319                         if _, ok := notInlinedReason[fullName]; ok {
320                                 // cmd/compile gave us a reason why
321                                 notInlinedReason[fullName] = reason
322                         }
323                         continue
324                 }
325         }
326         if err := <-cmdErr; err != nil {
327                 t.Fatal(err)
328         }
329         if err := scanner.Err(); err != nil {
330                 t.Fatal(err)
331         }
332         for fullName, reason := range notInlinedReason {
333                 t.Errorf("%s was not inlined: %s", fullName, reason)
334         }
335 }
336
337 func collectInlCands(msgs string) map[string]struct{} {
338         rv := make(map[string]struct{})
339         lines := strings.Split(msgs, "\n")
340         re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
341         for _, line := range lines {
342                 m := re.FindStringSubmatch(line)
343                 if m != nil {
344                         rv[m[1]] = struct{}{}
345                 }
346         }
347         return rv
348 }
349
350 func TestIssue56044(t *testing.T) {
351         if testing.Short() {
352                 t.Skipf("skipping test: too long for short mode")
353         }
354         if !goexperiment.CoverageRedesign {
355                 t.Skipf("skipping new coverage tests (experiment not enabled)")
356         }
357
358         testenv.MustHaveGoBuild(t)
359
360         modes := []string{"-covermode=set", "-covermode=atomic"}
361
362         for _, mode := range modes {
363                 // Build the Go runtime with "-m", capturing output.
364                 args := []string{"build", "-gcflags=runtime=-m", "runtime"}
365                 cmd := exec.Command(testenv.GoToolPath(t), args...)
366                 b, err := cmd.CombinedOutput()
367                 if err != nil {
368                         t.Fatalf("build failed (%v): %s", err, b)
369                 }
370                 mbase := collectInlCands(string(b))
371
372                 // Redo the build with -cover, also with "-m".
373                 args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
374                 cmd = exec.Command(testenv.GoToolPath(t), args...)
375                 b, err = cmd.CombinedOutput()
376                 if err != nil {
377                         t.Fatalf("build failed (%v): %s", err, b)
378                 }
379                 mcov := collectInlCands(string(b))
380
381                 // Make sure that there aren't any functions that are marked
382                 // as inline candidates at base but not with coverage.
383                 for k := range mbase {
384                         if _, ok := mcov[k]; !ok {
385                                 t.Errorf("error: did not find %s in coverage -m output", k)
386                         }
387                 }
388         }
389 }