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