diff options
Diffstat (limited to 'src/cmd/compile/internal/typecheck/crawler.go')
-rw-r--r-- | src/cmd/compile/internal/typecheck/crawler.go | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/typecheck/crawler.go b/src/cmd/compile/internal/typecheck/crawler.go new file mode 100644 index 0000000000..9a348b9f37 --- /dev/null +++ b/src/cmd/compile/internal/typecheck/crawler.go @@ -0,0 +1,231 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typecheck + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/types" +) + +// crawlExports crawls the type/object graph rooted at the given list +// of exported objects. Any functions that are found to be potentially +// callable by importers are marked with ExportInline so that +// iexport.go knows to re-export their inline body. +func crawlExports(exports []*ir.Name) { + p := crawler{ + marked: make(map[*types.Type]bool), + embedded: make(map[*types.Type]bool), + } + for _, n := range exports { + p.markObject(n) + } +} + +type crawler struct { + marked map[*types.Type]bool // types already seen by markType + embedded map[*types.Type]bool // types already seen by markEmbed +} + +// markObject visits a reachable object. +func (p *crawler) markObject(n *ir.Name) { + if n.Op() == ir.ONAME && n.Class == ir.PFUNC { + p.markInlBody(n) + } + + // If a declared type name is reachable, users can embed it in their + // own types, which makes even its unexported methods reachable. + if n.Op() == ir.OTYPE { + p.markEmbed(n.Type()) + } + + p.markType(n.Type()) +} + +// markType recursively visits types reachable from t to identify +// functions whose inline bodies may be needed. +func (p *crawler) markType(t *types.Type) { + if t.IsInstantiatedGeneric() { + // Re-instantiated types don't add anything new, so don't follow them. + return + } + if p.marked[t] { + return + } + p.marked[t] = true + + // If this is a defined type, mark all of its associated + // methods. Skip interface types because t.Methods contains + // only their unexpanded method set (i.e., exclusive of + // interface embeddings), and the switch statement below + // handles their full method set. + if t.Sym() != nil && t.Kind() != types.TINTER { + for _, m := range t.Methods().Slice() { + if types.IsExported(m.Sym.Name) { + p.markObject(m.Nname.(*ir.Name)) + } + } + } + + // Recursively mark any types that can be produced given a + // value of type t: dereferencing a pointer; indexing or + // iterating over an array, slice, or map; receiving from a + // channel; accessing a struct field or interface method; or + // calling a function. + // + // Notably, we don't mark function parameter types, because + // the user already needs some way to construct values of + // those types. + switch t.Kind() { + case types.TPTR, types.TARRAY, types.TSLICE: + p.markType(t.Elem()) + + case types.TCHAN: + if t.ChanDir().CanRecv() { + p.markType(t.Elem()) + } + + case types.TMAP: + p.markType(t.Key()) + p.markType(t.Elem()) + + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + if types.IsExported(f.Sym.Name) || f.Embedded != 0 { + p.markType(f.Type) + } + } + + case types.TFUNC: + for _, f := range t.Results().FieldSlice() { + p.markType(f.Type) + } + + case types.TINTER: + // TODO(danscales) - will have to deal with the types in interface + // elements here when implemented in types2 and represented in types1. + for _, f := range t.AllMethods().Slice() { + if types.IsExported(f.Sym.Name) { + p.markType(f.Type) + } + } + + case types.TTYPEPARAM: + // No other type that needs to be followed. + } +} + +// markEmbed is similar to markType, but handles finding methods that +// need to be re-exported because t can be embedded in user code +// (possibly transitively). +func (p *crawler) markEmbed(t *types.Type) { + if t.IsPtr() { + // Defined pointer type; not allowed to embed anyway. + if t.Sym() != nil { + return + } + t = t.Elem() + } + + if t.IsInstantiatedGeneric() { + // Re-instantiated types don't add anything new, so don't follow them. + return + } + + if p.embedded[t] { + return + } + p.embedded[t] = true + + // If t is a defined type, then re-export all of its methods. Unlike + // in markType, we include even unexported methods here, because we + // still need to generate wrappers for them, even if the user can't + // refer to them directly. + if t.Sym() != nil && t.Kind() != types.TINTER { + for _, m := range t.Methods().Slice() { + p.markObject(m.Nname.(*ir.Name)) + } + } + + // If t is a struct, recursively visit its embedded fields. + if t.IsStruct() { + for _, f := range t.FieldSlice() { + if f.Embedded != 0 { + p.markEmbed(f.Type) + } + } + } +} + +// markInlBody marks n's inline body for export and recursively +// ensures all called functions are marked too. +func (p *crawler) markInlBody(n *ir.Name) { + if n == nil { + return + } + if n.Op() != ir.ONAME || n.Class != ir.PFUNC { + base.Fatalf("markInlBody: unexpected %v, %v, %v", n, n.Op(), n.Class) + } + fn := n.Func + if fn == nil { + base.Fatalf("markInlBody: missing Func on %v", n) + } + if fn.Inl == nil { + return + } + + if fn.ExportInline() { + return + } + fn.SetExportInline(true) + + ImportedBody(fn) + + var doFlood func(n ir.Node) + doFlood = func(n ir.Node) { + switch n.Op() { + case ir.OMETHEXPR, ir.ODOTMETH: + p.markInlBody(ir.MethodExprName(n)) + + case ir.ONAME: + n := n.(*ir.Name) + switch n.Class { + case ir.PFUNC: + p.markInlBody(n) + Export(n) + case ir.PEXTERN: + Export(n) + } + p.checkGenericType(n.Type()) + case ir.OTYPE: + p.checkGenericType(n.Type()) + case ir.OMETHVALUE: + // Okay, because we don't yet inline indirect + // calls to method values. + case ir.OCLOSURE: + // VisitList doesn't visit closure bodies, so force a + // recursive call to VisitList on the body of the closure. + ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doFlood) + } + } + + // Recursively identify all referenced functions for + // reexport. We want to include even non-called functions, + // because after inlining they might be callable. + ir.VisitList(fn.Inl.Body, doFlood) +} + +// checkGenerictype ensures that we call markType() on any base generic type that +// is written to the export file (even if not explicitly marked +// for export), so its methods will be available for inlining if needed. +func (p *crawler) checkGenericType(t *types.Type) { + if t != nil && t.HasTParam() { + if t.OrigSym != nil { + // Convert to the base generic type. + t = t.OrigSym.Def.Type() + } + p.markType(t) + } +} |