github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/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  	"github.com/gagliardetto/golang-go/not-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 runtime 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  			"deferArgs",
    46  			"deferclass",
    47  			"evacuated",
    48  			"fastlog2",
    49  			"fastrand",
    50  			"float64bits",
    51  			"funcPC",
    52  			"getArgInfoFast",
    53  			"getm",
    54  			"isDirectIface",
    55  			"itabHashFunc",
    56  			"noescape",
    57  			"pcvalueCacheKey",
    58  			"readUnaligned32",
    59  			"readUnaligned64",
    60  			"releasem",
    61  			"roundupsize",
    62  			"stackmapdata",
    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  			"markBits.isMarked",
    82  			"muintptr.ptr",
    83  			"puintptr.ptr",
    84  			"spanOf",
    85  			"spanOfUnchecked",
    86  			//"(*gcWork).putFast", // TODO(austin): For debugging #27993
    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  		"runtime/internal/math": {
    99  			"MulUintptr",
   100  		},
   101  		"bytes": {
   102  			"(*Buffer).Bytes",
   103  			"(*Buffer).Cap",
   104  			"(*Buffer).Len",
   105  			"(*Buffer).Grow",
   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  		"encoding/base64": {
   120  			"assemble32",
   121  			"assemble64",
   122  		},
   123  		"unicode/utf8": {
   124  			"FullRune",
   125  			"FullRuneInString",
   126  			"RuneLen",
   127  			"ValidRune",
   128  		},
   129  		"reflect": {
   130  			"Value.CanAddr",
   131  			"Value.CanSet",
   132  			"Value.CanInterface",
   133  			"Value.IsValid",
   134  			"Value.pointer",
   135  			"add",
   136  			"align",
   137  			"flag.mustBe",
   138  			"flag.mustBeAssignable",
   139  			"flag.mustBeExported",
   140  			"flag.kind",
   141  			"flag.ro",
   142  		},
   143  		"regexp": {
   144  			"(*bitState).push",
   145  		},
   146  		"math/big": {
   147  			"bigEndianWord",
   148  			// The following functions require the math_big_pure_go build tag.
   149  			"addVW",
   150  			"subVW",
   151  		},
   152  		"math/rand": {
   153  			"(*rngSource).Int63",
   154  			"(*rngSource).Uint64",
   155  		},
   156  	}
   157  
   158  	if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" {
   159  		// nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
   160  		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
   161  		// On MIPS64x, Ctz64 is not intrinsified and causes nextFreeFast too expensive to inline
   162  		// (Issue 22239).
   163  		want["runtime"] = append(want["runtime"], "nextFreeFast")
   164  	}
   165  	if runtime.GOARCH != "386" {
   166  		// As explained above, Ctz64 and Ctz32 are not Go code on 386.
   167  		// The same applies to Bswap32.
   168  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
   169  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
   170  		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
   171  	}
   172  	if bits.UintSize == 64 {
   173  		// rotl_31 is only defined on 64-bit architectures
   174  		want["runtime"] = append(want["runtime"], "rotl_31")
   175  	}
   176  
   177  	switch runtime.GOARCH {
   178  	case "386", "wasm", "arm", "riscv64":
   179  	default:
   180  		// TODO(mvdan): As explained in /test/inline_sync.go, some
   181  		// architectures don't have atomic intrinsics, so these go over
   182  		// the inlining budget. Move back to the main table once that
   183  		// problem is solved.
   184  		want["sync"] = []string{
   185  			"(*Mutex).Lock",
   186  			"(*Mutex).Unlock",
   187  			"(*RWMutex).RLock",
   188  			"(*RWMutex).RUnlock",
   189  			"(*Once).Do",
   190  		}
   191  	}
   192  
   193  	// Functions that must actually be inlined; they must have actual callers.
   194  	must := map[string]bool{
   195  		"compress/flate.byLiteral.Len":  true,
   196  		"compress/flate.byLiteral.Less": true,
   197  		"compress/flate.byLiteral.Swap": true,
   198  	}
   199  
   200  	notInlinedReason := make(map[string]string)
   201  	pkgs := make([]string, 0, len(want))
   202  	for pname, fnames := range want {
   203  		pkgs = append(pkgs, pname)
   204  		for _, fname := range fnames {
   205  			fullName := pname + "." + fname
   206  			if _, ok := notInlinedReason[fullName]; ok {
   207  				t.Errorf("duplicate func: %s", fullName)
   208  			}
   209  			notInlinedReason[fullName] = "unknown reason"
   210  		}
   211  	}
   212  
   213  	args := append([]string{"build", "-a", "-gcflags=all=-m -m", "-tags=math_big_pure_go"}, pkgs...)
   214  	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
   215  	pr, pw := io.Pipe()
   216  	cmd.Stdout = pw
   217  	cmd.Stderr = pw
   218  	cmdErr := make(chan error, 1)
   219  	go func() {
   220  		cmdErr <- cmd.Run()
   221  		pw.Close()
   222  	}()
   223  	scanner := bufio.NewScanner(pr)
   224  	curPkg := ""
   225  	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
   226  	haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
   227  	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
   228  	for scanner.Scan() {
   229  		line := scanner.Text()
   230  		if strings.HasPrefix(line, "# ") {
   231  			curPkg = line[2:]
   232  			continue
   233  		}
   234  		if m := haveInlined.FindStringSubmatch(line); m != nil {
   235  			fname := m[1]
   236  			delete(notInlinedReason, curPkg+"."+fname)
   237  			continue
   238  		}
   239  		if m := canInline.FindStringSubmatch(line); m != nil {
   240  			fname := m[1]
   241  			fullname := curPkg + "." + fname
   242  			// If function must be inlined somewhere, being inlinable is not enough
   243  			if _, ok := must[fullname]; !ok {
   244  				delete(notInlinedReason, fullname)
   245  				continue
   246  			}
   247  		}
   248  		if m := cannotInline.FindStringSubmatch(line); m != nil {
   249  			fname, reason := m[1], m[2]
   250  			fullName := curPkg + "." + fname
   251  			if _, ok := notInlinedReason[fullName]; ok {
   252  				// cmd/compile gave us a reason why
   253  				notInlinedReason[fullName] = reason
   254  			}
   255  			continue
   256  		}
   257  	}
   258  	if err := <-cmdErr; err != nil {
   259  		t.Fatal(err)
   260  	}
   261  	if err := scanner.Err(); err != nil {
   262  		t.Fatal(err)
   263  	}
   264  	for fullName, reason := range notInlinedReason {
   265  		t.Errorf("%s was not inlined: %s", fullName, reason)
   266  	}
   267  }