| | |
| | |
| | |
| |
|
| | package ld |
| |
|
| | import ( |
| | "cmd/internal/goobj" |
| | "cmd/internal/objabi" |
| | "cmd/internal/sys" |
| | "cmd/link/internal/loader" |
| | "cmd/link/internal/sym" |
| | "fmt" |
| | "internal/abi" |
| | "internal/buildcfg" |
| | "strings" |
| | "unicode" |
| | ) |
| |
|
| | var _ = fmt.Print |
| |
|
| | type deadcodePass struct { |
| | ctxt *Link |
| | ldr *loader.Loader |
| | wq heap |
| |
|
| | ifaceMethod map[methodsig]bool |
| | genericIfaceMethod map[string]bool |
| | markableMethods []methodref |
| | reflectSeen bool |
| | dynlink bool |
| |
|
| | methodsigstmp []methodsig |
| | pkginits []loader.Sym |
| | mapinitnoop loader.Sym |
| | } |
| |
|
| | func (d *deadcodePass) init() { |
| | d.ldr.InitReachable() |
| | d.ifaceMethod = make(map[methodsig]bool) |
| | d.genericIfaceMethod = make(map[string]bool) |
| | if buildcfg.Experiment.FieldTrack { |
| | d.ldr.Reachparent = make([]loader.Sym, d.ldr.NSym()) |
| | } |
| | d.dynlink = d.ctxt.DynlinkingGo() |
| |
|
| | if d.ctxt.BuildMode == BuildModeShared { |
| | |
| | |
| | n := d.ldr.NDef() |
| | for i := 1; i < n; i++ { |
| | s := loader.Sym(i) |
| | if d.ldr.SymType(s).IsText() && d.ldr.SymSize(s) == 0 { |
| | |
| | |
| | |
| | continue |
| | } |
| | d.mark(s, 0) |
| | } |
| | d.mark(d.ctxt.mainInittasks, 0) |
| | return |
| | } |
| |
|
| | var names []string |
| |
|
| | |
| | |
| | if d.ctxt.linkShared && (d.ctxt.BuildMode == BuildModeExe || d.ctxt.BuildMode == BuildModePIE) { |
| | names = append(names, "main.main", "main..inittask") |
| | } else { |
| | |
| | if d.ctxt.LinkMode == LinkExternal && (d.ctxt.BuildMode == BuildModeExe || d.ctxt.BuildMode == BuildModePIE) { |
| | if d.ctxt.HeadType == objabi.Hwindows && d.ctxt.Arch.Family == sys.I386 { |
| | *flagEntrySymbol = "_main" |
| | } else { |
| | *flagEntrySymbol = "main" |
| | } |
| | } |
| | names = append(names, *flagEntrySymbol) |
| | } |
| | |
| | |
| | names = append(names, "runtime.unreachableMethod") |
| | if d.ctxt.BuildMode == BuildModePlugin { |
| | names = append(names, objabi.PathToPrefix(*flagPluginPath)+"..inittask", objabi.PathToPrefix(*flagPluginPath)+".main", "go:plugin.tabs") |
| |
|
| | |
| | |
| | exportsIdx := d.ldr.Lookup("go:plugin.exports", 0) |
| | if exportsIdx != 0 { |
| | relocs := d.ldr.Relocs(exportsIdx) |
| | for i := 0; i < relocs.Count(); i++ { |
| | d.mark(relocs.At(i).Sym(), 0) |
| | } |
| | } |
| | } |
| |
|
| | if d.ctxt.Debugvlog > 1 { |
| | d.ctxt.Logf("deadcode start names: %v\n", names) |
| | } |
| |
|
| | for _, name := range names { |
| | |
| | d.mark(d.ldr.Lookup(name, 0), 0) |
| | if abiInternalVer != 0 { |
| | |
| | d.mark(d.ldr.Lookup(name, abiInternalVer), 0) |
| | } |
| | } |
| |
|
| | |
| | for _, s := range d.ctxt.dynexp { |
| | if d.ctxt.Debugvlog > 1 { |
| | d.ctxt.Logf("deadcode start dynexp: %s<%d>\n", d.ldr.SymName(s), d.ldr.SymVersion(s)) |
| | } |
| | d.mark(s, 0) |
| | } |
| | |
| | for _, s := range d.ldr.WasmExports { |
| | if d.ctxt.Debugvlog > 1 { |
| | d.ctxt.Logf("deadcode start wasmexport: %s<%d>\n", d.ldr.SymName(s), d.ldr.SymVersion(s)) |
| | } |
| | d.mark(s, 0) |
| | } |
| |
|
| | d.mapinitnoop = d.ldr.Lookup("runtime.mapinitnoop", abiInternalVer) |
| | if d.mapinitnoop == 0 { |
| | panic("could not look up runtime.mapinitnoop") |
| | } |
| | if d.ctxt.mainInittasks != 0 { |
| | d.mark(d.ctxt.mainInittasks, 0) |
| | } |
| | } |
| |
|
| | func (d *deadcodePass) flood() { |
| | var methods []methodref |
| | for !d.wq.empty() { |
| | symIdx := d.wq.pop() |
| |
|
| | |
| | |
| | d.reflectSeen = d.reflectSeen || d.ldr.IsReflectMethod(symIdx) |
| |
|
| | isgotype := d.ldr.IsGoType(symIdx) |
| | relocs := d.ldr.Relocs(symIdx) |
| | var usedInIface bool |
| |
|
| | if isgotype { |
| | if d.dynlink { |
| | |
| | |
| | d.ldr.SetAttrUsedInIface(symIdx, true) |
| | } |
| | usedInIface = d.ldr.AttrUsedInIface(symIdx) |
| | } |
| |
|
| | methods = methods[:0] |
| | for i := 0; i < relocs.Count(); i++ { |
| | r := relocs.At(i) |
| | if r.Weak() { |
| | convertWeakToStrong := false |
| | |
| | |
| | |
| | if d.ctxt.linkShared && d.ldr.IsItab(symIdx) { |
| | convertWeakToStrong = true |
| | } |
| | |
| | |
| | |
| | |
| | |
| | if d.ctxt.canUsePlugins && r.Type().IsDirectCall() { |
| | convertWeakToStrong = true |
| | } |
| | if !convertWeakToStrong { |
| | |
| | continue |
| | } |
| | } |
| | t := r.Type() |
| | switch t { |
| | case objabi.R_METHODOFF: |
| | if i+2 >= relocs.Count() { |
| | panic("expect three consecutive R_METHODOFF relocs") |
| | } |
| | if usedInIface { |
| | methods = append(methods, methodref{src: symIdx, r: i}) |
| | |
| | |
| | |
| | |
| | |
| | rs := r.Sym() |
| | if !d.ldr.AttrUsedInIface(rs) { |
| | d.ldr.SetAttrUsedInIface(rs, true) |
| | if d.ldr.AttrReachable(rs) { |
| | d.ldr.SetAttrReachable(rs, false) |
| | d.mark(rs, symIdx) |
| | } |
| | } |
| | } |
| | i += 2 |
| | continue |
| | case objabi.R_USETYPE: |
| | |
| | |
| | |
| | continue |
| | case objabi.R_USEIFACE: |
| | |
| | |
| | |
| | rs := r.Sym() |
| | if d.ldr.IsItab(rs) { |
| | |
| | |
| | rs = decodeItabType(d.ldr, d.ctxt.Arch, rs) |
| | } |
| | if !d.ldr.IsGoType(rs) && !d.ctxt.linkShared { |
| | panic(fmt.Sprintf("R_USEIFACE in %s references %s which is not a type or itab", d.ldr.SymName(symIdx), d.ldr.SymName(rs))) |
| | } |
| | if !d.ldr.AttrUsedInIface(rs) { |
| | d.ldr.SetAttrUsedInIface(rs, true) |
| | if d.ldr.AttrReachable(rs) { |
| | d.ldr.SetAttrReachable(rs, false) |
| | d.mark(rs, symIdx) |
| | } |
| | } |
| | continue |
| | case objabi.R_USEIFACEMETHOD: |
| | |
| | |
| | rs := r.Sym() |
| | if d.ctxt.linkShared && (d.ldr.SymType(rs) == sym.SDYNIMPORT || d.ldr.SymType(rs) == sym.Sxxx) { |
| | |
| | |
| | |
| | continue |
| | } |
| | m := d.decodeIfaceMethod(d.ldr, d.ctxt.Arch, rs, r.Add()) |
| | if d.ctxt.Debugvlog > 1 { |
| | d.ctxt.Logf("reached iface method: %v\n", m) |
| | } |
| | d.ifaceMethod[m] = true |
| | continue |
| | case objabi.R_USENAMEDMETHOD: |
| | name := d.decodeGenericIfaceMethod(d.ldr, r.Sym()) |
| | if d.ctxt.Debugvlog > 1 { |
| | d.ctxt.Logf("reached generic iface method: %s\n", name) |
| | } |
| | d.genericIfaceMethod[name] = true |
| | continue |
| | case objabi.R_INITORDER: |
| | |
| | |
| | |
| | continue |
| | } |
| | rs := r.Sym() |
| | if isgotype && usedInIface && d.ldr.IsGoType(rs) && !d.ldr.AttrUsedInIface(rs) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | d.ldr.SetAttrUsedInIface(rs, true) |
| | d.ldr.SetAttrReachable(rs, false) |
| | } |
| | d.mark(rs, symIdx) |
| | } |
| | naux := d.ldr.NAux(symIdx) |
| | for i := 0; i < naux; i++ { |
| | a := d.ldr.Aux(symIdx, i) |
| | if a.Type() == goobj.AuxGotype { |
| | |
| | |
| | continue |
| | } |
| | d.mark(a.Sym(), symIdx) |
| | } |
| | |
| | |
| | if naux != 0 && d.ldr.IsPkgInit(symIdx) { |
| |
|
| | d.pkginits = append(d.pkginits, symIdx) |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | if d.ldr.IsExternal(symIdx) { |
| | d.mark(d.ldr.OuterSym(symIdx), symIdx) |
| | d.mark(d.ldr.SubSym(symIdx), symIdx) |
| | } |
| |
|
| | if len(methods) != 0 { |
| | if !isgotype { |
| | panic("method found on non-type symbol") |
| | } |
| | |
| | |
| | |
| | methodsigs := d.decodetypeMethods(d.ldr, d.ctxt.Arch, symIdx, &relocs) |
| | if len(methods) != len(methodsigs) { |
| | panic(fmt.Sprintf("%q has %d method relocations for %d methods", d.ldr.SymName(symIdx), len(methods), len(methodsigs))) |
| | } |
| | for i, m := range methodsigs { |
| | methods[i].m = m |
| | if d.ctxt.Debugvlog > 1 { |
| | d.ctxt.Logf("markable method: %v of sym %v %s\n", m, symIdx, d.ldr.SymName(symIdx)) |
| | } |
| | } |
| | d.markableMethods = append(d.markableMethods, methods...) |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | func (d *deadcodePass) mapinitcleanup() { |
| | for _, idx := range d.pkginits { |
| | relocs := d.ldr.Relocs(idx) |
| | var su *loader.SymbolBuilder |
| | for i := 0; i < relocs.Count(); i++ { |
| | r := relocs.At(i) |
| | rs := r.Sym() |
| | if r.Weak() && r.Type().IsDirectCall() && !d.ldr.AttrReachable(rs) { |
| | |
| | rsn := d.ldr.SymName(rs) |
| | if !strings.Contains(rsn, "map.init") { |
| | panic(fmt.Sprintf("internal error: expected map.init sym for weak call reloc, got %s -> %s", d.ldr.SymName(idx), rsn)) |
| | } |
| | d.ldr.SetAttrReachable(d.mapinitnoop, true) |
| | if d.ctxt.Debugvlog > 1 { |
| | d.ctxt.Logf("deadcode: %s rewrite %s ref to %s\n", |
| | d.ldr.SymName(idx), rsn, |
| | d.ldr.SymName(d.mapinitnoop)) |
| | } |
| | if su == nil { |
| | su = d.ldr.MakeSymbolUpdater(idx) |
| | } |
| | su.SetRelocSym(i, d.mapinitnoop) |
| | } |
| | } |
| | } |
| | } |
| |
|
| | func (d *deadcodePass) mark(symIdx, parent loader.Sym) { |
| | if symIdx != 0 && !d.ldr.AttrReachable(symIdx) { |
| | d.wq.push(symIdx) |
| | d.ldr.SetAttrReachable(symIdx, true) |
| | if buildcfg.Experiment.FieldTrack && d.ldr.Reachparent[symIdx] == 0 { |
| | d.ldr.Reachparent[symIdx] = parent |
| | } |
| | if *flagDumpDep { |
| | to := d.ldr.SymName(symIdx) |
| | if to != "" { |
| | to = d.dumpDepAddFlags(to, symIdx) |
| | from := "_" |
| | if parent != 0 { |
| | from = d.ldr.SymName(parent) |
| | from = d.dumpDepAddFlags(from, parent) |
| | } |
| | fmt.Printf("%s -> %s\n", from, to) |
| | } |
| | } |
| | } |
| | } |
| |
|
| | func (d *deadcodePass) dumpDepAddFlags(name string, symIdx loader.Sym) string { |
| | var flags strings.Builder |
| | if d.ldr.AttrUsedInIface(symIdx) { |
| | flags.WriteString("<UsedInIface>") |
| | } |
| | if d.ldr.IsReflectMethod(symIdx) { |
| | flags.WriteString("<ReflectMethod>") |
| | } |
| | if flags.Len() > 0 { |
| | return name + " " + flags.String() |
| | } |
| | return name |
| | } |
| |
|
| | func (d *deadcodePass) markMethod(m methodref) { |
| | relocs := d.ldr.Relocs(m.src) |
| | d.mark(relocs.At(m.r).Sym(), m.src) |
| | d.mark(relocs.At(m.r+1).Sym(), m.src) |
| | d.mark(relocs.At(m.r+2).Sym(), m.src) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func deadcode(ctxt *Link) { |
| | ldr := ctxt.loader |
| | d := deadcodePass{ctxt: ctxt, ldr: ldr} |
| | d.init() |
| | d.flood() |
| |
|
| | if ctxt.DynlinkingGo() { |
| | |
| | |
| | d.reflectSeen = true |
| | } |
| |
|
| | for { |
| | |
| | |
| | |
| | |
| | rem := d.markableMethods[:0] |
| | for _, m := range d.markableMethods { |
| | if (d.reflectSeen && (m.isExported() || d.dynlink)) || d.ifaceMethod[m.m] || d.genericIfaceMethod[m.m.name] { |
| | d.markMethod(m) |
| | } else { |
| | rem = append(rem, m) |
| | } |
| | } |
| | d.markableMethods = rem |
| |
|
| | if d.wq.empty() { |
| | |
| | break |
| | } |
| | d.flood() |
| | } |
| | if *flagPruneWeakMap { |
| | d.mapinitcleanup() |
| | } |
| | } |
| |
|
| | |
| | type methodsig struct { |
| | name string |
| | typ loader.Sym |
| | } |
| |
|
| | |
| | |
| | |
| | type methodref struct { |
| | m methodsig |
| | src loader.Sym |
| | r int |
| | } |
| |
|
| | func (m methodref) isExported() bool { |
| | for _, r := range m.m.name { |
| | return unicode.IsUpper(r) |
| | } |
| | panic("methodref has no signature") |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | func (d *deadcodePass) decodeMethodSig(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs, off, size, count int) []methodsig { |
| | if cap(d.methodsigstmp) < count { |
| | d.methodsigstmp = append(d.methodsigstmp[:0], make([]methodsig, count)...) |
| | } |
| | var methods = d.methodsigstmp[:count] |
| | for i := 0; i < count; i++ { |
| | methods[i].name = decodetypeName(ldr, symIdx, relocs, off) |
| | methods[i].typ = decodeRelocSym(ldr, symIdx, relocs, int32(off+4)) |
| | off += size |
| | } |
| | return methods |
| | } |
| |
|
| | |
| | func (d *deadcodePass) decodeIfaceMethod(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, off int64) methodsig { |
| | p := ldr.Data(symIdx) |
| | if p == nil { |
| | panic(fmt.Sprintf("missing symbol %q", ldr.SymName(symIdx))) |
| | } |
| | if decodetypeKind(arch, p) != abi.Interface { |
| | panic(fmt.Sprintf("symbol %q is not an interface", ldr.SymName(symIdx))) |
| | } |
| | relocs := ldr.Relocs(symIdx) |
| | var m methodsig |
| | m.name = decodetypeName(ldr, symIdx, &relocs, int(off)) |
| | m.typ = decodeRelocSym(ldr, symIdx, &relocs, int32(off+4)) |
| | return m |
| | } |
| |
|
| | |
| | func (d *deadcodePass) decodeGenericIfaceMethod(ldr *loader.Loader, symIdx loader.Sym) string { |
| | return ldr.DataString(symIdx) |
| | } |
| |
|
| | func (d *deadcodePass) decodetypeMethods(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs) []methodsig { |
| | p := ldr.Data(symIdx) |
| | if !decodetypeHasUncommon(arch, p) { |
| | panic(fmt.Sprintf("no methods on %q", ldr.SymName(symIdx))) |
| | } |
| | off := commonsize(arch) |
| | switch decodetypeKind(arch, p) { |
| | case abi.Struct: |
| | off += 4 * arch.PtrSize |
| | case abi.Pointer: |
| | off += arch.PtrSize |
| | case abi.Func: |
| | off += arch.PtrSize |
| | case abi.Slice: |
| | off += arch.PtrSize |
| | case abi.Array: |
| | off += 3 * arch.PtrSize |
| | case abi.Chan: |
| | off += 2 * arch.PtrSize |
| | case abi.Map: |
| | off += 7*arch.PtrSize + 4 |
| | if arch.PtrSize == 8 { |
| | off += 4 |
| | } |
| | case abi.Interface: |
| | off += 3 * arch.PtrSize |
| | default: |
| | |
| | } |
| |
|
| | mcount := int(decodeInuxi(arch, p[off+4:], 2)) |
| | moff := int(decodeInuxi(arch, p[off+4+2+2:], 4)) |
| | off += moff |
| | const sizeofMethod = 4 * 4 |
| | return d.decodeMethodSig(ldr, arch, symIdx, relocs, off, sizeofMethod, mcount) |
| | } |
| |
|