| // 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) |
| } |
| } |