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 }