1
2
3
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
20
21
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
30
31
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 "float64bits",
48 "funcspdelta",
49 "getm",
50 "getMCache",
51 "isDirectIface",
52 "itabHashFunc",
53 "nextslicecap",
54 "noescape",
55 "pcvalueCacheKey",
56 "rand32",
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
72 "cgoInRange",
73 "gclinkptr.ptr",
74 "guintptr.ptr",
75 "heapBitsSlice",
76 "markBits.isMarked",
77 "muintptr.ptr",
78 "puintptr.ptr",
79 "spanOf",
80 "spanOfUnchecked",
81 "typePointers.nextFast",
82 "(*gcWork).putFast",
83 "(*gcWork).tryGetFast",
84 "(*guintptr).set",
85 "(*markBits).advance",
86 "(*mspan).allocBitsForIndex",
87 "(*mspan).base",
88 "(*mspan).markBitsForBase",
89 "(*mspan).markBitsForIndex",
90 "(*mspan).writeUserArenaHeapBits",
91 "(*muintptr).set",
92 "(*puintptr).set",
93 "(*wbBuf).get1",
94 "(*wbBuf).get2",
95
96
97 "traceLocker.ok",
98 "traceEnabled",
99 },
100 "runtime/internal/sys": {},
101 "runtime/internal/math": {
102 "MulUintptr",
103 },
104 "bytes": {
105 "(*Buffer).Bytes",
106 "(*Buffer).Cap",
107 "(*Buffer).Len",
108 "(*Buffer).Grow",
109 "(*Buffer).Next",
110 "(*Buffer).Read",
111 "(*Buffer).ReadByte",
112 "(*Buffer).Reset",
113 "(*Buffer).String",
114 "(*Buffer).UnreadByte",
115 "(*Buffer).tryGrowByReslice",
116 },
117 "internal/abi": {
118 "UseInterfaceSwitchCache",
119 },
120 "compress/flate": {
121 "byLiteral.Len",
122 "byLiteral.Less",
123 "byLiteral.Swap",
124 "(*dictDecoder).tryWriteCopy",
125 },
126 "encoding/base64": {
127 "assemble32",
128 "assemble64",
129 },
130 "unicode/utf8": {
131 "FullRune",
132 "FullRuneInString",
133 "RuneLen",
134 "AppendRune",
135 "ValidRune",
136 },
137 "unicode/utf16": {
138 "Decode",
139 },
140 "reflect": {
141 "Value.Bool",
142 "Value.Bytes",
143 "Value.CanAddr",
144 "Value.CanComplex",
145 "Value.CanFloat",
146 "Value.CanInt",
147 "Value.CanInterface",
148 "Value.CanSet",
149 "Value.CanUint",
150 "Value.Cap",
151 "Value.Complex",
152 "Value.Float",
153 "Value.Int",
154 "Value.Interface",
155 "Value.IsNil",
156 "Value.IsValid",
157 "Value.Kind",
158 "Value.Len",
159 "Value.MapRange",
160 "Value.OverflowComplex",
161 "Value.OverflowFloat",
162 "Value.OverflowInt",
163 "Value.OverflowUint",
164 "Value.String",
165 "Value.Type",
166 "Value.Uint",
167 "Value.UnsafeAddr",
168 "Value.pointer",
169 "add",
170 "align",
171 "flag.mustBe",
172 "flag.mustBeAssignable",
173 "flag.mustBeExported",
174 "flag.kind",
175 "flag.ro",
176 },
177 "regexp": {
178 "(*bitState).push",
179 },
180 "math/big": {
181 "bigEndianWord",
182
183 "addVW",
184 "subVW",
185 },
186 "math/rand": {
187 "(*rngSource).Int63",
188 "(*rngSource).Uint64",
189 },
190 "net": {
191 "(*UDPConn).ReadFromUDP",
192 },
193 "sync": {
194
195
196 "OnceFunc",
197 "OnceFunc.func2",
198
199
200
201 },
202 "sync/atomic": {
203
204 "(*Bool).Load",
205 "(*Bool).Store",
206 "(*Bool).Swap",
207 "(*Int32).Add",
208 "(*Int32).CompareAndSwap",
209 "(*Int32).Load",
210 "(*Int32).Store",
211 "(*Int32).Swap",
212 "(*Int64).Add",
213 "(*Int64).CompareAndSwap",
214 "(*Int64).Load",
215 "(*Int64).Store",
216 "(*Int64).Swap",
217 "(*Uint32).Add",
218 "(*Uint32).CompareAndSwap",
219 "(*Uint32).Load",
220 "(*Uint32).Store",
221 "(*Uint32).Swap",
222 "(*Uint64).Add",
223 "(*Uint64).CompareAndSwap",
224 "(*Uint64).Load",
225 "(*Uint64).Store",
226 "(*Uint64).Swap",
227 "(*Uintptr).Add",
228 "(*Uintptr).CompareAndSwap",
229 "(*Uintptr).Load",
230 "(*Uintptr).Store",
231 "(*Uintptr).Swap",
232 "(*Pointer[go.shape.int]).CompareAndSwap",
233 "(*Pointer[go.shape.int]).Load",
234 "(*Pointer[go.shape.int]).Store",
235 "(*Pointer[go.shape.int]).Swap",
236 },
237 }
238
239 if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
240
241
242
243
244 want["runtime"] = append(want["runtime"], "nextFreeFast")
245 }
246 if runtime.GOARCH != "386" {
247
248
249 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros64")
250 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros32")
251 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
252 }
253 if runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "loong64" || runtime.GOARCH == "mips" || runtime.GOARCH == "mips64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "riscv64" || runtime.GOARCH == "s390x" {
254
255 want["runtime"] = append(want["runtime"], "traceAcquire")
256 }
257 if bits.UintSize == 64 {
258
259 want["runtime"] = append(want["runtime"], "mix")
260
261 want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
262 }
263
264 switch runtime.GOARCH {
265 case "386", "wasm", "arm":
266 default:
267
268
269
270
271 want["sync"] = []string{
272 "(*Mutex).Lock",
273 "(*Mutex).Unlock",
274 "(*RWMutex).RLock",
275 "(*RWMutex).RUnlock",
276 "(*Once).Do",
277 }
278 }
279
280
281 must := map[string]bool{
282 "compress/flate.byLiteral.Len": true,
283 "compress/flate.byLiteral.Less": true,
284 "compress/flate.byLiteral.Swap": true,
285 }
286
287 notInlinedReason := make(map[string]string)
288 pkgs := make([]string, 0, len(want))
289 for pname, fnames := range want {
290 pkgs = append(pkgs, pname)
291 for _, fname := range fnames {
292 fullName := pname + "." + fname
293 if _, ok := notInlinedReason[fullName]; ok {
294 t.Errorf("duplicate func: %s", fullName)
295 }
296 notInlinedReason[fullName] = "unknown reason"
297 }
298 }
299
300 args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
301 cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), args...))
302 pr, pw := io.Pipe()
303 cmd.Stdout = pw
304 cmd.Stderr = pw
305 cmdErr := make(chan error, 1)
306 go func() {
307 cmdErr <- cmd.Run()
308 pw.Close()
309 }()
310 scanner := bufio.NewScanner(pr)
311 curPkg := ""
312 canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
313 haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
314 cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
315 for scanner.Scan() {
316 line := scanner.Text()
317 if strings.HasPrefix(line, "# ") {
318 curPkg = line[2:]
319 continue
320 }
321 if m := haveInlined.FindStringSubmatch(line); m != nil {
322 fname := m[1]
323 delete(notInlinedReason, curPkg+"."+fname)
324 continue
325 }
326 if m := canInline.FindStringSubmatch(line); m != nil {
327 fname := m[1]
328 fullname := curPkg + "." + fname
329
330 if _, ok := must[fullname]; !ok {
331 delete(notInlinedReason, fullname)
332 continue
333 }
334 }
335 if m := cannotInline.FindStringSubmatch(line); m != nil {
336 fname, reason := m[1], m[2]
337 fullName := curPkg + "." + fname
338 if _, ok := notInlinedReason[fullName]; ok {
339
340 notInlinedReason[fullName] = reason
341 }
342 continue
343 }
344 }
345 if err := <-cmdErr; err != nil {
346 t.Fatal(err)
347 }
348 if err := scanner.Err(); err != nil {
349 t.Fatal(err)
350 }
351 for fullName, reason := range notInlinedReason {
352 t.Errorf("%s was not inlined: %s", fullName, reason)
353 }
354 }
355
356 func collectInlCands(msgs string) map[string]struct{} {
357 rv := make(map[string]struct{})
358 lines := strings.Split(msgs, "\n")
359 re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
360 for _, line := range lines {
361 m := re.FindStringSubmatch(line)
362 if m != nil {
363 rv[m[1]] = struct{}{}
364 }
365 }
366 return rv
367 }
368
369 func TestIssue56044(t *testing.T) {
370 if testing.Short() {
371 t.Skipf("skipping test: too long for short mode")
372 }
373 if !goexperiment.CoverageRedesign {
374 t.Skipf("skipping new coverage tests (experiment not enabled)")
375 }
376
377 testenv.MustHaveGoBuild(t)
378
379 modes := []string{"-covermode=set", "-covermode=atomic"}
380
381 for _, mode := range modes {
382
383 args := []string{"build", "-gcflags=runtime=-m", "runtime"}
384 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
385 b, err := cmd.CombinedOutput()
386 if err != nil {
387 t.Fatalf("build failed (%v): %s", err, b)
388 }
389 mbase := collectInlCands(string(b))
390
391
392 args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
393 cmd = testenv.Command(t, testenv.GoToolPath(t), args...)
394 b, err = cmd.CombinedOutput()
395 if err != nil {
396 t.Fatalf("build failed (%v): %s", err, b)
397 }
398 mcov := collectInlCands(string(b))
399
400
401
402 for k := range mbase {
403 if _, ok := mcov[k]; !ok {
404 t.Errorf("error: did not find %s in coverage -m output", k)
405 }
406 }
407 }
408 }
409
View as plain text