]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/test/inl_test.go
runtime: redo heap bitmap
[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/testenv"
10         "io"
11         "math/bits"
12         "os/exec"
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                         "getArgInfoFast",
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                         // TODO(rsc): Why are these not reported as inlined?
210                         // "(*Pointer[T]).CompareAndSwap",
211                         // "(*Pointer[T]).Load",
212                         // "(*Pointer[T]).Store",
213                         // "(*Pointer[T]).Swap",
214                 },
215         }
216
217         if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
218                 // nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
219                 // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
220                 // On loong64, mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive
221                 // to inline (Issue 22239).
222                 want["runtime"] = append(want["runtime"], "nextFreeFast")
223                 // Same behavior for heapBits.nextFast.
224                 want["runtime"] = append(want["runtime"], "heapBits.nextFast")
225         }
226         if runtime.GOARCH != "386" {
227                 // As explained above, Ctz64 and Ctz32 are not Go code on 386.
228                 // The same applies to Bswap32.
229                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
230                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
231                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
232         }
233         if bits.UintSize == 64 {
234                 // mix is only defined on 64-bit architectures
235                 want["runtime"] = append(want["runtime"], "mix")
236                 // (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
237                 want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
238         }
239
240         switch runtime.GOARCH {
241         case "386", "wasm", "arm":
242         default:
243                 // TODO(mvdan): As explained in /test/inline_sync.go, some
244                 // architectures don't have atomic intrinsics, so these go over
245                 // the inlining budget. Move back to the main table once that
246                 // problem is solved.
247                 want["sync"] = []string{
248                         "(*Mutex).Lock",
249                         "(*Mutex).Unlock",
250                         "(*RWMutex).RLock",
251                         "(*RWMutex).RUnlock",
252                         "(*Once).Do",
253                 }
254         }
255
256         // Functions that must actually be inlined; they must have actual callers.
257         must := map[string]bool{
258                 "compress/flate.byLiteral.Len":  true,
259                 "compress/flate.byLiteral.Less": true,
260                 "compress/flate.byLiteral.Swap": true,
261         }
262
263         notInlinedReason := make(map[string]string)
264         pkgs := make([]string, 0, len(want))
265         for pname, fnames := range want {
266                 pkgs = append(pkgs, pname)
267                 for _, fname := range fnames {
268                         fullName := pname + "." + fname
269                         if _, ok := notInlinedReason[fullName]; ok {
270                                 t.Errorf("duplicate func: %s", fullName)
271                         }
272                         notInlinedReason[fullName] = "unknown reason"
273                 }
274         }
275
276         args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
277         cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
278         pr, pw := io.Pipe()
279         cmd.Stdout = pw
280         cmd.Stderr = pw
281         cmdErr := make(chan error, 1)
282         go func() {
283                 cmdErr <- cmd.Run()
284                 pw.Close()
285         }()
286         scanner := bufio.NewScanner(pr)
287         curPkg := ""
288         canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
289         haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
290         cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
291         for scanner.Scan() {
292                 line := scanner.Text()
293                 if strings.HasPrefix(line, "# ") {
294                         curPkg = line[2:]
295                         continue
296                 }
297                 if m := haveInlined.FindStringSubmatch(line); m != nil {
298                         fname := m[1]
299                         delete(notInlinedReason, curPkg+"."+fname)
300                         continue
301                 }
302                 if m := canInline.FindStringSubmatch(line); m != nil {
303                         fname := m[1]
304                         fullname := curPkg + "." + fname
305                         // If function must be inlined somewhere, being inlinable is not enough
306                         if _, ok := must[fullname]; !ok {
307                                 delete(notInlinedReason, fullname)
308                                 continue
309                         }
310                 }
311                 if m := cannotInline.FindStringSubmatch(line); m != nil {
312                         fname, reason := m[1], m[2]
313                         fullName := curPkg + "." + fname
314                         if _, ok := notInlinedReason[fullName]; ok {
315                                 // cmd/compile gave us a reason why
316                                 notInlinedReason[fullName] = reason
317                         }
318                         continue
319                 }
320         }
321         if err := <-cmdErr; err != nil {
322                 t.Fatal(err)
323         }
324         if err := scanner.Err(); err != nil {
325                 t.Fatal(err)
326         }
327         for fullName, reason := range notInlinedReason {
328                 t.Errorf("%s was not inlined: %s", fullName, reason)
329         }
330 }