1
0
mirror of https://github.com/golang/go synced 2024-11-23 13:00:07 -07:00

cmd/link: consider interface conversions only in reachable code

The linker prunes methods that are not directly reachable if the
receiver type is never converted to interface. Currently, this
"never" is too strong: it is invalidated even if the interface
conversion is in an unreachable function. This CL improves it by
only considering interface conversions in reachable code. To do
that, we introduce a marker relocation R_USEIFACE, which marks
the target symbol as UsedInIface if the source symbol is reached.

binary size    before      after
cmd/compile   18897528   18887400
cmd/go        13607372   13470652

Change-Id: I66c6b69eeff9ae02d84d2e6f2bc7f1b29dd53910
Reviewed-on: https://go-review.googlesource.com/c/go/+/256797
Trust: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Jeremy Faller <jeremy@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Cherry Zhang 2020-09-20 23:29:20 -04:00
parent ad0ab812f8
commit af18bce87c
13 changed files with 129 additions and 15 deletions

View File

@ -231,6 +231,11 @@ func compile(fn *Node) {
return
}
// Set up the function's LSym early to avoid data races with the assemblers.
// Do this before walk, as walk needs the LSym to set attributes/relocations
// (e.g. in markTypeUsedInInterface).
fn.Func.initLSym(true)
walk(fn)
if nerrors != 0 {
return
@ -250,9 +255,6 @@ func compile(fn *Node) {
return
}
// Set up the function's LSym early to avoid data races with the assemblers.
fn.Func.initLSym(true)
// Make sure type syms are declared for all types that might
// be types of stack objects. We need to do this here
// because symbols must be allocated before the parallel

View File

@ -278,7 +278,7 @@ func (s *InitSchedule) staticassign(l *Node, r *Node) bool {
return Isconst(val, CTNIL)
}
markTypeUsedInInterface(val.Type)
markTypeUsedInInterface(val.Type, l.Sym.Linksym())
var itab *Node
if l.Type.IsEmptyInterface() {

View File

@ -805,8 +805,8 @@ opswitch:
fromType := n.Left.Type
toType := n.Type
if !fromType.IsInterface() {
markTypeUsedInInterface(fromType)
if !fromType.IsInterface() && !Curfn.Func.Nname.isBlank() { // skip unnamed functions (func _())
markTypeUsedInInterface(fromType, Curfn.Func.lsym)
}
// typeword generates the type word of the interface value.
@ -1621,8 +1621,13 @@ opswitch:
// markTypeUsedInInterface marks that type t is converted to an interface.
// This information is used in the linker in dead method elimination.
func markTypeUsedInInterface(t *types.Type) {
typenamesym(t).Linksym().Set(obj.AttrUsedInIface, true)
func markTypeUsedInInterface(t *types.Type, from *obj.LSym) {
tsym := typenamesym(t).Linksym()
// Emit a marker relocation. The linker will know the type is converted
// to an interface if "from" is reachable.
r := obj.Addrel(from)
r.Sym = tsym
r.Type = objabi.R_USEIFACE
}
// rtconvfn returns the parameter and result types that will be used by a
@ -3687,6 +3692,8 @@ func usemethod(n *Node) {
// Also need to check for reflect package itself (see Issue #38515).
if s := res0.Type.Sym; s != nil && s.Name == "Method" && isReflectPkg(s.Pkg) {
Curfn.Func.SetReflectMethod(true)
// The LSym is initialized at this point. We need to set the attribute on the LSym.
Curfn.Func.lsym.Set(obj.AttrReflectMethod, true)
}
}

View File

@ -461,6 +461,7 @@ func spanz(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
buffer := make([]byte, 0)
changed := true
loop := 0
nrelocs0 := len(c.cursym.R)
for changed {
if loop > 100 {
c.ctxt.Diag("stuck in spanz loop")
@ -468,7 +469,10 @@ func spanz(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
}
changed = false
buffer = buffer[:0]
c.cursym.R = make([]obj.Reloc, 0)
for i := range c.cursym.R[nrelocs0:] {
c.cursym.R[nrelocs0+i] = obj.Reloc{}
}
c.cursym.R = c.cursym.R[:nrelocs0] // preserve marker relocations generated by the compiler
for p := c.cursym.Func.Text; p != nil; p = p.Link {
pc := int64(len(buffer))
if pc != p.Pc {

View File

@ -1007,6 +1007,7 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
panic("bad name for Call")
}
r := obj.Addrel(s)
r.Siz = 1 // actually variable sized
r.Off = int32(w.Len())
r.Type = objabi.R_CALL
if p.Mark&WasmImport != 0 {
@ -1033,6 +1034,7 @@ func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
case AI32Const, AI64Const:
if p.From.Name == obj.NAME_EXTERN {
r := obj.Addrel(s)
r.Siz = 1 // actually variable sized
r.Off = int32(w.Len())
r.Type = objabi.R_ADDR
r.Sym = p.From.Sym

View File

@ -2100,14 +2100,15 @@ func span6(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
var c int32
errors := ctxt.Errors
var nops []nopPad // Padding for a particular assembly (reuse slice storage if multiple assemblies)
nrelocs0 := len(s.R)
for {
// This loop continues while there are reasons to re-assemble
// whole block, like the presence of long forward jumps.
reAssemble := false
for i := range s.R {
s.R[i] = obj.Reloc{}
for i := range s.R[nrelocs0:] {
s.R[nrelocs0+i] = obj.Reloc{}
}
s.R = s.R[:0]
s.R = s.R[:nrelocs0] // preserve marker relocations generated by the compiler
s.P = s.P[:0]
c = 0
var pPrev *obj.Prog

View File

@ -89,6 +89,11 @@ const (
// should be linked into the final binary, even if there are no other
// direct references. (This is used for types reachable by reflection.)
R_USETYPE
// R_USEIFACE marks a type is converted to an interface in the function this
// relocation is applied to. The target is a type descriptor.
// This is a marker relocation (0-sized), for the linker's reachabililty
// analysis.
R_USEIFACE
// R_METHODOFF resolves to a 32-bit offset from the beginning of the section
// holding the data being relocated to the referenced symbol.
// It is a variant of R_ADDROFF used when linking from the uncommonType of a

View File

@ -4,9 +4,71 @@ package objabi
import "strconv"
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_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"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[R_ADDR-1]
_ = x[R_ADDRPOWER-2]
_ = x[R_ADDRARM64-3]
_ = x[R_ADDRMIPS-4]
_ = x[R_ADDROFF-5]
_ = x[R_WEAKADDROFF-6]
_ = x[R_SIZE-7]
_ = x[R_CALL-8]
_ = x[R_CALLARM-9]
_ = x[R_CALLARM64-10]
_ = x[R_CALLIND-11]
_ = x[R_CALLPOWER-12]
_ = x[R_CALLMIPS-13]
_ = x[R_CALLRISCV-14]
_ = x[R_CONST-15]
_ = x[R_PCREL-16]
_ = x[R_TLS_LE-17]
_ = x[R_TLS_IE-18]
_ = x[R_GOTOFF-19]
_ = x[R_PLT0-20]
_ = x[R_PLT1-21]
_ = x[R_PLT2-22]
_ = x[R_USEFIELD-23]
_ = x[R_USETYPE-24]
_ = x[R_USEIFACE-25]
_ = x[R_METHODOFF-26]
_ = x[R_POWER_TOC-27]
_ = x[R_GOTPCREL-28]
_ = x[R_JMPMIPS-29]
_ = x[R_DWARFSECREF-30]
_ = x[R_DWARFFILEREF-31]
_ = x[R_ARM64_TLS_LE-32]
_ = x[R_ARM64_TLS_IE-33]
_ = x[R_ARM64_GOTPCREL-34]
_ = x[R_ARM64_GOT-35]
_ = x[R_ARM64_PCREL-36]
_ = x[R_ARM64_LDST8-37]
_ = x[R_ARM64_LDST32-38]
_ = x[R_ARM64_LDST64-39]
_ = x[R_ARM64_LDST128-40]
_ = x[R_POWER_TLS_LE-41]
_ = x[R_POWER_TLS_IE-42]
_ = x[R_POWER_TLS-43]
_ = x[R_ADDRPOWER_DS-44]
_ = x[R_ADDRPOWER_GOT-45]
_ = x[R_ADDRPOWER_PCREL-46]
_ = x[R_ADDRPOWER_TOCREL-47]
_ = x[R_ADDRPOWER_TOCREL_DS-48]
_ = x[R_RISCV_PCREL_ITYPE-49]
_ = x[R_RISCV_PCREL_STYPE-50]
_ = x[R_PCRELDBL-51]
_ = x[R_ADDRMIPSU-52]
_ = x[R_ADDRMIPSTLS-53]
_ = x[R_ADDRCUOFF-54]
_ = x[R_WASMIMPORT-55]
_ = x[R_XCOFFREF-56]
}
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, 219, 230, 240, 249, 262, 276, 290, 304, 320, 331, 344, 357, 371, 385, 400, 414, 428, 439, 453, 468, 485, 503, 524, 543, 562, 572, 583, 596, 607, 619, 629}
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"
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}
func (i RelocType) String() string {
i -= 1

View File

@ -698,6 +698,9 @@ func windynrelocsym(ctxt *Link, rel *loader.SymbolBuilder, s loader.Sym) {
relocs := ctxt.loader.Relocs(s)
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
if r.IsMarker() {
continue // skip marker relocations
}
targ := r.Sym()
if targ == 0 {
continue
@ -775,6 +778,9 @@ func dynrelocsym(ctxt *Link, s loader.Sym) {
relocs := ldr.Relocs(s)
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
if r.IsMarker() {
continue // skip marker relocations
}
if ctxt.BuildMode == BuildModePIE && ctxt.LinkMode == LinkInternal {
// It's expected that some relocations will be done
// later by relocsym (R_TLS_LE, R_ADDROFF), so

View File

@ -153,6 +153,20 @@ func (d *deadcodePass) flood() {
// do nothing for now as we still load all type symbols.
continue
}
if t == objabi.R_USEIFACE {
// 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
// comment below for why we need to unset the Reachable bit and re-mark it.
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)
}
}
continue
}
rs := r.Sym()
if isgotype && usedInIface && d.ldr.IsGoType(rs) && !d.ldr.AttrUsedInIface(rs) {
// If a type is converted to an interface, it is possible to obtain an

View File

@ -18,6 +18,13 @@ var p *T
var e interface{}
func main() {
p = new(T) // used T, but never converted to interface
p = new(T) // used T, but never converted to interface in any reachable code
e.(I).M() // used I and I.M
}
func Unused() { // convert T to interface, but this function is not reachable
var i I = T(0)
i.M()
}
var Unused2 interface{} = T(1) // convert T to interface, in an unreachable global initializer

View File

@ -63,6 +63,7 @@ type Reloc struct {
func (rel Reloc) Type() objabi.RelocType { return objabi.RelocType(rel.Reloc.Type()) + rel.typ }
func (rel Reloc) Sym() Sym { return rel.l.resolve(rel.r, rel.Reloc.Sym()) }
func (rel Reloc) SetSym(s Sym) { rel.Reloc.SetSym(goobj.SymRef{PkgIdx: 0, SymIdx: uint32(s)}) }
func (rel Reloc) IsMarker() bool { return rel.Siz() == 0 }
func (rel Reloc) SetType(t objabi.RelocType) {
if t != objabi.RelocType(uint8(t)) {

View File

@ -167,6 +167,9 @@ func asmb2(ctxt *ld.Link, ldr *loader.Loader) {
off := int32(0)
for ri := 0; ri < relocs.Count(); ri++ {
r := relocs.At(ri)
if r.Siz() == 0 {
continue // skip marker relocations
}
wfn.Write(P[off:r.Off()])
off = r.Off()
rs := ldr.ResolveABIAlias(r.Sym())