1
0
mirror of https://github.com/golang/go synced 2024-11-17 09:14:46 -07:00

cmd/compile: simplify reexport logic

Currently, we reexport any package-scope constant, function, type, or
variable declarations needed by an inlineable function body. However,
now that we have an early pass to walk inlineable function bodies
(golang.org/cl/74110), we can simplify the logic for finding these
declarations.

The binary export format supports writing out type declarations
in-place at their first use. Also, it always writes out constants by
value, so their declarations never need to be reexported.

Notably, we attempted this before (golang.org/cl/36170) and had to
revert it (golang.org/cl/45911). However, this was because while
writing out inline bodies, we could discover variable/function
dependencies. By collecting variable/function dependencies during
inlineable function discovery, we avoid this problem.

While here, get rid of isInlineable. We already typecheck inlineable
function bodies during inlFlood, so it's become a no-op. Just move the
comment explaining parameter numbering to its caller.

Change-Id: Ibbfaafce793733675d3a2ad98791758583055666
Reviewed-on: https://go-review.googlesource.com/103864
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Matthew Dempsky 2018-03-30 18:58:03 -07:00
parent fd22542eaa
commit bcc8edfd8a
3 changed files with 84 additions and 216 deletions

View File

@ -232,9 +232,32 @@ func export(out *bufio.Writer, trace bool) int {
p.tracef("\n")
}
// Mark all inlineable functions that the importer could call.
// This is done by tracking down all inlineable methods
// reachable from exported types.
// export objects
//
// We've already added all exported (package-level) objects to
// exportlist. These objects represent all information
// required to import this package and type-check against it;
// i.e., this is the platform-independent export data. The
// format is generic in the sense that different compilers can
// use the same representation.
//
// However, due to inlineable function and their dependencies,
// we may need to export (or possibly reexport) additional
// objects. We handle these objects separately. This data is
// platform-specific as it depends on the inlining decisions
// of the compiler and the representation of the inlined
// function bodies.
// Remember initial exportlist length.
numglobals := len(exportlist)
// Phase 0: Mark all inlineable functions that an importing
// package could call. This is done by tracking down all
// inlineable methods reachable from exported declarations.
//
// Along the way, we add to exportlist any function and
// variable declarations needed by the inline bodies.
if exportInlined {
p.marked = make(map[*types.Type]bool)
for _, n := range exportlist {
sym := n.Sym
@ -248,30 +271,11 @@ func export(out *bufio.Writer, trace bool) int {
p.markType(asNode(sym.Def).Type)
}
p.marked = nil
}
// export objects
//
// First, export all exported (package-level) objects; i.e., all objects
// in the current exportlist. These objects represent all information
// required to import this package and type-check against it; i.e., this
// is the platform-independent export data. The format is generic in the
// sense that different compilers can use the same representation.
//
// During this first phase, more objects may be added to the exportlist
// (due to inlined function bodies and their dependencies). Export those
// objects in a second phase. That data is platform-specific as it depends
// on the inlining decisions of the compiler and the representation of the
// inlined function bodies.
// remember initial exportlist length
var numglobals = len(exportlist)
// Phase 1: Export objects in _current_ exportlist; exported objects at
// package level.
// Use range since we want to ignore objects added to exportlist during
// this phase.
// Phase 1: Export package-level objects.
objcount := 0
for _, n := range exportlist {
for _, n := range exportlist[:numglobals] {
sym := n.Sym
if sym.Exported() {
@ -323,12 +327,9 @@ func export(out *bufio.Writer, trace bool) int {
p.tracef("\n")
}
// Phase 2: Export objects added to exportlist during phase 1.
// Don't use range since exportlist may grow during this phase
// and we want to export all remaining objects.
// Phase 2: Export objects added to exportlist during phase 0.
objcount = 0
for i := numglobals; exportInlined && i < len(exportlist); i++ {
n := exportlist[i]
for _, n := range exportlist[numglobals:] {
sym := n.Sym
// TODO(gri) The rest of this loop body is identical with
@ -389,7 +390,7 @@ func export(out *bufio.Writer, trace bool) int {
// Don't use range since funcList may grow.
objcount = 0
for i := 0; i < len(p.funcList); i++ {
if f := p.funcList[i]; f != nil {
if f := p.funcList[i]; f.ExportInline() {
// function has inlineable body:
// write index and body
if p.trace {
@ -584,24 +585,22 @@ func (p *exporter) obj(sym *types.Sym) {
p.qualifiedName(sym)
sig := asNode(sym.Def).Type
inlineable := isInlineable(asNode(sym.Def))
p.paramList(sig.Params(), inlineable)
p.paramList(sig.Results(), inlineable)
// Theoretically, we only need numbered
// parameters if we're supplying an inline
// function body. However, it's possible to
// import a function from a package that
// didn't supply the inline body, and then
// another that did. In this case, we would
// need to rename the parameters during
// import, which is a little sketchy.
//
// For simplicity, just always number
// parameters.
p.paramList(sig.Params(), true)
p.paramList(sig.Results(), true)
var f *Func
if inlineable && asNode(sym.Def).Func.ExportInline() {
f = asNode(sym.Def).Func
// TODO(gri) re-examine reexportdeplist:
// Because we can trivially export types
// in-place, we don't need to collect types
// inside function bodies in the exportlist.
// With an adjusted reexportdeplist used only
// by the binary exporter, we can also avoid
// the global exportlist.
reexportdeplist(f.Inl)
}
p.funcList = append(p.funcList, f)
p.funcList = append(p.funcList, asNode(sym.Def).Func)
} else {
// variable
p.tag(varTag)
@ -675,36 +674,6 @@ func fileLine(n *Node) (file string, line int) {
return
}
func isInlineable(n *Node) bool {
if exportInlined && n != nil {
// When lazily typechecking inlined bodies, some
// re-exported ones may not have been typechecked yet.
// Currently that can leave unresolved ONONAMEs in
// import-dot-ed packages in the wrong package.
//
// TODO(mdempsky): Having the ExportInline check here
// instead of the outer if statement means we end up
// exporting parameter names even for functions whose
// inline body won't be exported by this package. This
// is currently necessary because we might first
// import a function/method from a package where it
// doesn't need to be re-exported, and then from a
// package where it does. If this happens, we'll need
// the parameter names.
//
// We could initially do without the parameter names,
// and then fill them in when importing the inline
// body. But parameter names are attached to the
// function type, and modifying types after the fact
// is a little sketchy.
if Debug_typecheckinl == 0 && n.Func.ExportInline() {
typecheckinl(n)
}
return true
}
return false
}
func (p *exporter) typ(t *types.Type) {
if t == nil {
Fatalf("exporter: nil type")
@ -788,19 +757,15 @@ func (p *exporter) typ(t *types.Type) {
sig := m.Type
mfn := asNode(sig.FuncType().Nname)
inlineable := isInlineable(mfn)
p.paramList(sig.Recvs(), inlineable)
p.paramList(sig.Params(), inlineable)
p.paramList(sig.Results(), inlineable)
// See comment in (*exporter).obj about
// numbered parameters.
p.paramList(sig.Recvs(), true)
p.paramList(sig.Params(), true)
p.paramList(sig.Results(), true)
p.bool(m.Nointerface()) // record go:nointerface pragma value (see also #16243)
var f *Func
if inlineable && mfn.Func.ExportInline() {
f = mfn.Func
reexportdeplist(mfn.Func.Inl)
}
p.funcList = append(p.funcList, f)
p.funcList = append(p.funcList, mfn.Func)
}
if p.trace && len(methods) > 0 {

View File

@ -53,6 +53,18 @@ func exportsym(n *Node) {
exportlist = append(exportlist, n)
}
// reexportsym marks n for reexport.
func reexportsym(n *Node) {
if exportedsym(n.Sym) {
return
}
if Debug['E'] != 0 {
fmt.Printf("reexport name %v\n", n.Sym)
}
exportlist = append(exportlist, n)
}
func exportname(s string) bool {
if r := s[0]; r < utf8.RuneSelf {
return 'A' <= r && r <= 'Z'
@ -96,125 +108,6 @@ func autoexport(n *Node, ctxt Class) {
}
}
// Look for anything we need for the inline body
func reexportdeplist(ll Nodes) {
for _, n := range ll.Slice() {
reexportdep(n)
}
}
func reexportdep(n *Node) {
if n == nil {
return
}
//print("reexportdep %+hN\n", n);
switch n.Op {
case ONAME:
switch n.Class() {
case PFUNC:
// methods will be printed along with their type
// nodes for T.Method expressions
if n.isMethodExpression() {
break
}
// nodes for method calls.
if n.Type == nil || n.IsMethod() {
break
}
fallthrough
case PEXTERN:
if n.Sym != nil && !exportedsym(n.Sym) {
if Debug['E'] != 0 {
fmt.Printf("reexport name %v\n", n.Sym)
}
exportlist = append(exportlist, n)
}
}
// Local variables in the bodies need their type.
case ODCL:
t := n.Left.Type
if t != types.Types[t.Etype] && t != types.Idealbool && t != types.Idealstring {
if t.IsPtr() {
t = t.Elem()
}
if t != nil && t.Sym != nil && t.Sym.Def != nil && !exportedsym(t.Sym) {
if Debug['E'] != 0 {
fmt.Printf("reexport type %v from declaration\n", t.Sym)
}
exportlist = append(exportlist, asNode(t.Sym.Def))
}
}
case OLITERAL:
t := n.Type
if t != types.Types[n.Type.Etype] && t != types.Idealbool && t != types.Idealstring {
if t.IsPtr() {
t = t.Elem()
}
if t != nil && t.Sym != nil && t.Sym.Def != nil && !exportedsym(t.Sym) {
if Debug['E'] != 0 {
fmt.Printf("reexport literal type %v\n", t.Sym)
}
exportlist = append(exportlist, asNode(t.Sym.Def))
}
}
fallthrough
case OTYPE:
if n.Sym != nil && n.Sym.Def != nil && !exportedsym(n.Sym) {
if Debug['E'] != 0 {
fmt.Printf("reexport literal/type %v\n", n.Sym)
}
exportlist = append(exportlist, n)
}
// for operations that need a type when rendered, put the type on the export list.
case OCONV,
OCONVIFACE,
OCONVNOP,
ORUNESTR,
OARRAYBYTESTR,
OARRAYRUNESTR,
OSTRARRAYBYTE,
OSTRARRAYRUNE,
ODOTTYPE,
ODOTTYPE2,
OSTRUCTLIT,
OARRAYLIT,
OSLICELIT,
OPTRLIT,
OMAKEMAP,
OMAKESLICE,
OMAKECHAN:
t := n.Type
switch t.Etype {
case TARRAY, TCHAN, TPTR32, TPTR64, TSLICE:
if t.Sym == nil {
t = t.Elem()
}
}
if t != nil && t.Sym != nil && t.Sym.Def != nil && !exportedsym(t.Sym) {
if Debug['E'] != 0 {
fmt.Printf("reexport type for expression %v\n", t.Sym)
}
exportlist = append(exportlist, asNode(t.Sym.Def))
}
}
reexportdep(n.Left)
reexportdep(n.Right)
reexportdeplist(n.List)
reexportdeplist(n.Rlist)
reexportdeplist(n.Ninit)
reexportdeplist(n.Nbody)
}
// methodbyname sorts types by symbol name.
type methodbyname []*types.Field

View File

@ -200,10 +200,20 @@ func inlFlood(n *Node) {
typecheckinl(n)
// Recursively flood any functions called by this one.
inspectList(n.Func.Inl, func(n *Node) bool {
switch n.Op {
case ONAME:
// Mark any referenced global variables or
// functions for reexport. Skip methods,
// because they're reexported alongside their
// receiver type.
if n.Class() == PEXTERN || n.Class() == PFUNC && !n.isMethodExpression() {
reexportsym(n)
}
case OCALLFUNC, OCALLMETH:
// Recursively flood any functions called by
// this one.
inlFlood(asNode(n.Left.Type.Nname()))
}
return true