Skip to content

Commit 8684534

Browse files
committed
cmd/compile: don't export unreachable inline method bodies
Previously, anytime we exported a function or method declaration (which includes methods for every type transitively exported), we included the inline function bodies, if any. However, in many cases, it's impossible (or at least very unlikely) for the importing package to call the method. For example: package p type T int func (t T) M() { t.u() } func (t T) u() {} func (t T) v() {} T.M and T.u are inlineable, and they're both reachable through calls to T.M, which is exported. However, t.v is also inlineable, but cannot be reached. Exception: if p.T is embedded in another type q.U, p.T.v will be promoted to q.U.v, and the generated wrapper function could have inlined the call to p.T.v. However, in practice, this doesn't happen, and a missed inlining opportunity doesn't affect correctness. To implement this, this CL introduces an extra flood fill pass before exporting to mark inline bodies that are actually reachable, so the exporter can skip over methods like t.v. This reduces Kubernetes build time (as measured by "time go build -a k8s.io/kubernetes/cmd/...") on an HP Z620 measurably: == before == real 0m44.658s user 11m19.136s sys 0m53.844s == after == real 0m41.702s user 10m29.732s sys 0m50.908s It also significantly cuts down the cost of enabling mid-stack inlining (-l=4): == before (-l=4) == real 1m19.236s user 20m6.528s sys 1m17.328s == after (-l=4) == real 0m59.100s user 13m12.808s sys 0m58.776s Updates #19348. Change-Id: Iade58233ca42af823a1630517a53848b5d3c7a7e Reviewed-on: https://go-review.googlesource.com/74110 Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent f33f20e commit 8684534

File tree

6 files changed

+173
-26
lines changed

6 files changed

+173
-26
lines changed

src/cmd/compile/internal/gc/bexport.go

Lines changed: 109 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ type exporter struct {
174174
typIndex map[*types.Type]int
175175
funcList []*Func
176176

177+
marked map[*types.Type]bool // types already seen by markType
178+
177179
// position encoding
178180
posInfoFormat bool
179181
prevFile string
@@ -230,6 +232,23 @@ func export(out *bufio.Writer, trace bool) int {
230232
p.tracef("\n")
231233
}
232234

235+
// Mark all inlineable functions that the importer could call.
236+
// This is done by tracking down all inlineable methods
237+
// reachable from exported types.
238+
p.marked = make(map[*types.Type]bool)
239+
for _, n := range exportlist {
240+
sym := n.Sym
241+
if sym.Exported() {
242+
// Closures are added to exportlist, but with Exported
243+
// already set. The export code below skips over them, so
244+
// we have to here as well.
245+
// TODO(mdempsky): Investigate why. This seems suspicious.
246+
continue
247+
}
248+
p.markType(asNode(sym.Def).Type)
249+
}
250+
p.marked = nil
251+
233252
// export objects
234253
//
235254
// First, export all exported (package-level) objects; i.e., all objects
@@ -436,6 +455,72 @@ func unidealType(typ *types.Type, val Val) *types.Type {
436455
return typ
437456
}
438457

458+
// markType recursively visits types reachable from t to identify
459+
// functions whose inline bodies may be needed.
460+
func (p *exporter) markType(t *types.Type) {
461+
if p.marked[t] {
462+
return
463+
}
464+
p.marked[t] = true
465+
466+
// If this is a named type, mark all of its associated
467+
// methods. Skip interface types because t.Methods contains
468+
// only their unexpanded method set (i.e., exclusive of
469+
// interface embeddings), and the switch statement below
470+
// handles their full method set.
471+
if t.Sym != nil && t.Etype != TINTER {
472+
for _, m := range t.Methods().Slice() {
473+
if exportname(m.Sym.Name) {
474+
p.markType(m.Type)
475+
}
476+
}
477+
}
478+
479+
// Recursively mark any types that can be produced given a
480+
// value of type t: dereferencing a pointer; indexing an
481+
// array, slice, or map; receiving from a channel; accessing a
482+
// struct field or interface method; or calling a function.
483+
//
484+
// Notably, we don't mark map key or function parameter types,
485+
// because the user already needs some way to construct values
486+
// of those types.
487+
//
488+
// It's not critical for correctness that this algorithm is
489+
// perfect. Worst case, we might miss opportunities to inline
490+
// some function calls in downstream packages.
491+
switch t.Etype {
492+
case TPTR32, TPTR64, TARRAY, TSLICE, TCHAN:
493+
p.markType(t.Elem())
494+
495+
case TMAP:
496+
p.markType(t.Val())
497+
498+
case TSTRUCT:
499+
for _, f := range t.FieldSlice() {
500+
if exportname(f.Sym.Name) || f.Embedded != 0 {
501+
p.markType(f.Type)
502+
}
503+
}
504+
505+
case TFUNC:
506+
// If t is the type of a function or method, then
507+
// t.Nname() is its ONAME. Mark its inline body and
508+
// any recursively called functions for export.
509+
inlFlood(asNode(t.Nname()))
510+
511+
for _, f := range t.Results().FieldSlice() {
512+
p.markType(f.Type)
513+
}
514+
515+
case TINTER:
516+
for _, f := range t.FieldSlice() {
517+
if exportname(f.Sym.Name) {
518+
p.markType(f.Type)
519+
}
520+
}
521+
}
522+
}
523+
439524
func (p *exporter) obj(sym *types.Sym) {
440525
// Exported objects may be from different packages because they
441526
// may be re-exported via an exported alias or as dependencies in
@@ -505,7 +590,7 @@ func (p *exporter) obj(sym *types.Sym) {
505590
p.paramList(sig.Results(), inlineable)
506591

507592
var f *Func
508-
if inlineable {
593+
if inlineable && asNode(sym.Def).Func.ExportInline() {
509594
f = asNode(sym.Def).Func
510595
// TODO(gri) re-examine reexportdeplist:
511596
// Because we can trivially export types
@@ -591,10 +676,28 @@ func fileLine(n *Node) (file string, line int) {
591676
}
592677

593678
func isInlineable(n *Node) bool {
594-
if exportInlined && n != nil && n.Func != nil && n.Func.Inl.Len() != 0 {
595-
// when lazily typechecking inlined bodies, some re-exported ones may not have been typechecked yet.
596-
// currently that can leave unresolved ONONAMEs in import-dot-ed packages in the wrong package
597-
if Debug_typecheckinl == 0 {
679+
if exportInlined && n != nil && n.Func != nil {
680+
// When lazily typechecking inlined bodies, some
681+
// re-exported ones may not have been typechecked yet.
682+
// Currently that can leave unresolved ONONAMEs in
683+
// import-dot-ed packages in the wrong package.
684+
//
685+
// TODO(mdempsky): Having the ExportInline check here
686+
// instead of the outer if statement means we end up
687+
// exporting parameter names even for functions whose
688+
// inline body won't be exported by this package. This
689+
// is currently necessary because we might first
690+
// import a function/method from a package where it
691+
// doesn't need to be re-exported, and then from a
692+
// package where it does. If this happens, we'll need
693+
// the parameter names.
694+
//
695+
// We could initially do without the parameter names,
696+
// and then fill them in when importing the inline
697+
// body. But parameter names are attached to the
698+
// function type, and modifying types after the fact
699+
// is a little sketchy.
700+
if Debug_typecheckinl == 0 && n.Func.ExportInline() {
598701
typecheckinl(n)
599702
}
600703
return true
@@ -693,7 +796,7 @@ func (p *exporter) typ(t *types.Type) {
693796
p.bool(m.Nointerface()) // record go:nointerface pragma value (see also #16243)
694797

695798
var f *Func
696-
if inlineable {
799+
if inlineable && mfn.Func.ExportInline() {
697800
f = mfn.Func
698801
reexportdeplist(mfn.Func.Inl)
699802
}

src/cmd/compile/internal/gc/bimport.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func Import(imp *types.Pkg, in *bufio.Reader) {
188188
// parameter renaming which doesn't matter if we don't have a body.
189189

190190
inlCost := p.int()
191-
if f := p.funcList[i]; f != nil {
191+
if f := p.funcList[i]; f != nil && f.Func.Inl.Len() == 0 {
192192
// function not yet imported - read body and set it
193193
funchdr(f)
194194
body := p.stmtList()
@@ -357,12 +357,13 @@ func (p *importer) obj(tag int) {
357357

358358
sig := functypefield(nil, params, result)
359359
importsym(p.imp, sym, ONAME)
360-
if asNode(sym.Def) != nil && asNode(sym.Def).Op == ONAME {
360+
if old := asNode(sym.Def); old != nil && old.Op == ONAME {
361361
// function was imported before (via another import)
362-
if !eqtype(sig, asNode(sym.Def).Type) {
363-
p.formatErrorf("inconsistent definition for func %v during import\n\t%v\n\t%v", sym, asNode(sym.Def).Type, sig)
362+
if !eqtype(sig, old.Type) {
363+
p.formatErrorf("inconsistent definition for func %v during import\n\t%v\n\t%v", sym, old.Type, sig)
364364
}
365-
p.funcList = append(p.funcList, nil)
365+
n := asNode(old.Type.Nname())
366+
p.funcList = append(p.funcList, n)
366367
break
367368
}
368369

@@ -372,6 +373,8 @@ func (p *importer) obj(tag int) {
372373
p.funcList = append(p.funcList, n)
373374
importlist = append(importlist, n)
374375

376+
sig.SetNname(asTypesNode(n))
377+
375378
if Debug['E'] > 0 {
376379
fmt.Printf("import [%q] func %v \n", p.imp.Path, n)
377380
if Debug['m'] > 2 && n.Func.Inl.Len() != 0 {
@@ -518,17 +521,19 @@ func (p *importer) typ() *types.Type {
518521
nointerface := p.bool()
519522

520523
mt := functypefield(recv[0], params, result)
521-
addmethod(sym, mt, false, nointerface)
524+
oldm := addmethod(sym, mt, false, nointerface)
522525

523526
if dup {
524527
// An earlier import already declared this type and its methods.
525528
// Discard the duplicate method declaration.
526-
p.funcList = append(p.funcList, nil)
529+
n := asNode(oldm.Type.Nname())
530+
p.funcList = append(p.funcList, n)
527531
continue
528532
}
529533

530534
n := newfuncnamel(mpos, methodname(sym, recv[0].Type))
531535
n.Type = mt
536+
n.SetClass(PFUNC)
532537
checkwidth(n.Type)
533538
p.funcList = append(p.funcList, n)
534539
importlist = append(importlist, n)
@@ -538,7 +543,7 @@ func (p *importer) typ() *types.Type {
538543
// (dotmeth's type).Nname.Inl, and dotmeth's type has been pulled
539544
// out by typecheck's lookdot as this $$.ttype. So by providing
540545
// this back link here we avoid special casing there.
541-
n.Type.FuncType().Nname = asTypesNode(n)
546+
mt.SetNname(asTypesNode(n))
542547

543548
if Debug['E'] > 0 {
544549
fmt.Printf("import [%q] meth %v \n", p.imp.Path, n)

src/cmd/compile/internal/gc/dcl.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,8 @@ func methodname(s *types.Sym, recv *types.Type) *types.Sym {
936936
// Add a method, declared as a function.
937937
// - msym is the method symbol
938938
// - t is function type (with receiver)
939-
func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) {
939+
// Returns a pointer to the existing or added Field.
940+
func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) *types.Field {
940941
if msym == nil {
941942
Fatalf("no method symbol")
942943
}
@@ -945,7 +946,7 @@ func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) {
945946
rf := t.Recv() // ptr to this structure
946947
if rf == nil {
947948
yyerror("missing receiver")
948-
return
949+
return nil
949950
}
950951

951952
mt := methtype(rf.Type)
@@ -955,7 +956,7 @@ func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) {
955956
if t != nil && t.IsPtr() {
956957
if t.Sym != nil {
957958
yyerror("invalid receiver type %v (%v is a pointer type)", pa, t)
958-
return
959+
return nil
959960
}
960961
t = t.Elem()
961962
}
@@ -974,23 +975,23 @@ func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) {
974975
// but just in case, fall back to generic error.
975976
yyerror("invalid receiver type %v (%L / %L)", pa, pa, t)
976977
}
977-
return
978+
return nil
978979
}
979980

980981
if local && mt.Sym.Pkg != localpkg {
981982
yyerror("cannot define new methods on non-local type %v", mt)
982-
return
983+
return nil
983984
}
984985

985986
if msym.IsBlank() {
986-
return
987+
return nil
987988
}
988989

989990
if mt.IsStruct() {
990991
for _, f := range mt.Fields().Slice() {
991992
if f.Sym == msym {
992993
yyerror("type %v has both field and method named %v", mt, msym)
993-
return
994+
return nil
994995
}
995996
}
996997
}
@@ -1004,7 +1005,7 @@ func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) {
10041005
if !eqtype(t, f.Type) || !eqtype(t.Recv().Type, f.Type.Recv().Type) {
10051006
yyerror("method redeclared: %v.%v\n\t%v\n\t%v", mt, msym, f.Type, t)
10061007
}
1007-
return
1008+
return f
10081009
}
10091010

10101011
f := types.NewField()
@@ -1014,6 +1015,7 @@ func addmethod(msym *types.Sym, t *types.Type, local, nointerface bool) {
10141015
f.SetNointerface(nointerface)
10151016

10161017
mt.Methods().Append(f)
1018+
return f
10171019
}
10181020

10191021
func funccompile(n *Node) {

src/cmd/compile/internal/gc/esc.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,7 @@ func (v *bottomUpVisitor) visitcode(n *Node, min uint32) uint32 {
131131

132132
switch n.Op {
133133
case OCALLFUNC, OCALLMETH:
134-
fn := n.Left
135-
if n.Op == OCALLMETH {
136-
fn = asNode(n.Left.Sym.Def)
137-
}
134+
fn := asNode(n.Left.Type.Nname())
138135
if fn != nil && fn.Op == ONAME && fn.Class() == PFUNC && fn.Name.Defn != nil {
139136
m := v.visit(fn.Name.Defn)
140137
if m < min {

src/cmd/compile/internal/gc/inl.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,43 @@ func caninl(fn *Node) {
200200
Curfn = savefn
201201
}
202202

203+
// inlFlood marks n's inline body for export and recursively ensures
204+
// all called functions are marked too.
205+
func inlFlood(n *Node) {
206+
if n == nil {
207+
return
208+
}
209+
if n.Op != ONAME || n.Class() != PFUNC {
210+
Fatalf("inlFlood: unexpected %v, %v, %v", n, n.Op, n.Class())
211+
}
212+
if n.Func == nil {
213+
// TODO(mdempsky): Should init have a Func too?
214+
if n.Sym.Name == "init" {
215+
return
216+
}
217+
Fatalf("inlFlood: missing Func on %v", n)
218+
}
219+
if n.Func.Inl.Len() == 0 {
220+
return
221+
}
222+
223+
if n.Func.ExportInline() {
224+
return
225+
}
226+
n.Func.SetExportInline(true)
227+
228+
typecheckinl(n)
229+
230+
// Recursively flood any functions called by this one.
231+
inspectList(n.Func.Inl, func(n *Node) bool {
232+
switch n.Op {
233+
case OCALLFUNC, OCALLMETH:
234+
inlFlood(asNode(n.Left.Type.Nname()))
235+
}
236+
return true
237+
})
238+
}
239+
203240
// hairyVisitor visits a function body to determine its inlining
204241
// hairiness and whether or not it can be inlined.
205242
type hairyVisitor struct {

src/cmd/compile/internal/gc/syntax.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ const (
462462
funcHasDefer // contains a defer statement
463463
funcNilCheckDisabled // disable nil checks when compiling this function
464464
funcInlinabilityChecked // inliner has already determined whether the function is inlinable
465+
funcExportInline // include inline body in export data
465466
)
466467

467468
func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 }
@@ -473,6 +474,7 @@ func (f *Func) NoFramePointer() bool { return f.flags&funcNoFramePointer !=
473474
func (f *Func) HasDefer() bool { return f.flags&funcHasDefer != 0 }
474475
func (f *Func) NilCheckDisabled() bool { return f.flags&funcNilCheckDisabled != 0 }
475476
func (f *Func) InlinabilityChecked() bool { return f.flags&funcInlinabilityChecked != 0 }
477+
func (f *Func) ExportInline() bool { return f.flags&funcExportInline != 0 }
476478

477479
func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) }
478480
func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) }
@@ -483,6 +485,7 @@ func (f *Func) SetNoFramePointer(b bool) { f.flags.set(funcNoFramePointer,
483485
func (f *Func) SetHasDefer(b bool) { f.flags.set(funcHasDefer, b) }
484486
func (f *Func) SetNilCheckDisabled(b bool) { f.flags.set(funcNilCheckDisabled, b) }
485487
func (f *Func) SetInlinabilityChecked(b bool) { f.flags.set(funcInlinabilityChecked, b) }
488+
func (f *Func) SetExportInline(b bool) { f.flags.set(funcExportInline, b) }
486489

487490
func (f *Func) setWBPos(pos src.XPos) {
488491
if Debug_wb != 0 {

0 commit comments

Comments
 (0)