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  	}
   234  
   235  	if !goexperiment.SwissMap {
   236  		// Maps
   237  		want["runtime"] = append(want["runtime"], "bucketMask")
   238  		want["runtime"] = append(want["runtime"], "bucketShift")
   239  		want["runtime"] = append(want["runtime"], "evacuated")
   240  		want["runtime"] = append(want["runtime"], "tophash")
   241  		want["runtime"] = append(want["runtime"], "(*bmap).keys")
   242  		want["runtime"] = append(want["runtime"], "(*bmap).overflow")
   243  	}
   244  	if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
   245  		// nextFreeFast calls sys.TrailingZeros64, which on 386 is implemented in asm and is not inlinable.
   246  		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
   247  		// On loong64, mips64x and riscv64, TrailingZeros64 is not intrinsified and causes nextFreeFast
   248  		// too expensive to inline (Issue 22239).
   249  		want["runtime"] = append(want["runtime"], "nextFreeFast")
   250  	}
   251  	if runtime.GOARCH != "386" {
   252  		// As explained above, TrailingZeros64 and TrailingZeros32 are not Go code on 386.
   253  		// The same applies to Bswap32.
   254  		want["internal/runtime/sys"] = append(want["internal/runtime/sys"], "TrailingZeros64")
   255  		want["internal/runtime/sys"] = append(want["internal/runtime/sys"], "TrailingZeros32")
   256  		want["internal/runtime/sys"] = append(want["internal/runtime/sys"], "Bswap32")
   257  	}
   258  	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" {
   259  		// internal/runtime/atomic.Loaduintptr is only intrinsified on these platforms.
   260  		want["runtime"] = append(want["runtime"], "traceAcquire")
   261  	}
   262  	if bits.UintSize == 64 {
   263  		// mix is only defined on 64-bit architectures
   264  		want["runtime"] = append(want["runtime"], "mix")
   265  		// (*Bool).CompareAndSwap is just over budget on 32-bit systems (386, arm).
   266  		want["sync/atomic"] = append(want["sync/atomic"], "(*Bool).CompareAndSwap")
   267  	}
   268  
   269  	switch runtime.GOARCH {
   270  	case "386", "wasm", "arm":
   271  	default:
   272  		// TODO(mvdan): As explained in /test/inline_sync.go, some
   273  		// architectures don't have atomic intrinsics, so these go over
   274  		// the inlining budget. Move back to the main table once that
   275  		// problem is solved.
   276  		want["sync"] = []string{
   277  			"(*Mutex).Lock",
   278  			"(*Mutex).Unlock",
   279  			"(*RWMutex).RLock",
   280  			"(*RWMutex).RUnlock",
   281  			"(*Once).Do",
   282  		}
   283  	}
   284  
   285  	// Functions that must actually be inlined; they must have actual callers.
   286  	must := map[string]bool{
   287  		"compress/flate.byLiteral.Len":  true,
   288  		"compress/flate.byLiteral.Less": true,
   289  		"compress/flate.byLiteral.Swap": true,
   290  	}
   291  
   292  	notInlinedReason := make(map[string]string)
   293  	pkgs := make([]string, 0, len(want))
   294  	for pname, fnames := range want {
   295  		pkgs = append(pkgs, pname)
   296  		for _, fname := range fnames {
   297  			fullName := pname + "." + fname
   298  			if _, ok := notInlinedReason[fullName]; ok {
   299  				t.Errorf("duplicate func: %s", fullName)
   300  			}
   301  			notInlinedReason[fullName] = "unknown reason"
   302  		}
   303  	}
   304  
   305  	args := append([]string{"build", "-gcflags=-m -m", "-tags=math_big_pure_go"}, pkgs...)
   306  	cmd := testenv.CleanCmdEnv(testenv.Command(t, testenv.GoToolPath(t), args...))
   307  	pr, pw := io.Pipe()
   308  	cmd.Stdout = pw
   309  	cmd.Stderr = pw
   310  	cmdErr := make(chan error, 1)
   311  	go func() {
   312  		cmdErr <- cmd.Run()
   313  		pw.Close()
   314  	}()
   315  	scanner := bufio.NewScanner(pr)
   316  	curPkg := ""
   317  	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
   318  	haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
   319  	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
   320  	for scanner.Scan() {
   321  		line := scanner.Text()
   322  		if strings.HasPrefix(line, "# ") {
   323  			curPkg = line[2:]
   324  			continue
   325  		}
   326  		if m := haveInlined.FindStringSubmatch(line); m != nil {
   327  			fname := m[1]
   328  			delete(notInlinedReason, curPkg+"."+fname)
   329  			continue
   330  		}
   331  		if m := canInline.FindStringSubmatch(line); m != nil {
   332  			fname := m[1]
   333  			fullname := curPkg + "." + fname
   334  			// If function must be inlined somewhere, being inlinable is not enough
   335  			if _, ok := must[fullname]; !ok {
   336  				delete(notInlinedReason, fullname)
   337  				continue
   338  			}
   339  		}
   340  		if m := cannotInline.FindStringSubmatch(line); m != nil {
   341  			fname, reason := m[1], m[2]
   342  			fullName := curPkg + "." + fname
   343  			if _, ok := notInlinedReason[fullName]; ok {
   344  				// cmd/compile gave us a reason why
   345  				notInlinedReason[fullName] = reason
   346  			}
   347  			continue
   348  		}
   349  	}
   350  	if err := <-cmdErr; err != nil {
   351  		t.Fatal(err)
   352  	}
   353  	if err := scanner.Err(); err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	for fullName, reason := range notInlinedReason {
   357  		t.Errorf("%s was not inlined: %s", fullName, reason)
   358  	}
   359  }
   360  
   361  func collectInlCands(msgs string) map[string]struct{} {
   362  	rv := make(map[string]struct{})
   363  	lines := strings.Split(msgs, "\n")
   364  	re := regexp.MustCompile(`^\S+\s+can\s+inline\s+(\S+)`)
   365  	for _, line := range lines {
   366  		m := re.FindStringSubmatch(line)
   367  		if m != nil {
   368  			rv[m[1]] = struct{}{}
   369  		}
   370  	}
   371  	return rv
   372  }
   373  
   374  func TestIssue56044(t *testing.T) {
   375  	if testing.Short() {
   376  		t.Skipf("skipping test: too long for short mode")
   377  	}
   378  	if !goexperiment.CoverageRedesign {
   379  		t.Skipf("skipping new coverage tests (experiment not enabled)")
   380  	}
   381  
   382  	testenv.MustHaveGoBuild(t)
   383  
   384  	modes := []string{"-covermode=set", "-covermode=atomic"}
   385  
   386  	for _, mode := range modes {
   387  		// Build the Go runtime with "-m", capturing output.
   388  		args := []string{"build", "-gcflags=runtime=-m", "runtime"}
   389  		cmd := testenv.Command(t, testenv.GoToolPath(t), args...)
   390  		b, err := cmd.CombinedOutput()
   391  		if err != nil {
   392  			t.Fatalf("build failed (%v): %s", err, b)
   393  		}
   394  		mbase := collectInlCands(string(b))
   395  
   396  		// Redo the build with -cover, also with "-m".
   397  		args = []string{"build", "-gcflags=runtime=-m", mode, "runtime"}
   398  		cmd = testenv.Command(t, testenv.GoToolPath(t), args...)
   399  		b, err = cmd.CombinedOutput()
   400  		if err != nil {
   401  			t.Fatalf("build failed (%v): %s", err, b)
   402  		}
   403  		mcov := collectInlCands(string(b))
   404  
   405  		// Make sure that there aren't any functions that are marked
   406  		// as inline candidates at base but not with coverage.
   407  		for k := range mbase {
   408  			if _, ok := mcov[k]; !ok {
   409  				t.Errorf("error: did not find %s in coverage -m output", k)
   410  			}
   411  		}
   412  	}
   413  }
   414  

View as plain text