]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/test/inl_test.go
cmd: remove GOEXPERIMENT=nounified knob
[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[T])'s methods' handled below.
212                 },
213         }
214
215         if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
216                 // nextFreeFast calls sys.TrailingZeros64, which on 386 is implemented in asm and is not inlinable.
217                 // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
218                 // On loong64, mips64x and riscv64, TrailingZeros64 is not intrinsified and causes nextFreeFast
219                 // too expensive to inline (Issue 22239).
220                 want["runtime"] = append(want["runtime"], "nextFreeFast")
221                 // Same behavior for heapBits.nextFast.
222                 want["runtime"] = append(want["runtime"], "heapBits.nextFast")
223         }
224         if runtime.GOARCH != "386" {
225                 // As explained above, TrailingZeros64 and TrailingZeros32 are not Go code on 386.
226                 // The same applies to Bswap32.
227                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros64")
228                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros32")
229                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
230         }
231         if bits.UintSize == 64 {
232                 // mix is only defined on 64-bit architectures
233                 want["runtime"] = append(want["runtime"], "mix")
234                 // (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
235                 want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
236         }
237         if true /* was buildcfg.Experiment.Unified */ {
238                 // Non-unified IR does not report "inlining call ..." for atomic.Pointer[T]'s methods.
239                 // TODO(cuonglm): remove once non-unified IR frontend gone.
240                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).CompareAndSwap")
241                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).Load")
242                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).Store")
243                 want["sync/atomic"] = append(want["sync/atomic"], "(*Pointer[go.shape.int]).Swap")
244         }
245
246         switch runtime.GOARCH {
247         case "386", "wasm", "arm":
248         default:
249                 // TODO(mvdan): As explained in /test/inline_sync.go, some
250                 // architectures don't have atomic intrinsics, so these go over
251                 // the inlining budget. Move back to the main table once that
252                 // problem is solved.
253                 want["sync"] = []string{
254                         "(*Mutex).Lock",
255                         "(*Mutex).Unlock",
256                         "(*RWMutex).RLock",
257                         "(*RWMutex).RUnlock",
258                         "(*Once).Do",
259                 }
260         }
261
262         // Functions that must actually be inlined; they must have actual callers.
263         must := map[string]bool{
264                 "compress/flate.byLiteral.Len":  true,
265                 "compress/flate.byLiteral.Less": true,
266                 "compress/flate.byLiteral.Swap": true,
267         }
268
269         notInlinedReason := make(map[string]string)
270         pkgs := make([]string, 0, len(want))
271         for pname, fnames := range want {
272                 pkgs = append(pkgs, pname)
273                 for _, fname := range fnames {
274                         fullName := pname + "." + fname
275                         if _, ok := notInlinedReason[fullName]; ok {
276                                 t.Errorf("duplicate func: %s", fullName)
277                         }
278                         notInlinedReason[fullName] = "unknown reason"
279                 }
280         }
281
282         args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
283         cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), args...))
284         pr, pw := io.Pipe()
285         cmd.Stdout = pw
286         cmd.Stderr = pw
287         cmdErr := make(chan error, 1)
288         go func() {
289                 cmdErr <- cmd.Run()
290                 pw.Close()
291         }()
292         scanner := bufio.NewScanner(pr)
293         curPkg := ""
294         canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
295         haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
296         cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
297         for scanner.Scan() {
298                 line := scanner.Text()
299                 if strings.HasPrefix(line, "# ") {
300                         curPkg = line[2:]
301                         continue
302                 }
303                 if m := haveInlined.FindStringSubmatch(line); m != nil {
304                         fname := m[1]
305                         delete(notInlinedReason, curPkg+"."+fname)
306                         continue
307                 }
308                 if m := canInline.FindStringSubmatch(line); m != nil {
309                         fname := m[1]
310                         fullname := curPkg + "." + fname
311                         // If function must be inlined somewhere, being inlinable is not enough
312                         if _, ok := must[fullname]; !ok {
313                                 delete(notInlinedReason, fullname)
314                                 continue
315                         }
316                 }
317                 if m := cannotInline.FindStringSubmatch(line); m != nil {
318                         fname, reason := m[1], m[2]
319                         fullName := curPkg + "." + fname
320                         if _, ok := notInlinedReason[fullName]; ok {
321                                 // cmd/compile gave us a reason why
322                                 notInlinedReason[fullName] = reason
323                         }
324                         continue
325                 }
326         }
327         if err := <-cmdErr; err != nil {
328                 t.Fatal(err)
329         }
330         if err := scanner.Err(); err != nil {
331                 t.Fatal(err)
332         }
333         for fullName, reason := range notInlinedReason {
334                 t.Errorf("%s was not inlined: %s", fullName, reason)
335         }
336 }
337
338 func collectInlCands(msgs string) map[string]struct{} {
339         rv := make(map[string]struct{})
340         lines := strings.Split(msgs, "\n")
341         re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
342         for _, line := range lines {
343                 m := re.FindStringSubmatch(line)
344                 if m != nil {
345                         rv[m[1]] = struct{}{}
346                 }
347         }
348         return rv
349 }
350
351 func TestIssue56044(t *testing.T) {
352         if testing.Short() {
353                 t.Skipf("skipping test: too long for short mode")
354         }
355         if !goexperiment.CoverageRedesign {
356                 t.Skipf("skipping new coverage tests (experiment not enabled)")
357         }
358
359         testenv.MustHaveGoBuild(t)
360
361         modes := []string{"-covermode=set", "-covermode=atomic"}
362
363         for _, mode := range modes {
364                 // Build the Go runtime with "-m", capturing output.
365                 args := []string{"build", "-gcflags=runtime=-m", "runtime"}
366                 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
367                 b, err := cmd.CombinedOutput()
368                 if err != nil {
369                         t.Fatalf("build failed (%v): %s", err, b)
370                 }
371                 mbase := collectInlCands(string(b))
372
373                 // Redo the build with -cover, also with "-m".
374                 args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
375                 cmd = testenv.Command(t, testenv.GoToolPath(t), args...)
376                 b, err = cmd.CombinedOutput()
377                 if err != nil {
378                         t.Fatalf("build failed (%v): %s", err, b)
379                 }
380                 mcov := collectInlCands(string(b))
381
382                 // Make sure that there aren't any functions that are marked
383                 // as inline candidates at base but not with coverage.
384                 for k := range mbase {
385                         if _, ok := mcov[k]; !ok {
386                                 t.Errorf("error: did not find %s in coverage -m output", k)
387                         }
388                 }
389         }
390 }