[dev.link] cmd/link: implement live method tracking in deadcode2

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>
This commit is contained in:
Cherry Zhang 2019-10-04 15:08:58 -04:00
parent cc21e4d130
commit 7d6f79e617
2 changed files with 268 additions and 16 deletions

View file

@ -5,26 +5,29 @@
package ld package ld
import ( import (
"bytes"
"cmd/internal/objabi" "cmd/internal/objabi"
"cmd/internal/sys" "cmd/internal/sys"
"cmd/link/internal/objfile" "cmd/link/internal/objfile"
"cmd/link/internal/sym" "cmd/link/internal/sym"
"fmt" "fmt"
"strings" "strings"
"unicode"
) )
var _ = fmt.Print var _ = fmt.Print
// TODO: // TODO:
// - Live method tracking: // - Live method tracking:
// Prune methods that are not directly called and cannot // The special handling of reflect.Type.Method has not
// be potentially called by interface or reflect call. // been implemented.
// For now, all the methods from reachable type are alive.
// - Shared object support: // - Shared object support:
// It basically marks everything. We could consider using // It basically marks everything. We could consider using
// a different mechanism to represent it. // a different mechanism to represent it.
// - Field tracking support: // - Field tracking support:
// It needs to record from where the symbol is referenced. // 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 type workQueue []objfile.Sym
@ -36,10 +39,15 @@ type deadcodePass2 struct {
ctxt *Link ctxt *Link
loader *objfile.Loader loader *objfile.Loader
wq workQueue 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() { func (d *deadcodePass2) init() {
d.loader.InitReachable() d.loader.InitReachable()
d.ifaceMethod = make(map[methodsig]bool)
var names []string var names []string
@ -86,6 +94,21 @@ func (d *deadcodePass2) init() {
func (d *deadcodePass2) flood() { func (d *deadcodePass2) flood() {
for !d.wq.empty() { for !d.wq.empty() {
symIdx := d.wq.pop() 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) relocs := d.loader.Relocs(symIdx)
for i := 0; i < relocs.Count; i++ { for i := 0; i < relocs.Count; i++ {
r := relocs.At(i) r := relocs.At(i)
@ -93,8 +116,12 @@ func (d *deadcodePass2) flood() {
continue continue
} }
if r.Type == objabi.R_METHODOFF { if r.Type == objabi.R_METHODOFF {
// TODO: we should do something about it if i+2 >= relocs.Count {
// For now, all the methods are considered live panic("expect three consecutive R_METHODOFF relocs")
}
methods = append(methods, methodref2{src: symIdx, r: i})
i += 2
continue
} }
d.mark(r.Sym) d.mark(r.Sym)
} }
@ -102,6 +129,20 @@ func (d *deadcodePass2) flood() {
for i := 0; i < naux; i++ { for i := 0; i < naux; i++ {
d.mark(d.loader.AuxSym(symIdx, 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) { func deadcode2(ctxt *Link) {
loader := ctxt.loader loader := ctxt.loader
d := deadcodePass2{ctxt: ctxt, loader: loader} d := deadcodePass2{ctxt: ctxt, loader: loader}
d.init() d.init()
d.flood() 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() n := loader.NSym()
if ctxt.BuildMode != BuildModeShared { if ctxt.BuildMode != BuildModeShared {
// Keep a itablink if the symbol it points at is being kept. // Keep a itablink if the symbol it points at is being kept.
// (When BuildModeShared, always keep itablinks.) // (When BuildModeShared, always keep itablinks.)
for i := 1; i < n; i++ { for i := 1; i < n; i++ {
s := objfile.Sym(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) relocs := loader.Relocs(s)
if relocs.Count > 0 && loader.Reachable.Has(relocs.At(0).Sym) { if relocs.Count > 0 && loader.Reachable.Has(relocs.At(0).Sym) {
loader.Reachable.Set(s) 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)))
}

View file

@ -235,10 +235,10 @@ func (l *Loader) NSym() int {
// Returns the raw (unpatched) name of the i-th symbol. // Returns the raw (unpatched) name of the i-th symbol.
func (l *Loader) RawSymName(i Sym) string { func (l *Loader) RawSymName(i Sym) string {
r, li := l.ToLocal(i) if l.extStart != 0 && i >= l.extStart {
if r == nil {
return "" return ""
} }
r, li := l.ToLocal(i)
osym := goobj2.Sym{} osym := goobj2.Sym{}
osym.Read(r.Reader, r.SymOff(li)) osym.Read(r.Reader, r.SymOff(li))
return osym.Name return osym.Name
@ -246,10 +246,10 @@ func (l *Loader) RawSymName(i Sym) string {
// Returns the (patched) name of the i-th symbol. // Returns the (patched) name of the i-th symbol.
func (l *Loader) SymName(i Sym) string { func (l *Loader) SymName(i Sym) string {
r, li := l.ToLocal(i) if l.extStart != 0 && i >= l.extStart {
if r == nil {
return "" return ""
} }
r, li := l.ToLocal(i)
osym := goobj2.Sym{} osym := goobj2.Sym{}
osym.Read(r.Reader, r.SymOff(li)) osym.Read(r.Reader, r.SymOff(li))
return strings.Replace(osym.Name, "\"\".", r.pkgprefix, -1) 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. // Returns the type of the i-th symbol.
func (l *Loader) SymType(i Sym) sym.SymKind { func (l *Loader) SymType(i Sym) sym.SymKind {
r, li := l.ToLocal(i) if l.extStart != 0 && i >= l.extStart {
if r == nil {
return 0 return 0
} }
r, li := l.ToLocal(i)
osym := goobj2.Sym{} osym := goobj2.Sym{}
osym.Read(r.Reader, r.SymOff(li)) osym.Read(r.Reader, r.SymOff(li))
return sym.AbiSymKindToSymKind[objabi.SymKind(osym.Type)] 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. // Returns the number of aux symbols given a global index.
func (l *Loader) NAux(i Sym) int { func (l *Loader) NAux(i Sym) int {
r, li := l.ToLocal(i) if l.extStart != 0 && i >= l.extStart {
if r == nil {
return 0 return 0
} }
r, li := l.ToLocal(i)
return r.NAux(li) return r.NAux(li)
} }
// Returns the referred symbol of the j-th aux symbol of the i-th // Returns the referred symbol of the j-th aux symbol of the i-th
// symbol. // symbol.
func (l *Loader) AuxSym(i Sym, j int) Sym { func (l *Loader) AuxSym(i Sym, j int) Sym {
if l.extStart != 0 && i >= l.extStart {
return 0
}
r, li := l.ToLocal(i) r, li := l.ToLocal(i)
a := goobj2.Aux{} a := goobj2.Aux{}
a.Read(r.Reader, r.AuxOff(li, j)) 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. // Relocs returns a Relocs object for the given global sym.
func (l *Loader) Relocs(i Sym) Relocs { func (l *Loader) Relocs(i Sym) Relocs {
r, li := l.ToLocal(i) if l.extStart != 0 && i >= l.extStart {
if r == nil {
return Relocs{} return Relocs{}
} }
r, li := l.ToLocal(i)
return l.relocs(r, li) return l.relocs(r, li)
} }