| |
| |
| |
|
|
| 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) |
| } |
|
|