golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/go/ssa/stdlib_test.go (about) 1 // Copyright 2013 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 // Incomplete source tree on Android. 6 7 //go:build !android 8 // +build !android 9 10 package ssa_test 11 12 // This file runs the SSA builder in sanity-checking mode on all 13 // packages beneath $GOROOT and prints some summary information. 14 // 15 // Run with "go test -cpu=8 to" set GOMAXPROCS. 16 17 import ( 18 "go/ast" 19 "go/token" 20 "go/types" 21 "runtime" 22 "testing" 23 "time" 24 25 "golang.org/x/tools/go/packages" 26 "golang.org/x/tools/go/ssa" 27 "golang.org/x/tools/go/ssa/ssautil" 28 "golang.org/x/tools/internal/testenv" 29 ) 30 31 func bytesAllocated() uint64 { 32 runtime.GC() 33 var stats runtime.MemStats 34 runtime.ReadMemStats(&stats) 35 return stats.Alloc 36 } 37 38 // TestStdlib loads the entire standard library and its tools. 39 // 40 // Apart from a small number of internal packages that are not 41 // returned by the 'std' query, the set is essentially transitively 42 // closed, so marginal per-dependency costs are invisible. 43 func TestStdlib(t *testing.T) { 44 testLoad(t, 500, "std", "cmd") 45 } 46 47 // TestNetHTTP builds a single SSA package but not its dependencies. 48 // It may help reveal costs related to dependencies (e.g. unnecessary building). 49 func TestNetHTTP(t *testing.T) { 50 testLoad(t, 120, "net/http") 51 } 52 53 func testLoad(t *testing.T, minPkgs int, patterns ...string) { 54 // Note: most of the commentary below applies to TestStdlib. 55 56 if testing.Short() { 57 t.Skip("skipping in short mode; too slow (https://golang.org/issue/14113)") // ~5s 58 } 59 testenv.NeedsTool(t, "go") 60 61 // Load, parse and type-check the program. 62 t0 := time.Now() 63 alloc0 := bytesAllocated() 64 65 cfg := &packages.Config{Mode: packages.LoadSyntax} 66 pkgs, err := packages.Load(cfg, patterns...) 67 if err != nil { 68 t.Fatal(err) 69 } 70 71 t1 := time.Now() 72 alloc1 := bytesAllocated() 73 74 // Create SSA packages. 75 var mode ssa.BuilderMode 76 // Comment out these lines during benchmarking. Approx SSA build costs are noted. 77 mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time 78 mode |= ssa.GlobalDebug // +30% space, +18% time 79 mode |= ssa.InstantiateGenerics // + 0% space, + 2% time (unlikely to reproduce outside of stdlib) 80 prog, _ := ssautil.Packages(pkgs, mode) 81 82 t2 := time.Now() 83 84 // Build SSA. 85 prog.Build() 86 87 t3 := time.Now() 88 alloc3 := bytesAllocated() 89 90 // Sanity check to ensure we haven't dropped large numbers of packages. 91 numPkgs := len(prog.AllPackages()) 92 if numPkgs < minPkgs { 93 t.Errorf("Loaded only %d packages, want at least %d", numPkgs, minPkgs) 94 } 95 96 // Keep pkgs reachable until after we've measured memory usage. 97 if len(pkgs) == 0 { 98 panic("unreachable") 99 } 100 101 srcFuncs := srcFunctions(prog, pkgs) 102 allFuncs := ssautil.AllFunctions(prog) 103 104 // The assertion below is not valid if the program contains 105 // variants of the same package, such as the test variants 106 // (e.g. package p as compiled for test executable x) obtained 107 // when cfg.Tests=true. Profile-guided optimization may 108 // lead to similar variation for non-test executables. 109 // 110 // Ideally, the test would assert that all functions within 111 // each executable (more generally: within any singly rooted 112 // transitively closed subgraph of the import graph) have 113 // distinct names, but that isn't so easy to compute efficiently. 114 // Disabling for now. 115 if false { 116 // Check that all non-synthetic functions have distinct names. 117 // Synthetic wrappers for exported methods should be distinct too, 118 // except for unexported ones (explained at (*Function).RelString). 119 byName := make(map[string]*ssa.Function) 120 for fn := range allFuncs { 121 if fn.Synthetic == "" || ast.IsExported(fn.Name()) { 122 str := fn.String() 123 prev := byName[str] 124 byName[str] = fn 125 if prev != nil { 126 t.Errorf("%s: duplicate function named %s", 127 prog.Fset.Position(fn.Pos()), str) 128 t.Errorf("%s: (previously defined here)", 129 prog.Fset.Position(prev.Pos())) 130 } 131 } 132 } 133 } 134 135 // Dump some statistics. 136 var numInstrs int 137 for fn := range allFuncs { 138 for _, b := range fn.Blocks { 139 numInstrs += len(b.Instrs) 140 } 141 } 142 143 // determine line count 144 var lineCount int 145 prog.Fset.Iterate(func(f *token.File) bool { 146 lineCount += f.LineCount() 147 return true 148 }) 149 150 // NB: when benchmarking, don't forget to clear the debug + 151 // sanity builder flags for better performance. 152 153 t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) 154 t.Log("#Source lines: ", lineCount) 155 t.Log("Load/parse/typecheck: ", t1.Sub(t0)) 156 t.Log("SSA create: ", t2.Sub(t1)) 157 t.Log("SSA build: ", t3.Sub(t2)) 158 159 // SSA stats: 160 t.Log("#Packages: ", numPkgs) 161 t.Log("#SrcFunctions: ", len(srcFuncs)) 162 t.Log("#AllFunctions: ", len(allFuncs)) 163 t.Log("#Instructions: ", numInstrs) 164 t.Log("#MB AST+types: ", int64(alloc1-alloc0)/1e6) 165 t.Log("#MB SSA: ", int64(alloc3-alloc1)/1e6) 166 } 167 168 // srcFunctions gathers all ssa.Functions corresponding to syntax. 169 // (Includes generics but excludes instances and all wrappers.) 170 // 171 // This is essentially identical to the SrcFunctions logic in 172 // go/analysis/passes/buildssa. 173 func srcFunctions(prog *ssa.Program, pkgs []*packages.Package) (res []*ssa.Function) { 174 var addSrcFunc func(fn *ssa.Function) 175 addSrcFunc = func(fn *ssa.Function) { 176 res = append(res, fn) 177 for _, anon := range fn.AnonFuncs { 178 addSrcFunc(anon) 179 } 180 } 181 for _, pkg := range pkgs { 182 for _, file := range pkg.Syntax { 183 for _, decl := range file.Decls { 184 if decl, ok := decl.(*ast.FuncDecl); ok { 185 obj := pkg.TypesInfo.Defs[decl.Name].(*types.Func) 186 if obj == nil { 187 panic("nil *Func") 188 } 189 addSrcFunc(prog.FuncValue(obj)) 190 } 191 } 192 } 193 } 194 return res 195 }