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.
9 "internal/goexperiment"
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")
26 testenv.MustHaveGoRun(t)
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{
75 "writeHeapBitsForAddr",
82 "typePointers.nextFast",
84 "(*gcWork).tryGetFast",
86 "(*markBits).advance",
87 "(*mspan).allocBitsForIndex",
89 "(*mspan).markBitsForBase",
90 "(*mspan).markBitsForIndex",
91 "(*mspan).writeUserArenaHeapBits",
97 // Trace-related ones.
101 "runtime/internal/sys": {},
102 "runtime/internal/math": {
112 "(*Buffer).ReadByte",
115 "(*Buffer).UnreadByte",
116 "(*Buffer).tryGrowByReslice",
119 "UseInterfaceSwitchCache",
125 "(*dictDecoder).tryWriteCopy",
148 "Value.CanInterface",
161 "Value.OverflowComplex",
162 "Value.OverflowFloat",
164 "Value.OverflowUint",
173 "flag.mustBeAssignable",
174 "flag.mustBeExported",
183 // The following functions require the math_big_pure_go build tag.
188 "(*rngSource).Int63",
189 "(*rngSource).Uint64",
192 "(*UDPConn).ReadFromUDP",
195 // Both OnceFunc and its returned closure need to be inlinable so
196 // that the returned closure can be inlined into the caller of OnceFunc.
198 "OnceFunc.func2", // The returned closure.
199 // TODO(austin): It would be good to check OnceValue and OnceValues,
200 // too, but currently they aren't reported because they have type
201 // parameters and aren't instantiated in sync.
204 // (*Bool).CompareAndSwap handled below.
209 "(*Int32).CompareAndSwap",
214 "(*Int64).CompareAndSwap",
219 "(*Uint32).CompareAndSwap",
224 "(*Uint64).CompareAndSwap",
229 "(*Uintptr).CompareAndSwap",
233 "(*Pointer[go.shape.int]).CompareAndSwap",
234 "(*Pointer[go.shape.int]).Load",
235 "(*Pointer[go.shape.int]).Store",
236 "(*Pointer[go.shape.int]).Swap",
240 if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
241 // nextFreeFast calls sys.TrailingZeros64, which on 386 is implemented in asm and is not inlinable.
242 // We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
243 // On loong64, mips64x and riscv64, TrailingZeros64 is not intrinsified and causes nextFreeFast
244 // too expensive to inline (Issue 22239).
245 want["runtime"] = append(want["runtime"], "nextFreeFast")
246 // Same behavior for heapBits.nextFast.
247 want["runtime"] = append(want["runtime"], "heapBits.nextFast")
249 if runtime.GOARCH != "386" {
250 // As explained above, TrailingZeros64 and TrailingZeros32 are not Go code on 386.
251 // The same applies to Bswap32.
252 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros64")
253 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "TrailingZeros32")
254 want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
256 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" {
257 // runtime/internal/atomic.Loaduintptr is only intrinsified on these platforms.
258 want["runtime"] = append(want["runtime"], "traceAcquire")
260 if bits.UintSize == 64 {
261 // mix is only defined on 64-bit architectures
262 want["runtime"] = append(want["runtime"], "mix")
263 // (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
264 want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
267 switch runtime.GOARCH {
268 case "386", "wasm", "arm":
270 // TODO(mvdan): As explained in /test/inline_sync.go, some
271 // architectures don't have atomic intrinsics, so these go over
272 // the inlining budget. Move back to the main table once that
273 // problem is solved.
274 want["sync"] = []string{
278 "(*RWMutex).RUnlock",
283 // Functions that must actually be inlined; they must have actual callers.
284 must := map[string]bool{
285 "compress/flate.byLiteral.Len": true,
286 "compress/flate.byLiteral.Less": true,
287 "compress/flate.byLiteral.Swap": true,
290 notInlinedReason := make(map[string]string)
291 pkgs := make([]string, 0, len(want))
292 for pname, fnames := range want {
293 pkgs = append(pkgs, pname)
294 for _, fname := range fnames {
295 fullName := pname + "." + fname
296 if _, ok := notInlinedReason[fullName]; ok {
297 t.Errorf("duplicate func: %s", fullName)
299 notInlinedReason[fullName] = "unknown reason"
303 args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
304 cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), args...))
308 cmdErr := make(chan error, 1)
313 scanner := bufio.NewScanner(pr)
315 canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
316 haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
317 cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
319 line := scanner.Text()
320 if strings.HasPrefix(line, "# ") {
324 if m := haveInlined.FindStringSubmatch(line); m != nil {
326 delete(notInlinedReason, curPkg+"."+fname)
329 if m := canInline.FindStringSubmatch(line); m != nil {
331 fullname := curPkg + "." + fname
332 // If function must be inlined somewhere, being inlinable is not enough
333 if _, ok := must[fullname]; !ok {
334 delete(notInlinedReason, fullname)
338 if m := cannotInline.FindStringSubmatch(line); m != nil {
339 fname, reason := m[1], m[2]
340 fullName := curPkg + "." + fname
341 if _, ok := notInlinedReason[fullName]; ok {
342 // cmd/compile gave us a reason why
343 notInlinedReason[fullName] = reason
348 if err := <-cmdErr; err != nil {
351 if err := scanner.Err(); err != nil {
354 for fullName, reason := range notInlinedReason {
355 t.Errorf("%s was not inlined: %s", fullName, reason)
359 func collectInlCands(msgs string) map[string]struct{} {
360 rv := make(map[string]struct{})
361 lines := strings.Split(msgs, "\n")
362 re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
363 for _, line := range lines {
364 m := re.FindStringSubmatch(line)
366 rv[m[1]] = struct{}{}
372 func TestIssue56044(t *testing.T) {
374 t.Skipf("skipping test: too long for short mode")
376 if !goexperiment.CoverageRedesign {
377 t.Skipf("skipping new coverage tests (experiment not enabled)")
380 testenv.MustHaveGoBuild(t)
382 modes := []string{"-covermode=set", "-covermode=atomic"}
384 for _, mode := range modes {
385 // Build the Go runtime with "-m", capturing output.
386 args := []string{"build", "-gcflags=runtime=-m", "runtime"}
387 cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
388 b, err := cmd.CombinedOutput()
390 t.Fatalf("build failed (%v): %s", err, b)
392 mbase := collectInlCands(string(b))
394 // Redo the build with -cover, also with "-m".
395 args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
396 cmd = testenv.Command(t, testenv.GoToolPath(t), args...)
397 b, err = cmd.CombinedOutput()
399 t.Fatalf("build failed (%v): %s", err, b)
401 mcov := collectInlCands(string(b))
403 // Make sure that there aren't any functions that are marked
404 // as inline candidates at base but not with coverage.
405 for k := range mbase {
406 if _, ok := mcov[k]; !ok {
407 t.Errorf("error: did not find %s in coverage -m output", k)