github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/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 inlined.
    30  	want := map[string][]string{
    31  		"runtime": {
    32  			// TODO(mvdan): enable these once mid-stack
    33  			// inlining is available
    34  			// "adjustctxt",
    35  
    36  			"add",
    37  			"acquirem",
    38  			"add1",
    39  			"addb",
    40  			"adjustpanics",
    41  			"adjustpointer",
    42  			"bucketMask",
    43  			"bucketShift",
    44  			"chanbuf",
    45  			"deferArgs",
    46  			"deferclass",
    47  			"evacuated",
    48  			"fastlog2",
    49  			"fastrand",
    50  			"float64bits",
    51  			"funcPC",
    52  			"getm",
    53  			"isDirectIface",
    54  			"itabHashFunc",
    55  			"maxSliceCap",
    56  			"noescape",
    57  			"readUnaligned32",
    58  			"readUnaligned64",
    59  			"releasem",
    60  			"round",
    61  			"roundupsize",
    62  			"selectsize",
    63  			"stringStructOf",
    64  			"subtract1",
    65  			"subtractb",
    66  			"tophash",
    67  			"totaldefersize",
    68  			"(*bmap).keys",
    69  			"(*bmap).overflow",
    70  			"(*waitq).enqueue",
    71  
    72  			// GC-related ones
    73  			"cgoInRange",
    74  			"gclinkptr.ptr",
    75  			"guintptr.ptr",
    76  			"heapBits.bits",
    77  			"heapBits.isPointer",
    78  			"heapBits.morePointers",
    79  			"heapBits.next",
    80  			"heapBitsForAddr",
    81  			"inheap",
    82  			"markBits.isMarked",
    83  			"muintptr.ptr",
    84  			"puintptr.ptr",
    85  			"spanOfUnchecked",
    86  			"(*gcWork).putFast",
    87  			"(*gcWork).tryGetFast",
    88  			"(*guintptr).set",
    89  			"(*markBits).advance",
    90  			"(*mspan).allocBitsForIndex",
    91  			"(*mspan).base",
    92  			"(*mspan).markBitsForBase",
    93  			"(*mspan).markBitsForIndex",
    94  			"(*muintptr).set",
    95  			"(*puintptr).set",
    96  		},
    97  		"runtime/internal/sys": {},
    98  		"bytes": {
    99  			"(*Buffer).Bytes",
   100  			"(*Buffer).Cap",
   101  			"(*Buffer).Len",
   102  			"(*Buffer).Next",
   103  			"(*Buffer).Read",
   104  			"(*Buffer).ReadByte",
   105  			"(*Buffer).Reset",
   106  			"(*Buffer).String",
   107  			"(*Buffer).UnreadByte",
   108  			"(*Buffer).tryGrowByReslice",
   109  		},
   110  		"unicode/utf8": {
   111  			"FullRune",
   112  			"FullRuneInString",
   113  			"RuneLen",
   114  			"ValidRune",
   115  		},
   116  		"reflect": {
   117  			"Value.CanAddr",
   118  			"Value.CanSet",
   119  			"Value.IsValid",
   120  			"add",
   121  			"align",
   122  			"flag.kind",
   123  			"flag.ro",
   124  
   125  			// TODO: these use panic, need mid-stack
   126  			// inlining
   127  			// "Value.CanInterface",
   128  			// "Value.pointer",
   129  			// "flag.mustBe",
   130  			// "flag.mustBeAssignable",
   131  			// "flag.mustBeExported",
   132  		},
   133  		"regexp": {
   134  			"(*bitState).push",
   135  		},
   136  	}
   137  
   138  	if runtime.GOARCH != "386" {
   139  		// nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
   140  		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
   141  		// So check for it only on non-386 platforms.
   142  		want["runtime"] = append(want["runtime"], "nextFreeFast")
   143  		// As explained above, Ctz64 and Ctz32 are not Go code on 386.
   144  		// The same applies to Bswap32.
   145  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
   146  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
   147  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
   148  	}
   149  	switch runtime.GOARCH {
   150  	case "amd64", "amd64p32", "arm64", "mips64", "mips64le", "ppc64", "ppc64le", "s390x":
   151  		// rotl_31 is only defined on 64-bit architectures
   152  		want["runtime"] = append(want["runtime"], "rotl_31")
   153  	}
   154  
   155  	notInlinedReason := make(map[string]string)
   156  	pkgs := make([]string, 0, len(want))
   157  	for pname, fnames := range want {
   158  		pkgs = append(pkgs, pname)
   159  		for _, fname := range fnames {
   160  			fullName := pname + "." + fname
   161  			if _, ok := notInlinedReason[fullName]; ok {
   162  				t.Errorf("duplicate func: %s", fullName)
   163  			}
   164  			notInlinedReason[fullName] = "unknown reason"
   165  		}
   166  	}
   167  
   168  	args := append([]string{"build", "-a", "-gcflags=-m -m"}, pkgs...)
   169  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
   170  	pr, pw := io.Pipe()
   171  	cmd.Stdout = pw
   172  	cmd.Stderr = pw
   173  	cmdErr := make(chan error, 1)
   174  	go func() {
   175  		cmdErr <- cmd.Run()
   176  		pw.Close()
   177  	}()
   178  	scanner := bufio.NewScanner(pr)
   179  	curPkg := ""
   180  	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
   181  	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
   182  	for scanner.Scan() {
   183  		line := scanner.Text()
   184  		if strings.HasPrefix(line, "# ") {
   185  			curPkg = line[2:]
   186  			continue
   187  		}
   188  		if m := canInline.FindStringSubmatch(line); m != nil {
   189  			fname := m[1]
   190  			delete(notInlinedReason, curPkg+"."+fname)
   191  			continue
   192  		}
   193  		if m := cannotInline.FindStringSubmatch(line); m != nil {
   194  			fname, reason := m[1], m[2]
   195  			fullName := curPkg + "." + fname
   196  			if _, ok := notInlinedReason[fullName]; ok {
   197  				// cmd/compile gave us a reason why
   198  				notInlinedReason[fullName] = reason
   199  			}
   200  			continue
   201  		}
   202  	}
   203  	if err := <-cmdErr; err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	if err := scanner.Err(); err != nil {
   207  		t.Fatal(err)
   208  	}
   209  	for fullName, reason := range notInlinedReason {
   210  		t.Errorf("%s was not inlined: %s", fullName, reason)
   211  	}
   212  }