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