github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/types2/mono.go (about) 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 5 package types2 6 7 import ( 8 "github.com/go-asm/go/cmd/compile/syntax" 9 . "github.com/go-asm/go/types/errors" 10 ) 11 12 // This file implements a check to validate that a Go package doesn't 13 // have unbounded recursive instantiation, which is not compatible 14 // with compilers using static instantiation (such as 15 // monomorphization). 16 // 17 // It implements a sort of "type flow" analysis by detecting which 18 // type parameters are instantiated with other type parameters (or 19 // types derived thereof). A package cannot be statically instantiated 20 // if the graph has any cycles involving at least one derived type. 21 // 22 // Concretely, we construct a directed, weighted graph. Vertices are 23 // used to represent type parameters as well as some defined 24 // types. Edges are used to represent how types depend on each other: 25 // 26 // * Everywhere a type-parameterized function or type is instantiated, 27 // we add edges to each type parameter from the vertices (if any) 28 // representing each type parameter or defined type referenced by 29 // the type argument. If the type argument is just the referenced 30 // type itself, then the edge has weight 0, otherwise 1. 31 // 32 // * For every defined type declared within a type-parameterized 33 // function or method, we add an edge of weight 1 to the defined 34 // type from each ambient type parameter. 35 // 36 // For example, given: 37 // 38 // func f[A, B any]() { 39 // type T int 40 // f[T, map[A]B]() 41 // } 42 // 43 // we construct vertices representing types A, B, and T. Because of 44 // declaration "type T int", we construct edges T<-A and T<-B with 45 // weight 1; and because of instantiation "f[T, map[A]B]" we construct 46 // edges A<-T with weight 0, and B<-A and B<-B with weight 1. 47 // 48 // Finally, we look for any positive-weight cycles. Zero-weight cycles 49 // are allowed because static instantiation will reach a fixed point. 50 51 type monoGraph struct { 52 vertices []monoVertex 53 edges []monoEdge 54 55 // canon maps method receiver type parameters to their respective 56 // receiver type's type parameters. 57 canon map[*TypeParam]*TypeParam 58 59 // nameIdx maps a defined type or (canonical) type parameter to its 60 // vertex index. 61 nameIdx map[*TypeName]int 62 } 63 64 type monoVertex struct { 65 weight int // weight of heaviest known path to this vertex 66 pre int // previous edge (if any) in the above path 67 len int // length of the above path 68 69 // obj is the defined type or type parameter represented by this 70 // vertex. 71 obj *TypeName 72 } 73 74 type monoEdge struct { 75 dst, src int 76 weight int 77 78 pos syntax.Pos 79 typ Type 80 } 81 82 func (check *Checker) monomorph() { 83 // We detect unbounded instantiation cycles using a variant of 84 // Bellman-Ford's algorithm. Namely, instead of always running |V| 85 // iterations, we run until we either reach a fixed point or we've 86 // found a path of length |V|. This allows us to terminate earlier 87 // when there are no cycles, which should be the common case. 88 89 again := true 90 for again { 91 again = false 92 93 for i, edge := range check.mono.edges { 94 src := &check.mono.vertices[edge.src] 95 dst := &check.mono.vertices[edge.dst] 96 97 // N.B., we're looking for the greatest weight paths, unlike 98 // typical Bellman-Ford. 99 w := src.weight + edge.weight 100 if w <= dst.weight { 101 continue 102 } 103 104 dst.pre = i 105 dst.len = src.len + 1 106 if dst.len == len(check.mono.vertices) { 107 check.reportInstanceLoop(edge.dst) 108 return 109 } 110 111 dst.weight = w 112 again = true 113 } 114 } 115 } 116 117 func (check *Checker) reportInstanceLoop(v int) { 118 var stack []int 119 seen := make([]bool, len(check.mono.vertices)) 120 121 // We have a path that contains a cycle and ends at v, but v may 122 // only be reachable from the cycle, not on the cycle itself. We 123 // start by walking backwards along the path until we find a vertex 124 // that appears twice. 125 for !seen[v] { 126 stack = append(stack, v) 127 seen[v] = true 128 v = check.mono.edges[check.mono.vertices[v].pre].src 129 } 130 131 // Trim any vertices we visited before visiting v the first 132 // time. Since v is the first vertex we found within the cycle, any 133 // vertices we visited earlier cannot be part of the cycle. 134 for stack[0] != v { 135 stack = stack[1:] 136 } 137 138 // TODO(mdempsky): Pivot stack so we report the cycle from the top? 139 140 var err error_ 141 err.code = InvalidInstanceCycle 142 obj0 := check.mono.vertices[v].obj 143 err.errorf(obj0, "instantiation cycle:") 144 145 qf := RelativeTo(check.pkg) 146 for _, v := range stack { 147 edge := check.mono.edges[check.mono.vertices[v].pre] 148 obj := check.mono.vertices[edge.dst].obj 149 150 switch obj.Type().(type) { 151 default: 152 panic("unexpected type") 153 case *Named: 154 err.errorf(edge.pos, "%s implicitly parameterized by %s", obj.Name(), TypeString(edge.typ, qf)) // secondary error, \t indented 155 case *TypeParam: 156 err.errorf(edge.pos, "%s instantiated as %s", obj.Name(), TypeString(edge.typ, qf)) // secondary error, \t indented 157 } 158 } 159 check.report(&err) 160 } 161 162 // recordCanon records that tpar is the canonical type parameter 163 // corresponding to method type parameter mpar. 164 func (w *monoGraph) recordCanon(mpar, tpar *TypeParam) { 165 if w.canon == nil { 166 w.canon = make(map[*TypeParam]*TypeParam) 167 } 168 w.canon[mpar] = tpar 169 } 170 171 // recordInstance records that the given type parameters were 172 // instantiated with the corresponding type arguments. 173 func (w *monoGraph) recordInstance(pkg *Package, pos syntax.Pos, tparams []*TypeParam, targs []Type, xlist []syntax.Expr) { 174 for i, tpar := range tparams { 175 pos := pos 176 if i < len(xlist) { 177 pos = syntax.StartPos(xlist[i]) 178 } 179 w.assign(pkg, pos, tpar, targs[i]) 180 } 181 } 182 183 // assign records that tpar was instantiated as targ at pos. 184 func (w *monoGraph) assign(pkg *Package, pos syntax.Pos, tpar *TypeParam, targ Type) { 185 // Go generics do not have an analog to C++`s template-templates, 186 // where a template parameter can itself be an instantiable 187 // template. So any instantiation cycles must occur within a single 188 // package. Accordingly, we can ignore instantiations of imported 189 // type parameters. 190 // 191 // TODO(mdempsky): Push this check up into recordInstance? All type 192 // parameters in a list will appear in the same package. 193 if tpar.Obj().Pkg() != pkg { 194 return 195 } 196 197 // flow adds an edge from vertex src representing that typ flows to tpar. 198 flow := func(src int, typ Type) { 199 weight := 1 200 if typ == targ { 201 weight = 0 202 } 203 204 w.addEdge(w.typeParamVertex(tpar), src, weight, pos, targ) 205 } 206 207 // Recursively walk the type argument to find any defined types or 208 // type parameters. 209 var do func(typ Type) 210 do = func(typ Type) { 211 switch typ := Unalias(typ).(type) { 212 default: 213 panic("unexpected type") 214 215 case *TypeParam: 216 assert(typ.Obj().Pkg() == pkg) 217 flow(w.typeParamVertex(typ), typ) 218 219 case *Named: 220 if src := w.localNamedVertex(pkg, typ.Origin()); src >= 0 { 221 flow(src, typ) 222 } 223 224 targs := typ.TypeArgs() 225 for i := 0; i < targs.Len(); i++ { 226 do(targs.At(i)) 227 } 228 229 case *Array: 230 do(typ.Elem()) 231 case *Basic: 232 // ok 233 case *Chan: 234 do(typ.Elem()) 235 case *Map: 236 do(typ.Key()) 237 do(typ.Elem()) 238 case *Pointer: 239 do(typ.Elem()) 240 case *Slice: 241 do(typ.Elem()) 242 243 case *Interface: 244 for i := 0; i < typ.NumMethods(); i++ { 245 do(typ.Method(i).Type()) 246 } 247 case *Signature: 248 tuple := func(tup *Tuple) { 249 for i := 0; i < tup.Len(); i++ { 250 do(tup.At(i).Type()) 251 } 252 } 253 tuple(typ.Params()) 254 tuple(typ.Results()) 255 case *Struct: 256 for i := 0; i < typ.NumFields(); i++ { 257 do(typ.Field(i).Type()) 258 } 259 } 260 } 261 do(targ) 262 } 263 264 // localNamedVertex returns the index of the vertex representing 265 // named, or -1 if named doesn't need representation. 266 func (w *monoGraph) localNamedVertex(pkg *Package, named *Named) int { 267 obj := named.Obj() 268 if obj.Pkg() != pkg { 269 return -1 // imported type 270 } 271 272 root := pkg.Scope() 273 if obj.Parent() == root { 274 return -1 // package scope, no ambient type parameters 275 } 276 277 if idx, ok := w.nameIdx[obj]; ok { 278 return idx 279 } 280 281 idx := -1 282 283 // Walk the type definition's scope to find any ambient type 284 // parameters that it's implicitly parameterized by. 285 for scope := obj.Parent(); scope != root; scope = scope.Parent() { 286 for _, elem := range scope.elems { 287 if elem, ok := elem.(*TypeName); ok && !elem.IsAlias() && cmpPos(elem.Pos(), obj.Pos()) < 0 { 288 if tpar, ok := elem.Type().(*TypeParam); ok { 289 if idx < 0 { 290 idx = len(w.vertices) 291 w.vertices = append(w.vertices, monoVertex{obj: obj}) 292 } 293 294 w.addEdge(idx, w.typeParamVertex(tpar), 1, obj.Pos(), tpar) 295 } 296 } 297 } 298 } 299 300 if w.nameIdx == nil { 301 w.nameIdx = make(map[*TypeName]int) 302 } 303 w.nameIdx[obj] = idx 304 return idx 305 } 306 307 // typeParamVertex returns the index of the vertex representing tpar. 308 func (w *monoGraph) typeParamVertex(tpar *TypeParam) int { 309 if x, ok := w.canon[tpar]; ok { 310 tpar = x 311 } 312 313 obj := tpar.Obj() 314 315 if idx, ok := w.nameIdx[obj]; ok { 316 return idx 317 } 318 319 if w.nameIdx == nil { 320 w.nameIdx = make(map[*TypeName]int) 321 } 322 323 idx := len(w.vertices) 324 w.vertices = append(w.vertices, monoVertex{obj: obj}) 325 w.nameIdx[obj] = idx 326 return idx 327 } 328 329 func (w *monoGraph) addEdge(dst, src, weight int, pos syntax.Pos, typ Type) { 330 // TODO(mdempsky): Deduplicate redundant edges? 331 w.edges = append(w.edges, monoEdge{ 332 dst: dst, 333 src: src, 334 weight: weight, 335 336 pos: pos, 337 typ: typ, 338 }) 339 }