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 }