]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/test/inl_test.go
[dev.fuzz] all: merge master (d137b74) into dev.fuzz
[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                         "deferArgs",
46                         "deferclass",
47                         "evacuated",
48                         "fastlog2",
49                         "fastrand",
50                         "float64bits",
51                         "funcPC",
52                         "getArgInfoFast",
53                         "getm",
54                         "getMCache",
55                         "isDirectIface",
56                         "itabHashFunc",
57                         "noescape",
58                         "pcvalueCacheKey",
59                         "readUnaligned32",
60                         "readUnaligned64",
61                         "releasem",
62                         "roundupsize",
63                         "stackmapdata",
64                         "stringStructOf",
65                         "subtract1",
66                         "subtractb",
67                         "tophash",
68                         "totaldefersize",
69                         "(*bmap).keys",
70                         "(*bmap).overflow",
71                         "(*waitq).enqueue",
72
73                         // GC-related ones
74                         "cgoInRange",
75                         "gclinkptr.ptr",
76                         "guintptr.ptr",
77                         "heapBits.bits",
78                         "heapBits.isPointer",
79                         "heapBits.morePointers",
80                         "heapBits.next",
81                         "heapBitsForAddr",
82                         "markBits.isMarked",
83                         "muintptr.ptr",
84                         "puintptr.ptr",
85                         "spanOf",
86                         "spanOfUnchecked",
87                         "(*gcWork).putFast",
88                         "(*gcWork).tryGetFast",
89                         "(*guintptr).set",
90                         "(*markBits).advance",
91                         "(*mspan).allocBitsForIndex",
92                         "(*mspan).base",
93                         "(*mspan).markBitsForBase",
94                         "(*mspan).markBitsForIndex",
95                         "(*muintptr).set",
96                         "(*puintptr).set",
97                 },
98                 "runtime/internal/sys": {},
99                 "runtime/internal/math": {
100                         "MulUintptr",
101                 },
102                 "bytes": {
103                         "(*Buffer).Bytes",
104                         "(*Buffer).Cap",
105                         "(*Buffer).Len",
106                         "(*Buffer).Grow",
107                         "(*Buffer).Next",
108                         "(*Buffer).Read",
109                         "(*Buffer).ReadByte",
110                         "(*Buffer).Reset",
111                         "(*Buffer).String",
112                         "(*Buffer).UnreadByte",
113                         "(*Buffer).tryGrowByReslice",
114                 },
115                 "compress/flate": {
116                         "byLiteral.Len",
117                         "byLiteral.Less",
118                         "byLiteral.Swap",
119                         "(*dictDecoder).tryWriteCopy",
120                 },
121                 "encoding/base64": {
122                         "assemble32",
123                         "assemble64",
124                 },
125                 "unicode/utf8": {
126                         "FullRune",
127                         "FullRuneInString",
128                         "RuneLen",
129                         "ValidRune",
130                 },
131                 "reflect": {
132                         "Value.CanAddr",
133                         "Value.CanSet",
134                         "Value.CanInterface",
135                         "Value.IsValid",
136                         "Value.pointer",
137                         "add",
138                         "align",
139                         "flag.mustBe",
140                         "flag.mustBeAssignable",
141                         "flag.mustBeExported",
142                         "flag.kind",
143                         "flag.ro",
144                 },
145                 "regexp": {
146                         "(*bitState).push",
147                 },
148                 "math/big": {
149                         "bigEndianWord",
150                         // The following functions require the math_big_pure_go build tag.
151                         "addVW",
152                         "subVW",
153                 },
154                 "math/rand": {
155                         "(*rngSource).Int63",
156                         "(*rngSource).Uint64",
157                 },
158                 "net": {
159                         "(*UDPConn).ReadFromUDP",
160                 },
161         }
162
163         if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
164                 // nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
165                 // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
166                 // On mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive
167                 // to inline (Issue 22239).
168                 want["runtime"] = append(want["runtime"], "nextFreeFast")
169         }
170         if runtime.GOARCH != "386" {
171                 // As explained above, Ctz64 and Ctz32 are not Go code on 386.
172                 // The same applies to Bswap32.
173                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
174                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
175                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
176         }
177         if bits.UintSize == 64 {
178                 // mix is only defined on 64-bit architectures
179                 want["runtime"] = append(want["runtime"], "mix")
180         }
181
182         switch runtime.GOARCH {
183         case "386", "wasm", "arm":
184         default:
185                 // TODO(mvdan): As explained in /test/inline_sync.go, some
186                 // architectures don't have atomic intrinsics, so these go over
187                 // the inlining budget. Move back to the main table once that
188                 // problem is solved.
189                 want["sync"] = []string{
190                         "(*Mutex).Lock",
191                         "(*Mutex).Unlock",
192                         "(*RWMutex).RLock",
193                         "(*RWMutex).RUnlock",
194                         "(*Once).Do",
195                 }
196         }
197
198         // Functions that must actually be inlined; they must have actual callers.
199         must := map[string]bool{
200                 "compress/flate.byLiteral.Len":  true,
201                 "compress/flate.byLiteral.Less": true,
202                 "compress/flate.byLiteral.Swap": true,
203         }
204
205         notInlinedReason := make(map[string]string)
206         pkgs := make([]string, 0, len(want))
207         for pname, fnames := range want {
208                 pkgs = append(pkgs, pname)
209                 for _, fname := range fnames {
210                         fullName := pname + "." + fname
211                         if _, ok := notInlinedReason[fullName]; ok {
212                                 t.Errorf("duplicate func: %s", fullName)
213                         }
214                         notInlinedReason[fullName] = "unknown reason"
215                 }
216         }
217
218         args := append([]string{"build", "-a", "-gcflags=all=-m -m", "-tags=math_big_pure_go"}, pkgs...)
219         cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
220         pr, pw := io.Pipe()
221         cmd.Stdout = pw
222         cmd.Stderr = pw
223         cmdErr := make(chan error, 1)
224         go func() {
225                 cmdErr <- cmd.Run()
226                 pw.Close()
227         }()
228         scanner := bufio.NewScanner(pr)
229         curPkg := ""
230         canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
231         haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
232         cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
233         for scanner.Scan() {
234                 line := scanner.Text()
235                 if strings.HasPrefix(line, "# ") {
236                         curPkg = line[2:]
237                         continue
238                 }
239                 if m := haveInlined.FindStringSubmatch(line); m != nil {
240                         fname := m[1]
241                         delete(notInlinedReason, curPkg+"."+fname)
242                         continue
243                 }
244                 if m := canInline.FindStringSubmatch(line); m != nil {
245                         fname := m[1]
246                         fullname := curPkg + "." + fname
247                         // If function must be inlined somewhere, being inlinable is not enough
248                         if _, ok := must[fullname]; !ok {
249                                 delete(notInlinedReason, fullname)
250                                 continue
251                         }
252                 }
253                 if m := cannotInline.FindStringSubmatch(line); m != nil {
254                         fname, reason := m[1], m[2]
255                         fullName := curPkg + "." + fname
256                         if _, ok := notInlinedReason[fullName]; ok {
257                                 // cmd/compile gave us a reason why
258                                 notInlinedReason[fullName] = reason
259                         }
260                         continue
261                 }
262         }
263         if err := <-cmdErr; err != nil {
264                 t.Fatal(err)
265         }
266         if err := scanner.Err(); err != nil {
267                 t.Fatal(err)
268         }
269         for fullName, reason := range notInlinedReason {
270                 t.Errorf("%s was not inlined: %s", fullName, reason)
271         }
272 }