Home | History | Annotate | Download | only in gc
      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" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" {
    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 		// On MIPS64x, Ctz64 is not intrinsified and causes nextFreeFast too expensive to inline
    142 		// (Issue 22239).
    143 		want["runtime"] = append(want["runtime"], "nextFreeFast")
    144 	}
    145 	if runtime.GOARCH != "386" {
    146 		// As explained above, Ctz64 and Ctz32 are not Go code on 386.
    147 		// The same applies to Bswap32.
    148 		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
    149 		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
    150 		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
    151 	}
    152 	switch runtime.GOARCH {
    153 	case "amd64", "amd64p32", "arm64", "mips64", "mips64le", "ppc64", "ppc64le", "s390x":
    154 		// rotl_31 is only defined on 64-bit architectures
    155 		want["runtime"] = append(want["runtime"], "rotl_31")
    156 	}
    157 
    158 	notInlinedReason := make(map[string]string)
    159 	pkgs := make([]string, 0, len(want))
    160 	for pname, fnames := range want {
    161 		pkgs = append(pkgs, pname)
    162 		for _, fname := range fnames {
    163 			fullName := pname + "." + fname
    164 			if _, ok := notInlinedReason[fullName]; ok {
    165 				t.Errorf("duplicate func: %s", fullName)
    166 			}
    167 			notInlinedReason[fullName] = "unknown reason"
    168 		}
    169 	}
    170 
    171 	args := append([]string{"build", "-a", "-gcflags=all=-m -m"}, pkgs...)
    172 	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
    173 	pr, pw := io.Pipe()
    174 	cmd.Stdout = pw
    175 	cmd.Stderr = pw
    176 	cmdErr := make(chan error, 1)
    177 	go func() {
    178 		cmdErr <- cmd.Run()
    179 		pw.Close()
    180 	}()
    181 	scanner := bufio.NewScanner(pr)
    182 	curPkg := ""
    183 	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
    184 	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
    185 	for scanner.Scan() {
    186 		line := scanner.Text()
    187 		if strings.HasPrefix(line, "# ") {
    188 			curPkg = line[2:]
    189 			continue
    190 		}
    191 		if m := canInline.FindStringSubmatch(line); m != nil {
    192 			fname := m[1]
    193 			delete(notInlinedReason, curPkg+"."+fname)
    194 			continue
    195 		}
    196 		if m := cannotInline.FindStringSubmatch(line); m != nil {
    197 			fname, reason := m[1], m[2]
    198 			fullName := curPkg + "." + fname
    199 			if _, ok := notInlinedReason[fullName]; ok {
    200 				// cmd/compile gave us a reason why
    201 				notInlinedReason[fullName] = reason
    202 			}
    203 			continue
    204 		}
    205 	}
    206 	if err := <-cmdErr; err != nil {
    207 		t.Fatal(err)
    208 	}
    209 	if err := scanner.Err(); err != nil {
    210 		t.Fatal(err)
    211 	}
    212 	for fullName, reason := range notInlinedReason {
    213 		t.Errorf("%s was not inlined: %s", fullName, reason)
    214 	}
    215 }
    216