github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/compile/internal/gc/inl_test.go (about)

     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 gc
     6  
     7  import (
     8  	"bufio"
     9  	"internal/testenv"
    10  	"io"
    11  	"os/exec"
    12  	"regexp"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  // TestIntendedInlining tests that specific runtime functions are inlined.
    19  // This allows refactoring for code clarity and re-use without fear that
    20  // changes to the compiler will cause silent performance regressions.
    21  func TestIntendedInlining(t *testing.T) {
    22  	if testing.Short() && testenv.Builder() == "" {
    23  		t.Skip("skipping in short mode")
    24  	}
    25  	testenv.MustHaveGoRun(t)
    26  	t.Parallel()
    27  
    28  	// want is the list of function names (by package) that should
    29  	// be inlinable. If they have no callers in thier packages, they
    30  	// might not actually be inlined anywhere.
    31  	want := map[string][]string{
    32  		"runtime": {
    33  			// TODO(mvdan): enable these once mid-stack
    34  			// inlining is available
    35  			// "adjustctxt",
    36  
    37  			"add",
    38  			"acquirem",
    39  			"add1",
    40  			"addb",
    41  			"adjustpanics",
    42  			"adjustpointer",
    43  			"bucketMask",
    44  			"bucketShift",
    45  			"chanbuf",
    46  			"deferArgs",
    47  			"deferclass",
    48  			"evacuated",
    49  			"fastlog2",
    50  			"fastrand",
    51  			"float64bits",
    52  			"funcPC",
    53  			"getArgInfoFast",
    54  			"getm",
    55  			"isDirectIface",
    56  			"itabHashFunc",
    57  			"noescape",
    58  			"readUnaligned32",
    59  			"readUnaligned64",
    60  			"releasem",
    61  			"round",
    62  			"roundupsize",
    63  			"stackmapdata",
    64  			"stringStructOf",
    65  			"subtract1",
    66  			"subtractb",
    67  			"tophash",
    68  			"totaldefersize",
    69  			"(*bmap).keys",
    70  			"(*bmap).overflow",
    71  			"(*waitq).enqueue",
    72  
    73  			// GC-related ones
    74  			"cgoInRange",
    75  			"gclinkptr.ptr",
    76  			"guintptr.ptr",
    77  			"heapBits.bits",
    78  			"heapBits.isPointer",
    79  			"heapBits.morePointers",
    80  			"heapBits.next",
    81  			"heapBitsForAddr",
    82  			"markBits.isMarked",
    83  			"muintptr.ptr",
    84  			"puintptr.ptr",
    85  			"spanOf",
    86  			"spanOfUnchecked",
    87  			"(*gcWork).putFast",
    88  			"(*gcWork).tryGetFast",
    89  			"(*guintptr).set",
    90  			"(*markBits).advance",
    91  			"(*mspan).allocBitsForIndex",
    92  			"(*mspan).base",
    93  			"(*mspan).markBitsForBase",
    94  			"(*mspan).markBitsForIndex",
    95  			"(*muintptr).set",
    96  			"(*puintptr).set",
    97  		},
    98  		"runtime/internal/sys": {},
    99  		"runtime/internal/math": {
   100  			"MulUintptr",
   101  		},
   102  		"bytes": {
   103  			"(*Buffer).Bytes",
   104  			"(*Buffer).Cap",
   105  			"(*Buffer).Len",
   106  			"(*Buffer).Next",
   107  			"(*Buffer).Read",
   108  			"(*Buffer).ReadByte",
   109  			"(*Buffer).Reset",
   110  			"(*Buffer).String",
   111  			"(*Buffer).UnreadByte",
   112  			"(*Buffer).tryGrowByReslice",
   113  		},
   114  		"compress/flate": {
   115  			"byLiteral.Len",
   116  			"byLiteral.Less",
   117  			"byLiteral.Swap",
   118  		},
   119  		"unicode/utf8": {
   120  			"FullRune",
   121  			"FullRuneInString",
   122  			"RuneLen",
   123  			"ValidRune",
   124  		},
   125  		"reflect": {
   126  			"Value.CanAddr",
   127  			"Value.CanSet",
   128  			"Value.IsValid",
   129  			"add",
   130  			"align",
   131  			"flag.kind",
   132  			"flag.ro",
   133  
   134  			// TODO: these use panic, need mid-stack
   135  			// inlining
   136  			// "Value.CanInterface",
   137  			// "Value.pointer",
   138  			// "flag.mustBe",
   139  			// "flag.mustBeAssignable",
   140  			// "flag.mustBeExported",
   141  		},
   142  		"regexp": {
   143  			"(*bitState).push",
   144  		},
   145  		"math/big": {
   146  			"bigEndianWord",
   147  		},
   148  	}
   149  
   150  	if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" {
   151  		// nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
   152  		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
   153  		// On MIPS64x, Ctz64 is not intrinsified and causes nextFreeFast too expensive to inline
   154  		// (Issue 22239).
   155  		want["runtime"] = append(want["runtime"], "nextFreeFast")
   156  	}
   157  	if runtime.GOARCH != "386" {
   158  		// As explained above, Ctz64 and Ctz32 are not Go code on 386.
   159  		// The same applies to Bswap32.
   160  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
   161  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
   162  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
   163  	}
   164  	switch runtime.GOARCH {
   165  	case "amd64", "amd64p32", "arm64", "mips64", "mips64le", "ppc64", "ppc64le", "s390x":
   166  		// rotl_31 is only defined on 64-bit architectures
   167  		want["runtime"] = append(want["runtime"], "rotl_31")
   168  	}
   169  
   170  	// Functions that must actually be inlined; they must have actual callers.
   171  	must := map[string]bool{
   172  		"compress/flate.byLiteral.Len":  true,
   173  		"compress/flate.byLiteral.Less": true,
   174  		"compress/flate.byLiteral.Swap": true,
   175  	}
   176  
   177  	notInlinedReason := make(map[string]string)
   178  	pkgs := make([]string, 0, len(want))
   179  	for pname, fnames := range want {
   180  		pkgs = append(pkgs, pname)
   181  		for _, fname := range fnames {
   182  			fullName := pname + "." + fname
   183  			if _, ok := notInlinedReason[fullName]; ok {
   184  				t.Errorf("duplicate func: %s", fullName)
   185  			}
   186  			notInlinedReason[fullName] = "unknown reason"
   187  		}
   188  	}
   189  
   190  	args := append([]string{"build", "-a", "-gcflags=all=-m -m"}, pkgs...)
   191  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
   192  	pr, pw := io.Pipe()
   193  	cmd.Stdout = pw
   194  	cmd.Stderr = pw
   195  	cmdErr := make(chan error, 1)
   196  	go func() {
   197  		cmdErr <- cmd.Run()
   198  		pw.Close()
   199  	}()
   200  	scanner := bufio.NewScanner(pr)
   201  	curPkg := ""
   202  	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
   203  	haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
   204  	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
   205  	for scanner.Scan() {
   206  		line := scanner.Text()
   207  		if strings.HasPrefix(line, "# ") {
   208  			curPkg = line[2:]
   209  			continue
   210  		}
   211  		if m := haveInlined.FindStringSubmatch(line); m != nil {
   212  			fname := m[1]
   213  			delete(notInlinedReason, curPkg+"."+fname)
   214  			continue
   215  		}
   216  		if m := canInline.FindStringSubmatch(line); m != nil {
   217  			fname := m[1]
   218  			fullname := curPkg + "." + fname
   219  			// If function must be inlined somewhere, beeing inlinable is not enough
   220  			if _, ok := must[fullname]; !ok {
   221  				delete(notInlinedReason, fullname)
   222  				continue
   223  			}
   224  		}
   225  		if m := cannotInline.FindStringSubmatch(line); m != nil {
   226  			fname, reason := m[1], m[2]
   227  			fullName := curPkg + "." + fname
   228  			if _, ok := notInlinedReason[fullName]; ok {
   229  				// cmd/compile gave us a reason why
   230  				notInlinedReason[fullName] = reason
   231  			}
   232  			continue
   233  		}
   234  	}
   235  	if err := <-cmdErr; err != nil {
   236  		t.Fatal(err)
   237  	}
   238  	if err := scanner.Err(); err != nil {
   239  		t.Fatal(err)
   240  	}
   241  	for fullName, reason := range notInlinedReason {
   242  		t.Errorf("%s was not inlined: %s", fullName, reason)
   243  	}
   244  }