]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.link] cmd/link: implement live method tracking in deadcode2
authorCherry Zhang <cherryyz@google.com>
Fri, 4 Oct 2019 19:08:58 +0000 (15:08 -0400)
committerCherry Zhang <cherryyz@google.com>
Mon, 14 Oct 2019 14:29:03 +0000 (14:29 +0000)
This essentially replicates the logic of live method tracking and
type symbol decoding, rewritten to operate on indices instead of
Symbols.

TODO: the special handling of reflect.Type.Method has not been
implemented.

TODO: the symbol name is used too much. It ought to be a better
way to do it.

Change-Id: I860ee7a506c00833902e4870d15aea698a705dd9
Reviewed-on: https://go-review.googlesource.com/c/go/+/199078
Reviewed-by: Than McIntosh <thanm@google.com>
src/cmd/link/internal/ld/deadcode2.go
src/cmd/link/internal/objfile/objfile2.go

index 354d15837181afc600c5ee81a67b26203f531a70..b1504e2e8a42562467c2b2c347f85ba22458e4c4 100644 (file)
@@ -5,26 +5,29 @@
 package ld
 
 import (
+       "bytes"
        "cmd/internal/objabi"
        "cmd/internal/sys"
        "cmd/link/internal/objfile"
        "cmd/link/internal/sym"
        "fmt"
        "strings"
+       "unicode"
 )
 
 var _ = fmt.Print
 
 // TODO:
 // - Live method tracking:
-//   Prune methods that are not directly called and cannot
-//   be potentially called by interface or reflect call.
-//   For now, all the methods from reachable type are alive.
+//   The special handling of reflect.Type.Method has not
+//   been implemented.
 // - Shared object support:
 //   It basically marks everything. We could consider using
 //   a different mechanism to represent it.
 // - Field tracking support:
 //   It needs to record from where the symbol is referenced.
+// - Debug output:
+//   Emit messages about which symbols are kept or deleted.
 
 type workQueue []objfile.Sym
 
@@ -36,10 +39,15 @@ type deadcodePass2 struct {
        ctxt   *Link
        loader *objfile.Loader
        wq     workQueue
+
+       ifaceMethod     map[methodsig]bool // methods declared in reached interfaces
+       markableMethods []methodref2       // methods of reached types
+       reflectMethod   bool               // TODO: this is not set for now
 }
 
 func (d *deadcodePass2) init() {
        d.loader.InitReachable()
+       d.ifaceMethod = make(map[methodsig]bool)
 
        var names []string
 
@@ -86,6 +94,21 @@ func (d *deadcodePass2) init() {
 func (d *deadcodePass2) flood() {
        for !d.wq.empty() {
                symIdx := d.wq.pop()
+
+               name := d.loader.RawSymName(symIdx)
+               if strings.HasPrefix(name, "type.") && name[5] != '.' { // TODO: use an attribute instead of checking name
+                       p := d.loader.Data(symIdx)
+                       if len(p) != 0 && decodetypeKind(d.ctxt.Arch, p)&kindMask == kindInterface {
+                               for _, sig := range decodeIfaceMethods2(d.loader, d.ctxt.Arch, symIdx) {
+                                       if d.ctxt.Debugvlog > 1 {
+                                               d.ctxt.Logf("reached iface method: %s\n", sig)
+                                       }
+                                       d.ifaceMethod[sig] = true
+                               }
+                       }
+               }
+
+               var methods []methodref2
                relocs := d.loader.Relocs(symIdx)
                for i := 0; i < relocs.Count; i++ {
                        r := relocs.At(i)
@@ -93,8 +116,12 @@ func (d *deadcodePass2) flood() {
                                continue
                        }
                        if r.Type == objabi.R_METHODOFF {
-                               // TODO: we should do something about it
-                               // For now, all the methods are considered live
+                               if i+2 >= relocs.Count {
+                                       panic("expect three consecutive R_METHODOFF relocs")
+                               }
+                               methods = append(methods, methodref2{src: symIdx, r: i})
+                               i += 2
+                               continue
                        }
                        d.mark(r.Sym)
                }
@@ -102,6 +129,20 @@ func (d *deadcodePass2) flood() {
                for i := 0; i < naux; i++ {
                        d.mark(d.loader.AuxSym(symIdx, i))
                }
+
+               if len(methods) != 0 {
+                       // Decode runtime type information for type methods
+                       // to help work out which methods can be called
+                       // dynamically via interfaces.
+                       methodsigs := decodetypeMethods2(d.loader, d.ctxt.Arch, symIdx)
+                       if len(methods) != len(methodsigs) {
+                               panic(fmt.Sprintf("%q has %d method relocations for %d methods", d.loader.SymName(symIdx), len(methods), len(methodsigs)))
+                       }
+                       for i, m := range methodsigs {
+                               methods[i].m = m
+                       }
+                       d.markableMethods = append(d.markableMethods, methods...)
+               }
        }
 }
 
@@ -112,19 +153,67 @@ func (d *deadcodePass2) mark(symIdx objfile.Sym) {
        }
 }
 
+func (d *deadcodePass2) markMethod(m methodref2) {
+       relocs := d.loader.Relocs(m.src)
+       d.mark(relocs.At(m.r).Sym)
+       d.mark(relocs.At(m.r + 1).Sym)
+       d.mark(relocs.At(m.r + 2).Sym)
+}
+
 func deadcode2(ctxt *Link) {
        loader := ctxt.loader
        d := deadcodePass2{ctxt: ctxt, loader: loader}
        d.init()
        d.flood()
 
+       callSym := loader.Lookup("reflect.Value.Call", sym.SymVerABIInternal)
+       methSym := loader.Lookup("reflect.Value.Method", sym.SymVerABIInternal)
+       reflectSeen := false
+
+       if ctxt.DynlinkingGo() {
+               // Exported methods may satisfy interfaces we don't know
+               // about yet when dynamically linking.
+               reflectSeen = true
+       }
+
+       for {
+               if !reflectSeen {
+                       if d.reflectMethod || (callSym != 0 && loader.Reachable.Has(callSym)) || (methSym != 0 && loader.Reachable.Has(methSym)) {
+                               // Methods might be called via reflection. Give up on
+                               // static analysis, mark all exported methods of
+                               // all reachable types as reachable.
+                               reflectSeen = true
+                       }
+               }
+
+               // Mark all methods that could satisfy a discovered
+               // interface as reachable. We recheck old marked interfaces
+               // as new types (with new methods) may have been discovered
+               // in the last pass.
+               rem := d.markableMethods[:0]
+               for _, m := range d.markableMethods {
+                       if (reflectSeen && m.isExported()) || d.ifaceMethod[m.m] {
+                               d.markMethod(m)
+                       } else {
+                               rem = append(rem, m)
+                       }
+               }
+               d.markableMethods = rem
+
+               if d.wq.empty() {
+                       // No new work was discovered. Done.
+                       break
+               }
+               d.flood()
+       }
+
        n := loader.NSym()
        if ctxt.BuildMode != BuildModeShared {
                // Keep a itablink if the symbol it points at is being kept.
                // (When BuildModeShared, always keep itablinks.)
                for i := 1; i < n; i++ {
                        s := objfile.Sym(i)
-                       if strings.HasPrefix(loader.RawSymName(s), "go.itablink.") {
+                       if strings.HasPrefix(loader.RawSymName(s), "go.itablink.") { // TODO: use an attribute instread of checking name
                                relocs := loader.Relocs(s)
                                if relocs.Count > 0 && loader.Reachable.Has(relocs.At(0).Sym) {
                                        loader.Reachable.Set(s)
@@ -143,3 +232,154 @@ func deadcode2(ctxt *Link) {
                }
        }
 }
+
+// methodref2 holds the relocations from a receiver type symbol to its
+// method. There are three relocations, one for each of the fields in
+// the reflect.method struct: mtyp, ifn, and tfn.
+type methodref2 struct {
+       m   methodsig
+       src objfile.Sym // receiver type symbol
+       r   int         // the index of R_METHODOFF relocations
+}
+
+func (m methodref2) isExported() bool {
+       for _, r := range m.m {
+               return unicode.IsUpper(r)
+       }
+       panic("methodref has no signature")
+}
+
+// decodeMethodSig2 decodes an array of method signature information.
+// Each element of the array is size bytes. The first 4 bytes is a
+// nameOff for the method name, and the next 4 bytes is a typeOff for
+// the function type.
+//
+// Conveniently this is the layout of both runtime.method and runtime.imethod.
+func decodeMethodSig2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym, off, size, count int) []methodsig {
+       var buf bytes.Buffer
+       var methods []methodsig
+       for i := 0; i < count; i++ {
+               buf.WriteString(decodetypeName2(loader, symIdx, off))
+               mtypSym := decodeRelocSym2(loader, symIdx, int32(off+4))
+               mp := loader.Data(mtypSym)
+
+               buf.WriteRune('(')
+               inCount := decodetypeFuncInCount(arch, mp)
+               for i := 0; i < inCount; i++ {
+                       if i > 0 {
+                               buf.WriteString(", ")
+                       }
+                       a := decodetypeFuncInType2(loader, arch, mtypSym, i)
+                       buf.WriteString(loader.SymName(a))
+               }
+               buf.WriteString(") (")
+               outCount := decodetypeFuncOutCount(arch, mp)
+               for i := 0; i < outCount; i++ {
+                       if i > 0 {
+                               buf.WriteString(", ")
+                       }
+                       a := decodetypeFuncOutType2(loader, arch, mtypSym, i)
+                       buf.WriteString(loader.SymName(a))
+               }
+               buf.WriteRune(')')
+
+               off += size
+               methods = append(methods, methodsig(buf.String()))
+               buf.Reset()
+       }
+       return methods
+}
+
+func decodeIfaceMethods2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym) []methodsig {
+       p := loader.Data(symIdx)
+       if decodetypeKind(arch, p)&kindMask != kindInterface {
+               panic(fmt.Sprintf("symbol %q is not an interface", loader.SymName(symIdx)))
+       }
+       rel := decodeReloc2(loader, symIdx, int32(commonsize(arch)+arch.PtrSize))
+       if rel.Sym == 0 {
+               return nil
+       }
+       if rel.Sym != symIdx {
+               panic(fmt.Sprintf("imethod slice pointer in %q leads to a different symbol", loader.SymName(symIdx)))
+       }
+       off := int(rel.Add) // array of reflect.imethod values
+       numMethods := int(decodetypeIfaceMethodCount(arch, p))
+       sizeofIMethod := 4 + 4
+       return decodeMethodSig2(loader, arch, symIdx, off, sizeofIMethod, numMethods)
+}
+
+func decodetypeMethods2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym) []methodsig {
+       p := loader.Data(symIdx)
+       if !decodetypeHasUncommon(arch, p) {
+               panic(fmt.Sprintf("no methods on %q", loader.SymName(symIdx)))
+       }
+       off := commonsize(arch) // reflect.rtype
+       switch decodetypeKind(arch, p) & kindMask {
+       case kindStruct: // reflect.structType
+               off += 4 * arch.PtrSize
+       case kindPtr: // reflect.ptrType
+               off += arch.PtrSize
+       case kindFunc: // reflect.funcType
+               off += arch.PtrSize // 4 bytes, pointer aligned
+       case kindSlice: // reflect.sliceType
+               off += arch.PtrSize
+       case kindArray: // reflect.arrayType
+               off += 3 * arch.PtrSize
+       case kindChan: // reflect.chanType
+               off += 2 * arch.PtrSize
+       case kindMap: // reflect.mapType
+               off += 4*arch.PtrSize + 8
+       case kindInterface: // reflect.interfaceType
+               off += 3 * arch.PtrSize
+       default:
+               // just Sizeof(rtype)
+       }
+
+       mcount := int(decodeInuxi(arch, p[off+4:], 2))
+       moff := int(decodeInuxi(arch, p[off+4+2+2:], 4))
+       off += moff                // offset to array of reflect.method values
+       const sizeofMethod = 4 * 4 // sizeof reflect.method in program
+       return decodeMethodSig2(loader, arch, symIdx, off, sizeofMethod, mcount)
+}
+
+func decodeReloc2(loader *objfile.Loader, symIdx objfile.Sym, off int32) objfile.Reloc {
+       relocs := loader.Relocs(symIdx)
+       for j := 0; j < relocs.Count; j++ {
+               rel := relocs.At(j)
+               if rel.Off == off {
+                       return rel
+               }
+       }
+       return objfile.Reloc{}
+}
+
+func decodeRelocSym2(loader *objfile.Loader, symIdx objfile.Sym, off int32) objfile.Sym {
+       return decodeReloc2(loader, symIdx, off).Sym
+}
+
+// decodetypeName2 decodes the name from a reflect.name.
+func decodetypeName2(loader *objfile.Loader, symIdx objfile.Sym, off int) string {
+       r := decodeRelocSym2(loader, symIdx, int32(off))
+       if r == 0 {
+               return ""
+       }
+
+       data := loader.Data(r)
+       namelen := int(uint16(data[1])<<8 | uint16(data[2]))
+       return string(data[3 : 3+namelen])
+}
+
+func decodetypeFuncInType2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym, i int) objfile.Sym {
+       uadd := commonsize(arch) + 4
+       if arch.PtrSize == 8 {
+               uadd += 4
+       }
+       if decodetypeHasUncommon(arch, loader.Data(symIdx)) {
+               uadd += uncommonSize()
+       }
+       return decodeRelocSym2(loader, symIdx, int32(uadd+i*arch.PtrSize))
+}
+
+func decodetypeFuncOutType2(loader *objfile.Loader, arch *sys.Arch, symIdx objfile.Sym, i int) objfile.Sym {
+       return decodetypeFuncInType2(loader, arch, symIdx, i+decodetypeFuncInCount(arch, loader.Data(symIdx)))
+}
index 8208125cba9584bf824e58b8c0968003c7477da9..4a91a979262b78d919e9719c28e52f997ded128f 100644 (file)
@@ -235,10 +235,10 @@ func (l *Loader) NSym() int {
 
 // Returns the raw (unpatched) name of the i-th symbol.
 func (l *Loader) RawSymName(i Sym) string {
-       r, li := l.ToLocal(i)
-       if r == nil {
+       if l.extStart != 0 && i >= l.extStart {
                return ""
        }
+       r, li := l.ToLocal(i)
        osym := goobj2.Sym{}
        osym.Read(r.Reader, r.SymOff(li))
        return osym.Name
@@ -246,10 +246,10 @@ func (l *Loader) RawSymName(i Sym) string {
 
 // Returns the (patched) name of the i-th symbol.
 func (l *Loader) SymName(i Sym) string {
-       r, li := l.ToLocal(i)
-       if r == nil {
+       if l.extStart != 0 && i >= l.extStart {
                return ""
        }
+       r, li := l.ToLocal(i)
        osym := goobj2.Sym{}
        osym.Read(r.Reader, r.SymOff(li))
        return strings.Replace(osym.Name, "\"\".", r.pkgprefix, -1)
@@ -257,27 +257,39 @@ func (l *Loader) SymName(i Sym) string {
 
 // Returns the type of the i-th symbol.
 func (l *Loader) SymType(i Sym) sym.SymKind {
-       r, li := l.ToLocal(i)
-       if r == nil {
+       if l.extStart != 0 && i >= l.extStart {
                return 0
        }
+       r, li := l.ToLocal(i)
        osym := goobj2.Sym{}
        osym.Read(r.Reader, r.SymOff(li))
        return sym.AbiSymKindToSymKind[objabi.SymKind(osym.Type)]
 }
 
+// Returns the symbol content of the i-th symbol. i is global index.
+func (l *Loader) Data(i Sym) []byte {
+       if l.extStart != 0 && i >= l.extStart {
+               return nil
+       }
+       r, li := l.ToLocal(i)
+       return r.Data(li)
+}
+
 // Returns the number of aux symbols given a global index.
 func (l *Loader) NAux(i Sym) int {
-       r, li := l.ToLocal(i)
-       if r == nil {
+       if l.extStart != 0 && i >= l.extStart {
                return 0
        }
+       r, li := l.ToLocal(i)
        return r.NAux(li)
 }
 
 // Returns the referred symbol of the j-th aux symbol of the i-th
 // symbol.
 func (l *Loader) AuxSym(i Sym, j int) Sym {
+       if l.extStart != 0 && i >= l.extStart {
+               return 0
+       }
        r, li := l.ToLocal(i)
        a := goobj2.Aux{}
        a.Read(r.Reader, r.AuxOff(li, j))
@@ -305,10 +317,10 @@ func (relocs *Relocs) At(j int) Reloc {
 
 // Relocs returns a Relocs object for the given global sym.
 func (l *Loader) Relocs(i Sym) Relocs {
-       r, li := l.ToLocal(i)
-       if r == nil {
+       if l.extStart != 0 && i >= l.extStart {
                return Relocs{}
        }
+       r, li := l.ToLocal(i)
        return l.relocs(r, li)
 }