mirror of
https://github.com/golang/go
synced 2024-11-17 16:44:44 -07:00
cmd/link: retain only used interface methods
Currently, in the linker's deadcode pass, when an interface type is live, the linker thinks all its methods are live, and uses them to match methods on concrete types. The interface method may never be used, though. This CL changes it to only keep used interface methods, for matching concrete type methods. To do that, when an interface method is used, the compiler generates a mark relocation. The linker uses the marker relocations to mark used interface methods, and only the used ones. binary size before after cmd/compile 18887400 18812200 cmd/go 13470652 13470492 Change-Id: I3cfd9df4a53783330ba87735853f2a0ec3c42802 Reviewed-on: https://go-review.googlesource.com/c/go/+/256798 Trust: Cherry Zhang <cherryyz@google.com> Reviewed-by: Than McIntosh <thanm@google.com> Reviewed-by: Jeremy Faller <jeremy@golang.org>
This commit is contained in:
parent
39a426d356
commit
39dde09126
@ -63,6 +63,7 @@ const (
|
|||||||
|
|
||||||
func structfieldSize() int { return 3 * Widthptr } // Sizeof(runtime.structfield{})
|
func structfieldSize() int { return 3 * Widthptr } // Sizeof(runtime.structfield{})
|
||||||
func imethodSize() int { return 4 + 4 } // Sizeof(runtime.imethod{})
|
func imethodSize() int { return 4 + 4 } // Sizeof(runtime.imethod{})
|
||||||
|
func commonSize() int { return 4*Widthptr + 8 + 8 } // Sizeof(runtime._type{})
|
||||||
|
|
||||||
func uncommonSize(t *types.Type) int { // Sizeof(runtime.uncommontype{})
|
func uncommonSize(t *types.Type) int { // Sizeof(runtime.uncommontype{})
|
||||||
if t.Sym == nil && len(methods(t)) == 0 {
|
if t.Sym == nil && len(methods(t)) == 0 {
|
||||||
@ -1422,6 +1423,20 @@ func dtypesym(t *types.Type) *obj.LSym {
|
|||||||
return lsym
|
return lsym
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ifaceMethodOffset returns the offset of the i-th method in the interface
|
||||||
|
// type descriptor, ityp.
|
||||||
|
func ifaceMethodOffset(ityp *types.Type, i int64) int64 {
|
||||||
|
// interface type descriptor layout is struct {
|
||||||
|
// _type // commonSize
|
||||||
|
// pkgpath // 1 word
|
||||||
|
// []imethod // 3 words (pointing to [...]imethod below)
|
||||||
|
// uncommontype // uncommonSize
|
||||||
|
// [...]imethod
|
||||||
|
// }
|
||||||
|
// The size of imethod is 8.
|
||||||
|
return int64(commonSize()+4*Widthptr+uncommonSize(ityp)) + i*8
|
||||||
|
}
|
||||||
|
|
||||||
// for each itabEntry, gather the methods on
|
// for each itabEntry, gather the methods on
|
||||||
// the concrete type that implement the interface
|
// the concrete type that implement the interface
|
||||||
func peekitabs() {
|
func peekitabs() {
|
||||||
|
@ -565,6 +565,7 @@ opswitch:
|
|||||||
case OCALLINTER, OCALLFUNC, OCALLMETH:
|
case OCALLINTER, OCALLFUNC, OCALLMETH:
|
||||||
if n.Op == OCALLINTER {
|
if n.Op == OCALLINTER {
|
||||||
usemethod(n)
|
usemethod(n)
|
||||||
|
markUsedIfaceMethod(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.Op == OCALLFUNC && n.Left.Op == OCLOSURE {
|
if n.Op == OCALLFUNC && n.Left.Op == OCLOSURE {
|
||||||
@ -1630,6 +1631,20 @@ func markTypeUsedInInterface(t *types.Type, from *obj.LSym) {
|
|||||||
r.Type = objabi.R_USEIFACE
|
r.Type = objabi.R_USEIFACE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// markUsedIfaceMethod marks that an interface method is used in the current
|
||||||
|
// function. n is OCALLINTER node.
|
||||||
|
func markUsedIfaceMethod(n *Node) {
|
||||||
|
ityp := n.Left.Left.Type
|
||||||
|
tsym := typenamesym(ityp).Linksym()
|
||||||
|
r := obj.Addrel(Curfn.Func.lsym)
|
||||||
|
r.Sym = tsym
|
||||||
|
// n.Left.Xoffset is the method index * Widthptr (the offset of code pointer
|
||||||
|
// in itab).
|
||||||
|
midx := n.Left.Xoffset / int64(Widthptr)
|
||||||
|
r.Add = ifaceMethodOffset(ityp, midx)
|
||||||
|
r.Type = objabi.R_USEIFACEMETHOD
|
||||||
|
}
|
||||||
|
|
||||||
// rtconvfn returns the parameter and result types that will be used by a
|
// rtconvfn returns the parameter and result types that will be used by a
|
||||||
// runtime function to convert from type src to type dst. The runtime function
|
// runtime function to convert from type src to type dst. The runtime function
|
||||||
// name can be derived from the names of the returned types.
|
// name can be derived from the names of the returned types.
|
||||||
|
@ -94,6 +94,12 @@ const (
|
|||||||
// This is a marker relocation (0-sized), for the linker's reachabililty
|
// This is a marker relocation (0-sized), for the linker's reachabililty
|
||||||
// analysis.
|
// analysis.
|
||||||
R_USEIFACE
|
R_USEIFACE
|
||||||
|
// R_USEIFACEMETHOD marks an interface method that is used in the function
|
||||||
|
// this relocation is applied to. The target is an interface type descriptor.
|
||||||
|
// The addend is the offset of the method in the type descriptor.
|
||||||
|
// This is a marker relocation (0-sized), for the linker's reachabililty
|
||||||
|
// analysis.
|
||||||
|
R_USEIFACEMETHOD
|
||||||
// R_METHODOFF resolves to a 32-bit offset from the beginning of the section
|
// R_METHODOFF resolves to a 32-bit offset from the beginning of the section
|
||||||
// holding the data being relocated to the referenced symbol.
|
// holding the data being relocated to the referenced symbol.
|
||||||
// It is a variant of R_ADDROFF used when linking from the uncommonType of a
|
// It is a variant of R_ADDROFF used when linking from the uncommonType of a
|
||||||
|
@ -33,42 +33,43 @@ func _() {
|
|||||||
_ = x[R_USEFIELD-23]
|
_ = x[R_USEFIELD-23]
|
||||||
_ = x[R_USETYPE-24]
|
_ = x[R_USETYPE-24]
|
||||||
_ = x[R_USEIFACE-25]
|
_ = x[R_USEIFACE-25]
|
||||||
_ = x[R_METHODOFF-26]
|
_ = x[R_USEIFACEMETHOD-26]
|
||||||
_ = x[R_POWER_TOC-27]
|
_ = x[R_METHODOFF-27]
|
||||||
_ = x[R_GOTPCREL-28]
|
_ = x[R_POWER_TOC-28]
|
||||||
_ = x[R_JMPMIPS-29]
|
_ = x[R_GOTPCREL-29]
|
||||||
_ = x[R_DWARFSECREF-30]
|
_ = x[R_JMPMIPS-30]
|
||||||
_ = x[R_DWARFFILEREF-31]
|
_ = x[R_DWARFSECREF-31]
|
||||||
_ = x[R_ARM64_TLS_LE-32]
|
_ = x[R_DWARFFILEREF-32]
|
||||||
_ = x[R_ARM64_TLS_IE-33]
|
_ = x[R_ARM64_TLS_LE-33]
|
||||||
_ = x[R_ARM64_GOTPCREL-34]
|
_ = x[R_ARM64_TLS_IE-34]
|
||||||
_ = x[R_ARM64_GOT-35]
|
_ = x[R_ARM64_GOTPCREL-35]
|
||||||
_ = x[R_ARM64_PCREL-36]
|
_ = x[R_ARM64_GOT-36]
|
||||||
_ = x[R_ARM64_LDST8-37]
|
_ = x[R_ARM64_PCREL-37]
|
||||||
_ = x[R_ARM64_LDST32-38]
|
_ = x[R_ARM64_LDST8-38]
|
||||||
_ = x[R_ARM64_LDST64-39]
|
_ = x[R_ARM64_LDST32-39]
|
||||||
_ = x[R_ARM64_LDST128-40]
|
_ = x[R_ARM64_LDST64-40]
|
||||||
_ = x[R_POWER_TLS_LE-41]
|
_ = x[R_ARM64_LDST128-41]
|
||||||
_ = x[R_POWER_TLS_IE-42]
|
_ = x[R_POWER_TLS_LE-42]
|
||||||
_ = x[R_POWER_TLS-43]
|
_ = x[R_POWER_TLS_IE-43]
|
||||||
_ = x[R_ADDRPOWER_DS-44]
|
_ = x[R_POWER_TLS-44]
|
||||||
_ = x[R_ADDRPOWER_GOT-45]
|
_ = x[R_ADDRPOWER_DS-45]
|
||||||
_ = x[R_ADDRPOWER_PCREL-46]
|
_ = x[R_ADDRPOWER_GOT-46]
|
||||||
_ = x[R_ADDRPOWER_TOCREL-47]
|
_ = x[R_ADDRPOWER_PCREL-47]
|
||||||
_ = x[R_ADDRPOWER_TOCREL_DS-48]
|
_ = x[R_ADDRPOWER_TOCREL-48]
|
||||||
_ = x[R_RISCV_PCREL_ITYPE-49]
|
_ = x[R_ADDRPOWER_TOCREL_DS-49]
|
||||||
_ = x[R_RISCV_PCREL_STYPE-50]
|
_ = x[R_RISCV_PCREL_ITYPE-50]
|
||||||
_ = x[R_PCRELDBL-51]
|
_ = x[R_RISCV_PCREL_STYPE-51]
|
||||||
_ = x[R_ADDRMIPSU-52]
|
_ = x[R_PCRELDBL-52]
|
||||||
_ = x[R_ADDRMIPSTLS-53]
|
_ = x[R_ADDRMIPSU-53]
|
||||||
_ = x[R_ADDRCUOFF-54]
|
_ = x[R_ADDRMIPSTLS-54]
|
||||||
_ = x[R_WASMIMPORT-55]
|
_ = x[R_ADDRCUOFF-55]
|
||||||
_ = x[R_XCOFFREF-56]
|
_ = x[R_WASMIMPORT-56]
|
||||||
|
_ = x[R_XCOFFREF-57]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _RelocType_name = "R_ADDRR_ADDRPOWERR_ADDRARM64R_ADDRMIPSR_ADDROFFR_WEAKADDROFFR_SIZER_CALLR_CALLARMR_CALLARM64R_CALLINDR_CALLPOWERR_CALLMIPSR_CALLRISCVR_CONSTR_PCRELR_TLS_LER_TLS_IER_GOTOFFR_PLT0R_PLT1R_PLT2R_USEFIELDR_USETYPER_USEIFACER_METHODOFFR_POWER_TOCR_GOTPCRELR_JMPMIPSR_DWARFSECREFR_DWARFFILEREFR_ARM64_TLS_LER_ARM64_TLS_IER_ARM64_GOTPCRELR_ARM64_GOTR_ARM64_PCRELR_ARM64_LDST8R_ARM64_LDST32R_ARM64_LDST64R_ARM64_LDST128R_POWER_TLS_LER_POWER_TLS_IER_POWER_TLSR_ADDRPOWER_DSR_ADDRPOWER_GOTR_ADDRPOWER_PCRELR_ADDRPOWER_TOCRELR_ADDRPOWER_TOCREL_DSR_RISCV_PCREL_ITYPER_RISCV_PCREL_STYPER_PCRELDBLR_ADDRMIPSUR_ADDRMIPSTLSR_ADDRCUOFFR_WASMIMPORTR_XCOFFREF"
|
const _RelocType_name = "R_ADDRR_ADDRPOWERR_ADDRARM64R_ADDRMIPSR_ADDROFFR_WEAKADDROFFR_SIZER_CALLR_CALLARMR_CALLARM64R_CALLINDR_CALLPOWERR_CALLMIPSR_CALLRISCVR_CONSTR_PCRELR_TLS_LER_TLS_IER_GOTOFFR_PLT0R_PLT1R_PLT2R_USEFIELDR_USETYPER_USEIFACER_USEIFACEMETHODR_METHODOFFR_POWER_TOCR_GOTPCRELR_JMPMIPSR_DWARFSECREFR_DWARFFILEREFR_ARM64_TLS_LER_ARM64_TLS_IER_ARM64_GOTPCRELR_ARM64_GOTR_ARM64_PCRELR_ARM64_LDST8R_ARM64_LDST32R_ARM64_LDST64R_ARM64_LDST128R_POWER_TLS_LER_POWER_TLS_IER_POWER_TLSR_ADDRPOWER_DSR_ADDRPOWER_GOTR_ADDRPOWER_PCRELR_ADDRPOWER_TOCRELR_ADDRPOWER_TOCREL_DSR_RISCV_PCREL_ITYPER_RISCV_PCREL_STYPER_PCRELDBLR_ADDRMIPSUR_ADDRMIPSTLSR_ADDRCUOFFR_WASMIMPORTR_XCOFFREF"
|
||||||
|
|
||||||
var _RelocType_index = [...]uint16{0, 6, 17, 28, 38, 47, 60, 66, 72, 81, 92, 101, 112, 122, 133, 140, 147, 155, 163, 171, 177, 183, 189, 199, 208, 218, 229, 240, 250, 259, 272, 286, 300, 314, 330, 341, 354, 367, 381, 395, 410, 424, 438, 449, 463, 478, 495, 513, 534, 553, 572, 582, 593, 606, 617, 629, 639}
|
var _RelocType_index = [...]uint16{0, 6, 17, 28, 38, 47, 60, 66, 72, 81, 92, 101, 112, 122, 133, 140, 147, 155, 163, 171, 177, 183, 189, 199, 208, 218, 234, 245, 256, 266, 275, 288, 302, 316, 330, 346, 357, 370, 383, 397, 411, 426, 440, 454, 465, 479, 494, 511, 529, 550, 569, 588, 598, 609, 622, 633, 645, 655}
|
||||||
|
|
||||||
func (i RelocType) String() string {
|
func (i RelocType) String() string {
|
||||||
i -= 1
|
i -= 1
|
||||||
|
@ -106,25 +106,16 @@ func (d *deadcodePass) flood() {
|
|||||||
|
|
||||||
if isgotype {
|
if isgotype {
|
||||||
usedInIface = d.ldr.AttrUsedInIface(symIdx)
|
usedInIface = d.ldr.AttrUsedInIface(symIdx)
|
||||||
p := d.ldr.Data(symIdx)
|
|
||||||
if len(p) != 0 && decodetypeKind(d.ctxt.Arch, p)&kindMask == kindInterface {
|
|
||||||
for _, sig := range d.decodeIfaceMethods(d.ldr, d.ctxt.Arch, symIdx, &relocs) {
|
|
||||||
if d.ctxt.Debugvlog > 1 {
|
|
||||||
d.ctxt.Logf("reached iface method: %v\n", sig)
|
|
||||||
}
|
|
||||||
d.ifaceMethod[sig] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
methods = methods[:0]
|
methods = methods[:0]
|
||||||
for i := 0; i < relocs.Count(); i++ {
|
for i := 0; i < relocs.Count(); i++ {
|
||||||
r := relocs.At(i)
|
r := relocs.At(i)
|
||||||
t := r.Type()
|
t := r.Type()
|
||||||
if t == objabi.R_WEAKADDROFF {
|
switch t {
|
||||||
|
case objabi.R_WEAKADDROFF:
|
||||||
continue
|
continue
|
||||||
}
|
case objabi.R_METHODOFF:
|
||||||
if t == objabi.R_METHODOFF {
|
|
||||||
if i+2 >= relocs.Count() {
|
if i+2 >= relocs.Count() {
|
||||||
panic("expect three consecutive R_METHODOFF relocs")
|
panic("expect three consecutive R_METHODOFF relocs")
|
||||||
}
|
}
|
||||||
@ -146,14 +137,12 @@ func (d *deadcodePass) flood() {
|
|||||||
}
|
}
|
||||||
i += 2
|
i += 2
|
||||||
continue
|
continue
|
||||||
}
|
case objabi.R_USETYPE:
|
||||||
if t == objabi.R_USETYPE {
|
|
||||||
// type symbol used for DWARF. we need to load the symbol but it may not
|
// type symbol used for DWARF. we need to load the symbol but it may not
|
||||||
// be otherwise reachable in the program.
|
// be otherwise reachable in the program.
|
||||||
// do nothing for now as we still load all type symbols.
|
// do nothing for now as we still load all type symbols.
|
||||||
continue
|
continue
|
||||||
}
|
case objabi.R_USEIFACE:
|
||||||
if t == objabi.R_USEIFACE {
|
|
||||||
// R_USEIFACE is a marker relocation that tells the linker the type is
|
// R_USEIFACE is a marker relocation that tells the linker the type is
|
||||||
// converted to an interface, i.e. should have UsedInIface set. See the
|
// converted to an interface, i.e. should have UsedInIface set. See the
|
||||||
// comment below for why we need to unset the Reachable bit and re-mark it.
|
// comment below for why we need to unset the Reachable bit and re-mark it.
|
||||||
@ -166,6 +155,18 @@ func (d *deadcodePass) flood() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
case objabi.R_USEIFACEMETHOD:
|
||||||
|
// R_USEIFACEMETHOD is a marker relocation that marks an interface
|
||||||
|
// method as used.
|
||||||
|
rs := r.Sym()
|
||||||
|
if d.ldr.SymType(rs) != sym.SDYNIMPORT { // don't decode DYNIMPORT symbol (we'll mark all exported methods anyway)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
rs := r.Sym()
|
rs := r.Sym()
|
||||||
if isgotype && usedInIface && d.ldr.IsGoType(rs) && !d.ldr.AttrUsedInIface(rs) {
|
if isgotype && usedInIface && d.ldr.IsGoType(rs) && !d.ldr.AttrUsedInIface(rs) {
|
||||||
@ -378,23 +379,17 @@ func (d *deadcodePass) decodeMethodSig(ldr *loader.Loader, arch *sys.Arch, symId
|
|||||||
return methods
|
return methods
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deadcodePass) decodeIfaceMethods(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs) []methodsig {
|
// Decode the method of interface type symbol symIdx at offset off.
|
||||||
|
func (d *deadcodePass) decodeIfaceMethod(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, off int64) methodsig {
|
||||||
p := ldr.Data(symIdx)
|
p := ldr.Data(symIdx)
|
||||||
if decodetypeKind(arch, p)&kindMask != kindInterface {
|
if decodetypeKind(arch, p)&kindMask != kindInterface {
|
||||||
panic(fmt.Sprintf("symbol %q is not an interface", ldr.SymName(symIdx)))
|
panic(fmt.Sprintf("symbol %q is not an interface", ldr.SymName(symIdx)))
|
||||||
}
|
}
|
||||||
rel := decodeReloc(ldr, symIdx, relocs, int32(commonsize(arch)+arch.PtrSize))
|
relocs := ldr.Relocs(symIdx)
|
||||||
s := rel.Sym()
|
var m methodsig
|
||||||
if s == 0 {
|
m.name = decodetypeName(ldr, symIdx, &relocs, int(off))
|
||||||
return nil
|
m.typ = decodeRelocSym(ldr, symIdx, &relocs, int32(off+4))
|
||||||
}
|
return m
|
||||||
if s != symIdx {
|
|
||||||
panic(fmt.Sprintf("imethod slice pointer in %q leads to a different symbol", ldr.SymName(symIdx)))
|
|
||||||
}
|
|
||||||
off := int(rel.Add()) // array of reflect.imethod values
|
|
||||||
numMethods := int(decodetypeIfaceMethodCount(arch, p))
|
|
||||||
sizeofIMethod := 4 + 4
|
|
||||||
return d.decodeMethodSig(ldr, arch, symIdx, relocs, off, sizeofIMethod, numMethods)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *deadcodePass) decodetypeMethods(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs) []methodsig {
|
func (d *deadcodePass) decodetypeMethods(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs) []methodsig {
|
||||||
|
@ -33,6 +33,7 @@ func TestDeadcode(t *testing.T) {
|
|||||||
{"ifacemethod", "", "main.T.M"},
|
{"ifacemethod", "", "main.T.M"},
|
||||||
{"ifacemethod2", "main.T.M", ""},
|
{"ifacemethod2", "main.T.M", ""},
|
||||||
{"ifacemethod3", "main.S.M", ""},
|
{"ifacemethod3", "main.S.M", ""},
|
||||||
|
{"ifacemethod4", "", "main.T.M"},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
test := test
|
test := test
|
||||||
|
23
src/cmd/link/internal/ld/testdata/deadcode/ifacemethod4.go
vendored
Normal file
23
src/cmd/link/internal/ld/testdata/deadcode/ifacemethod4.go
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Test that a live type's method is not live even if
|
||||||
|
// it matches an interface method, as long as the interface
|
||||||
|
// method is not used.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
type T int
|
||||||
|
|
||||||
|
func (T) M() {}
|
||||||
|
|
||||||
|
type I interface{ M() }
|
||||||
|
|
||||||
|
var p *T
|
||||||
|
var pp *I
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
p = new(T) // use type T
|
||||||
|
pp = new(I) // use type I
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user