]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/compile/internal/test/inl_test.go
all: make sure *Pointer[T]'s methods are inlined as intended
[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/testenv"
11         "io"
12         "math/bits"
13         "os/exec"
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.Ctz64, 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, Ctz64 is not intrinsified and causes nextFreeFast too expensive
217                 // 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, Ctz64 and Ctz32 are not Go code on 386.
224                 // The same applies to Bswap32.
225                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
226                 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
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(exec.Command(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 }