]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.typeparams] cmd/compile: simplify SSA devirtualization
authorMatthew Dempsky <mdempsky@google.com>
Mon, 14 Jun 2021 23:26:26 +0000 (16:26 -0700)
committerMatthew Dempsky <mdempsky@google.com>
Wed, 16 Jun 2021 20:57:38 +0000 (20:57 +0000)
This CL implements a few improvements to SSA devirtualization to make
it simpler and more general:

1. Change reflectdata.ITabAddr to now immediately generate the wrapper
functions and write out the itab symbol data. Previously, these were
each handled by separate phases later on.

2. Removes the hack in typecheck where we marked itabs that we
expected to need later. Instead, the calls to ITabAddr in walk now
handle generating the wrappers.

3. Changes the SSA interface call devirtualization algorithm to just
use the itab symbol data (namely, its relocations) to figure out what
pointer is available in memory at the given offset. This decouples it
somewhat from reflectdata.

Change-Id: I8fe06922af8f8a1e7c93f5aff2b60ff59b8e7114
Reviewed-on: https://go-review.googlesource.com/c/go/+/327871
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Matthew Dempsky <mdempsky@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
src/cmd/compile/internal/escape/escape.go
src/cmd/compile/internal/gc/main.go
src/cmd/compile/internal/gc/obj.go
src/cmd/compile/internal/reflectdata/reflect.go
src/cmd/compile/internal/ssa/config.go
src/cmd/compile/internal/ssa/rewrite.go
src/cmd/compile/internal/ssagen/ssa.go
src/cmd/compile/internal/typecheck/subr.go
src/cmd/compile/internal/typecheck/typecheck.go

index 842b0f4a7e05583b8e0e439e4ccf1aa63873baa7..e3727bca27ce286d08b694c3b5b6b733fbb3ce0a 100644 (file)
@@ -673,7 +673,7 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
                n := n.(*ir.BinaryExpr)
                // Note: n.X is not needed because it can never point to memory that might escape.
                e.expr(k, n.Y)
-       case ir.OIDATA:
+       case ir.OIDATA, ir.OSPTR:
                n := n.(*ir.UnaryExpr)
                e.expr(k, n.X)
        case ir.OSLICE2ARRPTR:
index ce50cbb4c2e691ed3054d20894fc1bc722623eda..c0346c020651f62924d096b15471d373477f256c 100644 (file)
@@ -181,7 +181,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
 
        typecheck.Target = new(ir.Package)
 
-       typecheck.NeedITab = func(t, iface *types.Type) { reflectdata.ITabAddr(t, iface) }
        typecheck.NeedRuntimeType = reflectdata.NeedRuntimeType // TODO(rsc): TypeSym for lock?
 
        base.AutogeneratedPos = makePos(src.NewFileBase("<autogenerated>", "<autogenerated>"), 1, 0)
@@ -193,6 +192,11 @@ func Main(archInit func(*ssagen.ArchInfo)) {
 
        dwarfgen.RecordPackageName()
 
+       // Prepare for backend processing. This must happen before pkginit,
+       // because it generates itabs for initializing global variables.
+       typecheck.InitRuntime()
+       ssagen.InitConfig()
+
        // Build init task.
        if initTask := pkginit.Task(); initTask != nil {
                typecheck.Export(initTask)
@@ -252,6 +256,11 @@ func Main(archInit func(*ssagen.ArchInfo)) {
        base.Timer.Start("fe", "escapes")
        escape.Funcs(typecheck.Target.Decls)
 
+       // TODO(mdempsky): This is a hack. We need a proper, global work
+       // queue for scheduling function compilation so components don't
+       // need to adjust their behavior depending on when they're called.
+       reflectdata.AfterGlobalEscapeAnalysis = true
+
        // Collect information for go:nowritebarrierrec
        // checking. This must happen before transforming closures during Walk
        // We'll do the final check after write barriers are
@@ -260,17 +269,7 @@ func Main(archInit func(*ssagen.ArchInfo)) {
                ssagen.EnableNoWriteBarrierRecCheck()
        }
 
-       // Prepare for SSA compilation.
-       // This must be before CompileITabs, because CompileITabs
-       // can trigger function compilation.
-       typecheck.InitRuntime()
-       ssagen.InitConfig()
-
-       // Just before compilation, compile itabs found on
-       // the right side of OCONVIFACE so that methods
-       // can be de-virtualized during compilation.
        ir.CurFunc = nil
-       reflectdata.CompileITabs()
 
        // Compile top level functions.
        // Don't use range--walk can add functions to Target.Decls.
index 8a2ff75583b4a8de9e432c5d6640d72537eabfaa..440f898211fe9519c557bb54883bb472f206ef4a 100644 (file)
@@ -117,7 +117,7 @@ func dumpdata() {
        addsignats(typecheck.Target.Externs)
        reflectdata.WriteRuntimeTypes()
        reflectdata.WriteTabs()
-       numPTabs, numITabs := reflectdata.CountTabs()
+       numPTabs := reflectdata.CountPTabs()
        reflectdata.WriteImportStrings()
        reflectdata.WriteBasicTypes()
        dumpembeds()
@@ -158,13 +158,10 @@ func dumpdata() {
        if numExports != len(typecheck.Target.Exports) {
                base.Fatalf("Target.Exports changed after compile functions loop")
        }
-       newNumPTabs, newNumITabs := reflectdata.CountTabs()
+       newNumPTabs := reflectdata.CountPTabs()
        if newNumPTabs != numPTabs {
                base.Fatalf("ptabs changed after compile functions loop")
        }
-       if newNumITabs != numITabs {
-               base.Fatalf("itabs changed after compile functions loop")
-       }
 }
 
 func dumpLinkerObj(bout *bio.Writer) {
index f4a0619935316214406a153501d10f216f9f47ff..9e070895a0b079c918f7b09ed8c1517b8ad161c5 100644 (file)
@@ -28,23 +28,13 @@ import (
        "cmd/internal/src"
 )
 
-type itabEntry struct {
-       t, itype *types.Type
-       lsym     *obj.LSym // symbol of the itab itself
-
-       // symbols of each method in
-       // the itab, sorted by byte offset;
-       // filled in by CompileITabs
-       entries []*obj.LSym
-}
-
 type ptabEntry struct {
        s *types.Sym
        t *types.Type
 }
 
-func CountTabs() (numPTabs, numITabs int) {
-       return len(ptabs), len(itabs)
+func CountPTabs() int {
+       return len(ptabs)
 }
 
 // runtime interface and reflection data structures
@@ -56,7 +46,6 @@ var (
        gcsymmu  sync.Mutex // protects gcsymset and gcsymslice
        gcsymset = make(map[*types.Type]struct{})
 
-       itabs []itabEntry
        ptabs []*ir.Name
 )
 
@@ -841,16 +830,16 @@ func TypePtr(t *types.Type) *ir.AddrExpr {
        return typecheck.Expr(typecheck.NodAddr(n)).(*ir.AddrExpr)
 }
 
-func ITabAddr(t, itype *types.Type) *ir.AddrExpr {
-       if t == nil || (t.IsPtr() && t.Elem() == nil) || t.IsUntyped() || !itype.IsInterface() || itype.IsEmptyInterface() {
-               base.Fatalf("ITabAddr(%v, %v)", t, itype)
-       }
-       s, existed := ir.Pkgs.Itab.LookupOK(t.ShortString() + "," + itype.ShortString())
+// ITabAddr returns an expression representing a pointer to the itab
+// for concrete type typ implementing interface iface.
+func ITabAddr(typ, iface *types.Type) *ir.AddrExpr {
+       s, existed := ir.Pkgs.Itab.LookupOK(typ.ShortString() + "," + iface.ShortString())
+       lsym := s.Linksym()
+
        if !existed {
-               itabs = append(itabs, itabEntry{t: t, itype: itype, lsym: s.Linksym()})
+               writeITab(lsym, typ, iface)
        }
 
-       lsym := s.Linksym()
        n := ir.NewLinksymExpr(base.Pos, lsym, types.Types[types.TUINT8])
        return typecheck.Expr(typecheck.NodAddr(n)).(*ir.AddrExpr)
 }
@@ -1223,83 +1212,6 @@ func InterfaceMethodOffset(ityp *types.Type, i int64) int64 {
        return int64(commonSize()+4*types.PtrSize+uncommonSize(ityp)) + i*8
 }
 
-// for each itabEntry, gather the methods on
-// the concrete type that implement the interface
-func CompileITabs() {
-       for i := range itabs {
-               tab := &itabs[i]
-               methods := genfun(tab.t, tab.itype)
-               if len(methods) == 0 {
-                       continue
-               }
-               tab.entries = methods
-       }
-}
-
-// for the given concrete type and interface
-// type, return the (sorted) set of methods
-// on the concrete type that implement the interface
-func genfun(t, it *types.Type) []*obj.LSym {
-       if t == nil || it == nil {
-               return nil
-       }
-       sigs := imethods(it)
-       methods := methods(t)
-       out := make([]*obj.LSym, 0, len(sigs))
-       // TODO(mdempsky): Short circuit before calling methods(t)?
-       // See discussion on CL 105039.
-       if len(sigs) == 0 {
-               return nil
-       }
-
-       // both sigs and methods are sorted by name,
-       // so we can find the intersect in a single pass
-       for _, m := range methods {
-               if m.name == sigs[0].name {
-                       out = append(out, m.isym)
-                       sigs = sigs[1:]
-                       if len(sigs) == 0 {
-                               break
-                       }
-               }
-       }
-
-       if len(sigs) != 0 {
-               base.Fatalf("incomplete itab")
-       }
-
-       return out
-}
-
-// ITabSym uses the information gathered in
-// CompileITabs to de-virtualize interface methods.
-// Since this is called by the SSA backend, it shouldn't
-// generate additional Nodes, Syms, etc.
-func ITabSym(it *obj.LSym, offset int64) *obj.LSym {
-       var syms []*obj.LSym
-       if it == nil {
-               return nil
-       }
-
-       for i := range itabs {
-               e := &itabs[i]
-               if e.lsym == it {
-                       syms = e.entries
-                       break
-               }
-       }
-       if syms == nil {
-               return nil
-       }
-
-       // keep this arithmetic in sync with *itab layout
-       methodnum := int((offset - 2*int64(types.PtrSize) - 8) / int64(types.PtrSize))
-       if methodnum >= len(syms) {
-               return nil
-       }
-       return syms[methodnum]
-}
-
 // NeedRuntimeType ensures that a runtime type descriptor is emitted for t.
 func NeedRuntimeType(t *types.Type) {
        if t.HasTParam() {
@@ -1346,29 +1258,57 @@ func WriteRuntimeTypes() {
        }
 }
 
-func WriteTabs() {
-       // process itabs
-       for _, i := range itabs {
-               // dump empty itab symbol into i.sym
-               // type itab struct {
-               //   inter  *interfacetype
-               //   _type  *_type
-               //   hash   uint32
-               //   _      [4]byte
-               //   fun    [1]uintptr // variable sized
-               // }
-               o := objw.SymPtr(i.lsym, 0, writeType(i.itype), 0)
-               o = objw.SymPtr(i.lsym, o, writeType(i.t), 0)
-               o = objw.Uint32(i.lsym, o, types.TypeHash(i.t)) // copy of type hash
-               o += 4                                          // skip unused field
-               for _, fn := range genfun(i.t, i.itype) {
-                       o = objw.SymPtrWeak(i.lsym, o, fn, 0) // method pointer for each method
-               }
-               // Nothing writes static itabs, so they are read only.
-               objw.Global(i.lsym, int32(o), int16(obj.DUPOK|obj.RODATA))
-               i.lsym.Set(obj.AttrContentAddressable, true)
+// writeITab writes the itab for concrete type typ implementing
+// interface iface.
+func writeITab(lsym *obj.LSym, typ, iface *types.Type) {
+       // TODO(mdempsky): Fix methodWrapper, geneq, and genhash (and maybe
+       // others) to stop clobbering these.
+       oldpos, oldfn := base.Pos, ir.CurFunc
+       defer func() { base.Pos, ir.CurFunc = oldpos, oldfn }()
+
+       if typ == nil || (typ.IsPtr() && typ.Elem() == nil) || typ.IsUntyped() || iface == nil || !iface.IsInterface() || iface.IsEmptyInterface() {
+               base.Fatalf("writeITab(%v, %v)", typ, iface)
+       }
+
+       sigs := iface.AllMethods().Slice()
+       entries := make([]*obj.LSym, 0, len(sigs))
+
+       // both sigs and methods are sorted by name,
+       // so we can find the intersection in a single pass
+       for _, m := range methods(typ) {
+               if m.name == sigs[0].Sym {
+                       entries = append(entries, m.isym)
+                       sigs = sigs[1:]
+                       if len(sigs) == 0 {
+                               break
+                       }
+               }
+       }
+       if len(sigs) != 0 {
+               base.Fatalf("incomplete itab")
+       }
+
+       // dump empty itab symbol into i.sym
+       // type itab struct {
+       //   inter  *interfacetype
+       //   _type  *_type
+       //   hash   uint32
+       //   _      [4]byte
+       //   fun    [1]uintptr // variable sized
+       // }
+       o := objw.SymPtr(lsym, 0, writeType(iface), 0)
+       o = objw.SymPtr(lsym, o, writeType(typ), 0)
+       o = objw.Uint32(lsym, o, types.TypeHash(typ)) // copy of type hash
+       o += 4                                        // skip unused field
+       for _, fn := range entries {
+               o = objw.SymPtrWeak(lsym, o, fn, 0) // method pointer for each method
        }
+       // Nothing writes static itabs, so they are read only.
+       objw.Global(lsym, int32(o), int16(obj.DUPOK|obj.RODATA))
+       lsym.Set(obj.AttrContentAddressable, true)
+}
 
+func WriteTabs() {
        // process ptabs
        if types.LocalPkg.Name == "main" && len(ptabs) > 0 {
                ot := 0
@@ -1926,20 +1866,10 @@ func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSy
        ir.CurFunc = fn
        typecheck.Stmts(fn.Body)
 
-       // TODO(mdempsky): Make this unconditional. The exporter now
-       // includes all of the inline bodies we need, and the "importedType"
-       // logic above now correctly suppresses compiling out-of-package
-       // types that we might not have inline bodies for. The only problem
-       // now is that the extra inlining can now introduce further new
-       // itabs, and gc.dumpdata's ad hoc compile loop doesn't handle this.
-       //
-       // CL 327871 will address this by writing itabs and generating
-       // wrappers as part of the loop, so we won't have to worry about
-       // "itabs changed after compile functions loop" errors anymore.
-       if rcvr.IsPtr() && rcvr.Elem() == method.Type.Recv().Type && rcvr.Elem().Sym() != nil {
+       if AfterGlobalEscapeAnalysis {
                inline.InlineCalls(fn)
+               escape.Batch([]*ir.Func{fn}, false)
        }
-       escape.Batch([]*ir.Func{fn}, false)
 
        ir.CurFunc = nil
        typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
@@ -1947,6 +1877,12 @@ func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSy
        return lsym
 }
 
+// AfterGlobalEscapeAnalysis tracks whether package gc has already
+// performed the main, global escape analysis pass. If so,
+// methodWrapper takes responsibility for escape analyzing any
+// generated wrappers.
+var AfterGlobalEscapeAnalysis bool
+
 var ZeroSize int64
 
 // MarkTypeUsedInInterface marks that type t is converted to an interface.
index 61c65f9e54c2d081a4d80f8baf155cec6955eaac..b08a394368653d20330d22d13fd783215006d73c 100644 (file)
@@ -149,12 +149,6 @@ type Frontend interface {
        // for the parts of that compound type.
        SplitSlot(parent *LocalSlot, suffix string, offset int64, t *types.Type) LocalSlot
 
-       // DerefItab dereferences an itab function
-       // entry, given the symbol of the itab and
-       // the byte offset of the function pointer.
-       // It may return nil.
-       DerefItab(sym *obj.LSym, offset int64) *obj.LSym
-
        // Line returns a string describing the given position.
        Line(src.XPos) string
 
index 375c4d5a5605f531b6b5ab761be276f41d72031b..115d5639333d89fd876f6fabbe7713d6a8fa77bd 100644 (file)
@@ -745,27 +745,21 @@ func uaddOvf(a, b int64) bool {
        return uint64(a)+uint64(b) < uint64(a)
 }
 
-// de-virtualize an InterCall
-// 'sym' is the symbol for the itab
-func devirt(v *Value, aux Aux, sym Sym, offset int64) *AuxCall {
-       f := v.Block.Func
-       n, ok := sym.(*obj.LSym)
-       if !ok {
+// loadLSymOffset simulates reading a word at an offset into a
+// read-only symbol's runtime memory. If it would read a pointer to
+// another symbol, that symbol is returned. Otherwise, it returns nil.
+func loadLSymOffset(lsym *obj.LSym, offset int64) *obj.LSym {
+       if lsym.Type != objabi.SRODATA {
                return nil
        }
-       lsym := f.fe.DerefItab(n, offset)
-       if f.pass.debug > 0 {
-               if lsym != nil {
-                       f.Warnl(v.Pos, "de-virtualizing call")
-               } else {
-                       f.Warnl(v.Pos, "couldn't de-virtualize call")
+
+       for _, r := range lsym.R {
+               if int64(r.Off) == offset && r.Type&^objabi.R_WEAK == objabi.R_ADDR && r.Add == 0 {
+                       return r.Sym
                }
        }
-       if lsym == nil {
-               return nil
-       }
-       va := aux.(*AuxCall)
-       return StaticAuxCall(lsym, va.abiInfo)
+
+       return nil
 }
 
 // de-virtualize an InterLECall
@@ -776,18 +770,14 @@ func devirtLESym(v *Value, aux Aux, sym Sym, offset int64) *obj.LSym {
                return nil
        }
 
-       f := v.Block.Func
-       lsym := f.fe.DerefItab(n, offset)
-       if f.pass.debug > 0 {
+       lsym := loadLSymOffset(n, offset)
+       if f := v.Block.Func; f.pass.debug > 0 {
                if lsym != nil {
                        f.Warnl(v.Pos, "de-virtualizing call")
                } else {
                        f.Warnl(v.Pos, "couldn't de-virtualize call")
                }
        }
-       if lsym == nil {
-               return nil
-       }
        return lsym
 }
 
index 0fbb39cfbb0c2efb8c68b6d4cfc0c8bf890780e9..7a6bf878e1bc5977d73865a7600339372ed67766 100644 (file)
@@ -7401,10 +7401,6 @@ func (e *ssafn) Auto(pos src.XPos, t *types.Type) *ir.Name {
        return typecheck.TempAt(pos, e.curfn, t) // Note: adds new auto to e.curfn.Func.Dcl list
 }
 
-func (e *ssafn) DerefItab(it *obj.LSym, offset int64) *obj.LSym {
-       return reflectdata.ITabSym(it, offset)
-}
-
 // SplitSlot returns a slot representing the data of parent starting at offset.
 func (e *ssafn) SplitSlot(parent *ssa.LocalSlot, suffix string, offset int64, t *types.Type) ssa.LocalSlot {
        node := parent.N
index 0e306eaea83f791ddb45aa6be38f88a2ea243b14..79b2402fe705521d6f2c0d0fad4be4121380d2f0 100644 (file)
@@ -379,14 +379,6 @@ func Assignop(src, dst *types.Type) (ir.Op, string) {
                var missing, have *types.Field
                var ptr int
                if implements(src, dst, &missing, &have, &ptr) {
-                       // Call NeedITab/ITabAddr so that (src, dst)
-                       // gets added to itabs early, which allows
-                       // us to de-virtualize calls through this
-                       // type/interface pair later. See CompileITabs in reflect.go
-                       if types.IsDirectIface(src) && !dst.IsEmptyInterface() {
-                               NeedITab(src, dst)
-                       }
-
                        return ir.OCONVIFACE, ""
                }
 
index 8454b8d5b34baf747b965f8c783ae5d776c52bdd..b1a4e193d638ba6e58a02224b1aefbfcb4142b1a 100644 (file)
@@ -24,7 +24,6 @@ var inimport bool // set during import
 var TypecheckAllowed bool
 
 var (
-       NeedITab        = func(t, itype *types.Type) {}
        NeedRuntimeType = func(*types.Type) {}
 )