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/testenv"
    10  	"io"
    11  	"math/bits"
    12  	"os/exec"
    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  			"bucketMask",
    43  			"bucketShift",
    44  			"chanbuf",
    45  			"evacuated",
    46  			"fastlog2",
    47  			"fastrand",
    48  			"float64bits",
    49  			"funcspdelta",
    50  			"getArgInfoFast",
    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  			"heapBits.bits",
    76  			"heapBits.isPointer",
    77  			"heapBits.morePointers",
    78  			"heapBits.next",
    79  			"heapBitsForAddr",
    80  			"markBits.isMarked",
    81  			"muintptr.ptr",
    82  			"puintptr.ptr",
    83  			"spanOf",
    84  			"spanOfUnchecked",
    85  			"(*gcWork).putFast",
    86  			"(*gcWork).tryGetFast",
    87  			"(*guintptr).set",
    88  			"(*markBits).advance",
    89  			"(*mspan).allocBitsForIndex",
    90  			"(*mspan).base",
    91  			"(*mspan).markBitsForBase",
    92  			"(*mspan).markBitsForIndex",
    93  			"(*muintptr).set",
    94  			"(*puintptr).set",
    95  		},
    96  		"runtime/internal/sys": {},
    97  		"runtime/internal/math": {
    98  			"MulUintptr",
    99  		},
   100  		"bytes": {
   101  			"(*Buffer).Bytes",
   102  			"(*Buffer).Cap",
   103  			"(*Buffer).Len",
   104  			"(*Buffer).Grow",
   105  			"(*Buffer).Next",
   106  			"(*Buffer).Read",
   107  			"(*Buffer).ReadByte",
   108  			"(*Buffer).Reset",
   109  			"(*Buffer).String",
   110  			"(*Buffer).UnreadByte",
   111  			"(*Buffer).tryGrowByReslice",
   112  		},
   113  		"compress/flate": {
   114  			"byLiteral.Len",
   115  			"byLiteral.Less",
   116  			"byLiteral.Swap",
   117  			"(*dictDecoder).tryWriteCopy",
   118  		},
   119  		"encoding/base64": {
   120  			"assemble32",
   121  			"assemble64",
   122  		},
   123  		"unicode/utf8": {
   124  			"FullRune",
   125  			"FullRuneInString",
   126  			"RuneLen",
   127  			"AppendRune",
   128  			"ValidRune",
   129  		},
   130  		"reflect": {
   131  			"Value.CanInt",
   132  			"Value.CanUint",
   133  			"Value.CanFloat",
   134  			"Value.CanComplex",
   135  			"Value.CanAddr",
   136  			"Value.CanSet",
   137  			"Value.CanInterface",
   138  			"Value.IsValid",
   139  			"Value.pointer",
   140  			"add",
   141  			"align",
   142  			"flag.mustBe",
   143  			"flag.mustBeAssignable",
   144  			"flag.mustBeExported",
   145  			"flag.kind",
   146  			"flag.ro",
   147  		},
   148  		"regexp": {
   149  			"(*bitState).push",
   150  		},
   151  		"math/big": {
   152  			"bigEndianWord",
   153  			// The following functions require the math_big_pure_go build tag.
   154  			"addVW",
   155  			"subVW",
   156  		},
   157  		"math/rand": {
   158  			"(*rngSource).Int63",
   159  			"(*rngSource).Uint64",
   160  		},
   161  		"net": {
   162  			"(*UDPConn).ReadFromUDP",
   163  		},
   164  	}
   165  
   166  	if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
   167  		// nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
   168  		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
   169  		// On mips64x and riscv64, Ctz64 is not intrinsified and causes nextFreeFast too expensive
   170  		// to inline (Issue 22239).
   171  		want["runtime"] = append(want["runtime"], "nextFreeFast")
   172  	}
   173  	if runtime.GOARCH != "386" {
   174  		// As explained above, Ctz64 and Ctz32 are not Go code on 386.
   175  		// The same applies to Bswap32.
   176  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
   177  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
   178  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
   179  	}
   180  	if bits.UintSize == 64 {
   181  		// mix is only defined on 64-bit architectures
   182  		want["runtime"] = append(want["runtime"], "mix")
   183  	}
   184  
   185  	switch runtime.GOARCH {
   186  	case "386", "wasm", "arm":
   187  	default:
   188  		// TODO(mvdan): As explained in /test/inline_sync.go, some
   189  		// architectures don't have atomic intrinsics, so these go over
   190  		// the inlining budget. Move back to the main table once that
   191  		// problem is solved.
   192  		want["sync"] = []string{
   193  			"(*Mutex).Lock",
   194  			"(*Mutex).Unlock",
   195  			"(*RWMutex).RLock",
   196  			"(*RWMutex).RUnlock",
   197  			"(*Once).Do",
   198  		}
   199  	}
   200  
   201  	// Functions that must actually be inlined; they must have actual callers.
   202  	must := map[string]bool{
   203  		"compress/flate.byLiteral.Len":  true,
   204  		"compress/flate.byLiteral.Less": true,
   205  		"compress/flate.byLiteral.Swap": true,
   206  	}
   207  
   208  	notInlinedReason := make(map[string]string)
   209  	pkgs := make([]string, 0, len(want))
   210  	for pname, fnames := range want {
   211  		pkgs = append(pkgs, pname)
   212  		for _, fname := range fnames {
   213  			fullName := pname + "." + fname
   214  			if _, ok := notInlinedReason[fullName]; ok {
   215  				t.Errorf("duplicate func: %s", fullName)
   216  			}
   217  			notInlinedReason[fullName] = "unknown reason"
   218  		}
   219  	}
   220  
   221  	args := append([]string{"build", "-a", "-gcflags=all=-m -m", "-tags=math_big_pure_go"}, pkgs...)
   222  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
   223  	pr, pw := io.Pipe()
   224  	cmd.Stdout = pw
   225  	cmd.Stderr = pw
   226  	cmdErr := make(chan error, 1)
   227  	go func() {
   228  		cmdErr <- cmd.Run()
   229  		pw.Close()
   230  	}()
   231  	scanner := bufio.NewScanner(pr)
   232  	curPkg := ""
   233  	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
   234  	haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
   235  	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
   236  	for scanner.Scan() {
   237  		line := scanner.Text()
   238  		if strings.HasPrefix(line, "# ") {
   239  			curPkg = line[2:]
   240  			continue
   241  		}
   242  		if m := haveInlined.FindStringSubmatch(line); m != nil {
   243  			fname := m[1]
   244  			delete(notInlinedReason, curPkg+"."+fname)
   245  			continue
   246  		}
   247  		if m := canInline.FindStringSubmatch(line); m != nil {
   248  			fname := m[1]
   249  			fullname := curPkg + "." + fname
   250  			// If function must be inlined somewhere, being inlinable is not enough
   251  			if _, ok := must[fullname]; !ok {
   252  				delete(notInlinedReason, fullname)
   253  				continue
   254  			}
   255  		}
   256  		if m := cannotInline.FindStringSubmatch(line); m != nil {
   257  			fname, reason := m[1], m[2]
   258  			fullName := curPkg + "." + fname
   259  			if _, ok := notInlinedReason[fullName]; ok {
   260  				// cmd/compile gave us a reason why
   261  				notInlinedReason[fullName] = reason
   262  			}
   263  			continue
   264  		}
   265  	}
   266  	if err := <-cmdErr; err != nil {
   267  		t.Fatal(err)
   268  	}
   269  	if err := scanner.Err(); err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	for fullName, reason := range notInlinedReason {
   273  		t.Errorf("%s was not inlined: %s", fullName, reason)
   274  	}
   275  }
   276  

View as plain text