Source file 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/goexperiment"
    10  	"internal/testenv"
    11  	"io"
    12  	"math/bits"
    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  			"chanbuf",
    43  			"fastlog2",
    44  			"float64bits",
    45  			"funcspdelta",
    46  			"getm",
    47  			"getMCache",
    48  			"heapSetTypeNoHeader",
    49  			"heapSetTypeSmallHeader",
    50  			"isDirectIface",
    51  			"itabHashFunc",
    52  			"nextslicecap",
    53  			"noescape",
    54  			"pcvalueCacheKey",
    55  			"rand32",
    56  			"readUnaligned32",
    57  			"readUnaligned64",
    58  			"releasem",
    59  			"roundupsize",
    60  			"stackmapdata",
    61  			"stringStructOf",
    62  			"subtract1",
    63  			"subtractb",
    64  			"(*waitq).enqueue",
    65  			"funcInfo.entry",
    66  
    67  			// GC-related ones
    68  			"cgoInRange",
    69  			"gclinkptr.ptr",
    70  			"guintptr.ptr",
    71  			"heapBitsSlice",
    72  			"markBits.isMarked",
    73  			"muintptr.ptr",
    74  			"puintptr.ptr",
    75  			"spanOf",
    76  			"spanOfUnchecked",
    77  			"typePointers.nextFast",
    78  			"(*gcWork).putFast",
    79  			"(*gcWork).tryGetFast",
    80  			"(*guintptr).set",
    81  			"(*markBits).advance",
    82  			"(*mspan).allocBitsForIndex",
    83  			"(*mspan).base",
    84  			"(*mspan).markBitsForBase",
    85  			"(*mspan).markBitsForIndex",
    86  			"(*mspan).writeUserArenaHeapBits",
    87  			"(*muintptr).set",
    88  			"(*puintptr).set",
    89  			"(*wbBuf).get1",
    90  			"(*wbBuf).get2",
    91  
    92  			// Trace-related ones.
    93  			"traceLocker.ok",
    94  			"traceEnabled",
    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  		"internal/abi": {
   110  			"UseInterfaceSwitchCache",
   111  		},
   112  		"internal/runtime/math": {
   113  			"MulUintptr",
   114  		},
   115  		"internal/runtime/sys": {},
   116  		"compress/flate": {
   117  			"byLiteral.Len",
   118  			"byLiteral.Less",
   119  			"byLiteral.Swap",
   120  			"(*dictDecoder).tryWriteCopy",
   121  		},
   122  		"encoding/base64": {
   123  			"assemble32",
   124  			"assemble64",
   125  		},
   126  		"unicode/utf8": {
   127  			"FullRune",
   128  			"FullRuneInString",
   129  			"RuneLen",
   130  			"AppendRune",
   131  			"ValidRune",
   132  		},
   133  		"unicode/utf16": {
   134  			"Decode",
   135  		},
   136  		"reflect": {
   137  			"Value.Bool",
   138  			"Value.Bytes",
   139  			"Value.CanAddr",
   140  			"Value.CanComplex",
   141  			"Value.CanFloat",
   142  			"Value.CanInt",
   143  			"Value.CanInterface",
   144  			"Value.CanSet",
   145  			"Value.CanUint",
   146  			"Value.Cap",
   147  			"Value.Complex",
   148  			"Value.Float",
   149  			"Value.Int",
   150  			"Value.Interface",
   151  			"Value.IsNil",
   152  			"Value.IsValid",
   153  			"Value.Kind",
   154  			"Value.Len",
   155  			"Value.MapRange",
   156  			"Value.OverflowComplex",
   157  			"Value.OverflowFloat",
   158  			"Value.OverflowInt",
   159  			"Value.OverflowUint",
   160  			"Value.String",
   161  			"Value.Type",
   162  			"Value.Uint",
   163  			"Value.UnsafeAddr",
   164  			"Value.pointer",
   165  			"add",
   166  			"align",
   167  			"flag.mustBe",
   168  			"flag.mustBeAssignable",
   169  			"flag.mustBeExported",
   170  			"flag.kind",
   171  			"flag.ro",
   172  		},
   173  		"regexp": {
   174  			"(*bitState).push",
   175  		},
   176  		"math/big": {
   177  			"bigEndianWord",
   178  			// The following functions require the math_big_pure_go build tag.
   179  			"addVW",
   180  			"subVW",
   181  		},
   182  		"math/rand": {
   183  			"(*rngSource).Int63",
   184  			"(*rngSource).Uint64",
   185  		},
   186  		"net": {
   187  			"(*UDPConn).ReadFromUDP",
   188  		},
   189  		"sync": {
   190  			// Both OnceFunc and its returned closure need to be inlinable so
   191  			// that the returned closure can be inlined into the caller of OnceFunc.
   192  			"OnceFunc",
   193  			"OnceFunc.func2", // The returned closure.
   194  			// TODO(austin): It would be good to check OnceValue and OnceValues,
   195  			// too, but currently they aren't reported because they have type
   196  			// parameters and aren't instantiated in sync.
   197  		},
   198  		"sync/atomic": {
   199  			// (*Bool).CompareAndSwap handled below.
   200  			"(*Bool).Load",
   201  			"(*Bool).Store",
   202  			"(*Bool).Swap",
   203  			"(*Int32).Add",
   204  			"(*Int32).CompareAndSwap",
   205  			"(*Int32).Load",
   206  			"(*Int32).Store",
   207  			"(*Int32).Swap",
   208  			"(*Int64).Add",
   209  			"(*Int64).CompareAndSwap",
   210  			"(*Int64).Load",
   211  			"(*Int64).Store",
   212  			"(*Int64).Swap",
   213  			"(*Uint32).Add",
   214  			"(*Uint32).CompareAndSwap",
   215  			"(*Uint32).Load",
   216  			"(*Uint32).Store",
   217  			"(*Uint32).Swap",
   218  			"(*Uint64).Add",
   219  			"(*Uint64).CompareAndSwap",
   220  			"(*Uint64).Load",
   221  			"(*Uint64).Store",
   222  			"(*Uint64).Swap",
   223  			"(*Uintptr).Add",
   224  			"(*Uintptr).CompareAndSwap",
   225  			"(*Uintptr).Load",
   226  			"(*Uintptr).Store",
   227  			"(*Uintptr).Swap",
   228  			"(*Pointer[go.shape.int]).CompareAndSwap",
   229  			"(*Pointer[go.shape.int]).Load",
   230  			"(*Pointer[go.shape.int]).Store",
   231  			"(*Pointer[go.shape.int]).Swap",
   232  		},
   233  		"testing": {
   234  			"(*B).Loop",
   235  		},
   236  	}
   237  
   238  	if !goexperiment.SwissMap {
   239  		// Maps
   240  		want["runtime"] = append(want["runtime"], "bucketMask")
   241  		want["runtime"] = append(want["runtime"], "bucketShift")
   242  		want["runtime"] = append(want["runtime"], "evacuated")
   243  		want["runtime"] = append(want["runtime"], "tophash")
   244  		want["runtime"] = append(want["runtime"], "(*bmap).keys")
   245  		want["runtime"] = append(want["runtime"], "(*bmap).overflow")
   246  	}
   247  	if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
   248  		// nextFreeFast calls sys.TrailingZeros64, which on 386 is implemented in asm and is not inlinable.
   249  		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
   250  		// On loong64, mips64x and riscv64, TrailingZeros64 is not intrinsified and causes nextFreeFast
   251  		// too expensive to inline (Issue 22239).
   252  		want["runtime"] = append(want["runtime"], "nextFreeFast")
   253  	}
   254  	if runtime.GOARCH != "386" {
   255  		// As explained above, TrailingZeros64 and TrailingZeros32 are not Go code on 386.
   256  		// The same applies to Bswap32.
   257  		want["internal/runtime/sys"] = append(want["internal/runtime/sys"], "TrailingZeros64")
   258  		want["internal/runtime/sys"] = append(want["internal/runtime/sys"], "TrailingZeros32")
   259  		want["internal/runtime/sys"] = append(want["internal/runtime/sys"], "Bswap32")
   260  	}
   261  	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" {
   262  		// internal/runtime/atomic.Loaduintptr is only intrinsified on these platforms.
   263  		want["runtime"] = append(want["runtime"], "traceAcquire")
   264  	}
   265  	if bits.UintSize == 64 {
   266  		// mix is only defined on 64-bit architectures
   267  		want["runtime"] = append(want["runtime"], "mix")
   268  		// (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
   269  		want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
   270  	}
   271  
   272  	switch runtime.GOARCH {
   273  	case "386", "wasm", "arm":
   274  	default:
   275  		// TODO(mvdan): As explained in /test/inline_sync.go, some
   276  		// architectures don't have atomic intrinsics, so these go over
   277  		// the inlining budget. Move back to the main table once that
   278  		// problem is solved.
   279  		want["sync"] = []string{
   280  			"(*Mutex).Lock",
   281  			"(*Mutex).Unlock",
   282  			"(*RWMutex).RLock",
   283  			"(*RWMutex).RUnlock",
   284  			"(*Once).Do",
   285  		}
   286  	}
   287  
   288  	// Functions that must actually be inlined; they must have actual callers.
   289  	must := map[string]bool{
   290  		"compress/flate.byLiteral.Len":  true,
   291  		"compress/flate.byLiteral.Less": true,
   292  		"compress/flate.byLiteral.Swap": true,
   293  	}
   294  
   295  	notInlinedReason := make(map[string]string)
   296  	pkgs := make([]string, 0, len(want))
   297  	for pname, fnames := range want {
   298  		pkgs = append(pkgs, pname)
   299  		for _, fname := range fnames {
   300  			fullName := pname + "." + fname
   301  			if _, ok := notInlinedReason[fullName]; ok {
   302  				t.Errorf("duplicate func: %s", fullName)
   303  			}
   304  			notInlinedReason[fullName] = "unknown reason"
   305  		}
   306  	}
   307  
   308  	args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
   309  	cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), args...))
   310  	pr, pw := io.Pipe()
   311  	cmd.Stdout = pw
   312  	cmd.Stderr = pw
   313  	cmdErr := make(chan error, 1)
   314  	go func() {
   315  		cmdErr <- cmd.Run()
   316  		pw.Close()
   317  	}()
   318  	scanner := bufio.NewScanner(pr)
   319  	curPkg := ""
   320  	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
   321  	haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
   322  	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
   323  	for scanner.Scan() {
   324  		line := scanner.Text()
   325  		if strings.HasPrefix(line, "# ") {
   326  			curPkg = line[2:]
   327  			continue
   328  		}
   329  		if m := haveInlined.FindStringSubmatch(line); m != nil {
   330  			fname := m[1]
   331  			delete(notInlinedReason, curPkg+"."+fname)
   332  			continue
   333  		}
   334  		if m := canInline.FindStringSubmatch(line); m != nil {
   335  			fname := m[1]
   336  			fullname := curPkg + "." + fname
   337  			// If function must be inlined somewhere, being inlinable is not enough
   338  			if _, ok := must[fullname]; !ok {
   339  				delete(notInlinedReason, fullname)
   340  				continue
   341  			}
   342  		}
   343  		if m := cannotInline.FindStringSubmatch(line); m != nil {
   344  			fname, reason := m[1], m[2]
   345  			fullName := curPkg + "." + fname
   346  			if _, ok := notInlinedReason[fullName]; ok {
   347  				// cmd/compile gave us a reason why
   348  				notInlinedReason[fullName] = reason
   349  			}
   350  			continue
   351  		}
   352  	}
   353  	if err := <-cmdErr; err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	if err := scanner.Err(); err != nil {
   357  		t.Fatal(err)
   358  	}
   359  	for fullName, reason := range notInlinedReason {
   360  		t.Errorf("%s was not inlined: %s", fullName, reason)
   361  	}
   362  }
   363  
   364  func collectInlCands(msgs string) map[string]struct{} {
   365  	rv := make(map[string]struct{})
   366  	lines := strings.Split(msgs, "\n")
   367  	re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
   368  	for _, line := range lines {
   369  		m := re.FindStringSubmatch(line)
   370  		if m != nil {
   371  			rv[m[1]] = struct{}{}
   372  		}
   373  	}
   374  	return rv
   375  }
   376  
   377  func TestIssue56044(t *testing.T) {
   378  	if testing.Short() {
   379  		t.Skipf("skipping test: too long for short mode")
   380  	}
   381  	if !goexperiment.CoverageRedesign {
   382  		t.Skipf("skipping new coverage tests (experiment not enabled)")
   383  	}
   384  
   385  	testenv.MustHaveGoBuild(t)
   386  
   387  	modes := []string{"-covermode=set", "-covermode=atomic"}
   388  
   389  	for _, mode := range modes {
   390  		// Build the Go runtime with "-m", capturing output.
   391  		args := []string{"build", "-gcflags=runtime=-m", "runtime"}
   392  		cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
   393  		b, err := cmd.CombinedOutput()
   394  		if err != nil {
   395  			t.Fatalf("build failed (%v): %s", err, b)
   396  		}
   397  		mbase := collectInlCands(string(b))
   398  
   399  		// Redo the build with -cover, also with "-m".
   400  		args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
   401  		cmd = testenv.Command(t, testenv.GoToolPath(t), args...)
   402  		b, err = cmd.CombinedOutput()
   403  		if err != nil {
   404  			t.Fatalf("build failed (%v): %s", err, b)
   405  		}
   406  		mcov := collectInlCands(string(b))
   407  
   408  		// Make sure that there aren't any functions that are marked
   409  		// as inline candidates at base but not with coverage.
   410  		for k := range mbase {
   411  			if _, ok := mcov[k]; !ok {
   412  				t.Errorf("error: did not find %s in coverage -m output", k)
   413  			}
   414  		}
   415  	}
   416  }
   417  

View as plain text