1// Copyright 2021 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 5package typecheck 6 7import ( 8 "cmd/compile/internal/base" 9 "cmd/compile/internal/ir" 10 "cmd/compile/internal/types" 11) 12 13// crawlExports crawls the type/object graph rooted at the given list of exported 14// objects. It descends through all parts of types and follows any methods on defined 15// types. Any functions that are found to be potentially callable by importers are 16// marked with ExportInline, so that iexport.go knows to re-export their inline body. 17// Also, any function or global referenced by a function marked by ExportInline() is 18// marked for export (whether its name is exported or not). 19func crawlExports(exports []*ir.Name) { 20 p := crawler{ 21 marked: make(map[*types.Type]bool), 22 embedded: make(map[*types.Type]bool), 23 generic: make(map[*types.Type]bool), 24 } 25 for _, n := range exports { 26 p.markObject(n) 27 } 28} 29 30type crawler struct { 31 marked map[*types.Type]bool // types already seen by markType 32 embedded map[*types.Type]bool // types already seen by markEmbed 33 generic map[*types.Type]bool // types already seen by markGeneric 34} 35 36// markObject visits a reachable object (function, method, global type, or global variable) 37func (p *crawler) markObject(n *ir.Name) { 38 if n.Op() == ir.ONAME && n.Class == ir.PFUNC { 39 p.markInlBody(n) 40 } 41 42 // If a declared type name is reachable, users can embed it in their 43 // own types, which makes even its unexported methods reachable. 44 if n.Op() == ir.OTYPE { 45 p.markEmbed(n.Type()) 46 } 47 48 p.markType(n.Type()) 49} 50 51// markType recursively visits types reachable from t to identify functions whose 52// inline bodies may be needed. For instantiated generic types, it visits the base 53// generic type, which has the relevant methods. 54func (p *crawler) markType(t *types.Type) { 55 if t.OrigSym() != nil { 56 // Convert to the base generic type. 57 t = t.OrigSym().Def.Type() 58 } 59 if p.marked[t] { 60 return 61 } 62 p.marked[t] = true 63 64 // If this is a defined type, mark all of its associated 65 // methods. Skip interface types because t.Methods contains 66 // only their unexpanded method set (i.e., exclusive of 67 // interface embeddings), and the switch statement below 68 // handles their full method set. 69 if t.Sym() != nil && t.Kind() != types.TINTER { 70 for _, m := range t.Methods().Slice() { 71 if types.IsExported(m.Sym.Name) { 72 p.markObject(m.Nname.(*ir.Name)) 73 } 74 } 75 } 76 77 // Recursively mark any types that can be produced given a 78 // value of type t: dereferencing a pointer; indexing or 79 // iterating over an array, slice, or map; receiving from a 80 // channel; accessing a struct field or interface method; or 81 // calling a function. 82 // 83 // Notably, we don't mark function parameter types, because 84 // the user already needs some way to construct values of 85 // those types. 86 switch t.Kind() { 87 case types.TPTR, types.TARRAY, types.TSLICE: 88 p.markType(t.Elem()) 89 90 case types.TCHAN: 91 if t.ChanDir().CanRecv() { 92 p.markType(t.Elem()) 93 } 94 95 case types.TMAP: 96 p.markType(t.Key()) 97 p.markType(t.Elem()) 98 99 case types.TSTRUCT: 100 if t.IsFuncArgStruct() { 101 break 102 } 103 for _, f := range t.FieldSlice() { 104 // Mark the type of a unexported field if it is a 105 // fully-instantiated type, since we create and instantiate 106 // the methods of any fully-instantiated type that we see 107 // during import (see end of typecheck.substInstType). 108 if types.IsExported(f.Sym.Name) || f.Embedded != 0 || 109 isPtrFullyInstantiated(f.Type) { 110 p.markType(f.Type) 111 } 112 } 113 114 case types.TFUNC: 115 for _, f := range t.Results().FieldSlice() { 116 p.markType(f.Type) 117 } 118 119 case types.TINTER: 120 for _, f := range t.AllMethods().Slice() { 121 if types.IsExported(f.Sym.Name) { 122 p.markType(f.Type) 123 } 124 } 125 126 case types.TTYPEPARAM: 127 // No other type that needs to be followed. 128 } 129} 130 131// markEmbed is similar to markType, but handles finding methods that 132// need to be re-exported because t can be embedded in user code 133// (possibly transitively). 134func (p *crawler) markEmbed(t *types.Type) { 135 if t.IsPtr() { 136 // Defined pointer type; not allowed to embed anyway. 137 if t.Sym() != nil { 138 return 139 } 140 t = t.Elem() 141 } 142 143 if t.OrigSym() != nil { 144 // Convert to the base generic type. 145 t = t.OrigSym().Def.Type() 146 } 147 148 if p.embedded[t] { 149 return 150 } 151 p.embedded[t] = true 152 153 // If t is a defined type, then re-export all of its methods. Unlike 154 // in markType, we include even unexported methods here, because we 155 // still need to generate wrappers for them, even if the user can't 156 // refer to them directly. 157 if t.Sym() != nil && t.Kind() != types.TINTER { 158 for _, m := range t.Methods().Slice() { 159 p.markObject(m.Nname.(*ir.Name)) 160 } 161 } 162 163 // If t is a struct, recursively visit its embedded fields. 164 if t.IsStruct() { 165 for _, f := range t.FieldSlice() { 166 if f.Embedded != 0 { 167 p.markEmbed(f.Type) 168 } 169 } 170 } 171} 172 173// markGeneric takes an instantiated type or a base generic type t, and 174// marks all the methods of the base generic type of t. If a base generic 175// type is written to export file, even if not explicitly marked for export, 176// all of its methods need to be available for instantiation if needed. 177func (p *crawler) markGeneric(t *types.Type) { 178 if t.IsPtr() { 179 t = t.Elem() 180 } 181 if t.OrigSym() != nil { 182 // Convert to the base generic type. 183 t = t.OrigSym().Def.Type() 184 } 185 if p.generic[t] { 186 return 187 } 188 p.generic[t] = true 189 190 if t.Sym() != nil && t.Kind() != types.TINTER { 191 for _, m := range t.Methods().Slice() { 192 p.markObject(m.Nname.(*ir.Name)) 193 } 194 } 195} 196 197// markInlBody marks n's inline body for export and recursively 198// ensures all called functions are marked too. 199func (p *crawler) markInlBody(n *ir.Name) { 200 if n == nil { 201 return 202 } 203 if n.Op() != ir.ONAME || n.Class != ir.PFUNC { 204 base.Fatalf("markInlBody: unexpected %v, %v, %v", n, n.Op(), n.Class) 205 } 206 fn := n.Func 207 if fn == nil { 208 base.Fatalf("markInlBody: missing Func on %v", n) 209 } 210 if !HaveInlineBody(fn) { 211 return 212 } 213 214 if fn.ExportInline() { 215 return 216 } 217 fn.SetExportInline(true) 218 219 ImportedBody(fn) 220 221 var doFlood func(n ir.Node) 222 doFlood = func(n ir.Node) { 223 t := n.Type() 224 if t != nil { 225 if t.HasTParam() || t.IsFullyInstantiated() { 226 p.markGeneric(t) 227 } 228 if base.Debug.Unified == 0 { 229 // If a method of un-exported type is promoted and accessible by 230 // embedding in an exported type, it makes that type reachable. 231 // 232 // Example: 233 // 234 // type t struct {} 235 // func (t) M() {} 236 // 237 // func F() interface{} { return struct{ t }{} } 238 // 239 // We generate the wrapper for "struct{ t }".M, and inline call 240 // to "struct{ t }".M, which makes "t.M" reachable. 241 if t.IsStruct() { 242 for _, f := range t.FieldSlice() { 243 if f.Embedded != 0 { 244 p.markEmbed(f.Type) 245 } 246 } 247 } 248 } 249 } 250 251 switch n.Op() { 252 case ir.OMETHEXPR, ir.ODOTMETH: 253 p.markInlBody(ir.MethodExprName(n)) 254 case ir.ONAME: 255 n := n.(*ir.Name) 256 switch n.Class { 257 case ir.PFUNC: 258 p.markInlBody(n) 259 Export(n) 260 case ir.PEXTERN: 261 Export(n) 262 } 263 case ir.OMETHVALUE: 264 // Okay, because we don't yet inline indirect 265 // calls to method values. 266 case ir.OCLOSURE: 267 // VisitList doesn't visit closure bodies, so force a 268 // recursive call to VisitList on the body of the closure. 269 ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doFlood) 270 } 271 } 272 273 // Recursively identify all referenced functions for 274 // reexport. We want to include even non-called functions, 275 // because after inlining they might be callable. 276 ir.VisitList(fn.Inl.Body, doFlood) 277} 278 279// isPtrFullyInstantiated returns true if t is a fully-instantiated type, or it is a 280// pointer to a fully-instantiated type. 281func isPtrFullyInstantiated(t *types.Type) bool { 282 return t.IsPtr() && t.Elem().IsFullyInstantiated() || 283 t.IsFullyInstantiated() 284} 285