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 }