Skip to content

Commit 39dde09

Browse files
committed
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 <[email protected]> Reviewed-by: Than McIntosh <[email protected]> Reviewed-by: Jeremy Faller <[email protected]>
1 parent 39a426d commit 39dde09

File tree

7 files changed

+120
-64
lines changed

7 files changed

+120
-64
lines changed

src/cmd/compile/internal/gc/reflect.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ const (
6161
MAXELEMSIZE = 128
6262
)
6363

64-
func structfieldSize() int { return 3 * Widthptr } // Sizeof(runtime.structfield{})
65-
func imethodSize() int { return 4 + 4 } // Sizeof(runtime.imethod{})
64+
func structfieldSize() int { return 3 * Widthptr } // Sizeof(runtime.structfield{})
65+
func imethodSize() int { return 4 + 4 } // Sizeof(runtime.imethod{})
66+
func commonSize() int { return 4*Widthptr + 8 + 8 } // Sizeof(runtime._type{})
6667

6768
func uncommonSize(t *types.Type) int { // Sizeof(runtime.uncommontype{})
6869
if t.Sym == nil && len(methods(t)) == 0 {
@@ -1422,6 +1423,20 @@ func dtypesym(t *types.Type) *obj.LSym {
14221423
return lsym
14231424
}
14241425

1426+
// ifaceMethodOffset returns the offset of the i-th method in the interface
1427+
// type descriptor, ityp.
1428+
func ifaceMethodOffset(ityp *types.Type, i int64) int64 {
1429+
// interface type descriptor layout is struct {
1430+
// _type // commonSize
1431+
// pkgpath // 1 word
1432+
// []imethod // 3 words (pointing to [...]imethod below)
1433+
// uncommontype // uncommonSize
1434+
// [...]imethod
1435+
// }
1436+
// The size of imethod is 8.
1437+
return int64(commonSize()+4*Widthptr+uncommonSize(ityp)) + i*8
1438+
}
1439+
14251440
// for each itabEntry, gather the methods on
14261441
// the concrete type that implement the interface
14271442
func peekitabs() {

src/cmd/compile/internal/gc/walk.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ opswitch:
565565
case OCALLINTER, OCALLFUNC, OCALLMETH:
566566
if n.Op == OCALLINTER {
567567
usemethod(n)
568+
markUsedIfaceMethod(n)
568569
}
569570

570571
if n.Op == OCALLFUNC && n.Left.Op == OCLOSURE {
@@ -1630,6 +1631,20 @@ func markTypeUsedInInterface(t *types.Type, from *obj.LSym) {
16301631
r.Type = objabi.R_USEIFACE
16311632
}
16321633

1634+
// markUsedIfaceMethod marks that an interface method is used in the current
1635+
// function. n is OCALLINTER node.
1636+
func markUsedIfaceMethod(n *Node) {
1637+
ityp := n.Left.Left.Type
1638+
tsym := typenamesym(ityp).Linksym()
1639+
r := obj.Addrel(Curfn.Func.lsym)
1640+
r.Sym = tsym
1641+
// n.Left.Xoffset is the method index * Widthptr (the offset of code pointer
1642+
// in itab).
1643+
midx := n.Left.Xoffset / int64(Widthptr)
1644+
r.Add = ifaceMethodOffset(ityp, midx)
1645+
r.Type = objabi.R_USEIFACEMETHOD
1646+
}
1647+
16331648
// rtconvfn returns the parameter and result types that will be used by a
16341649
// runtime function to convert from type src to type dst. The runtime function
16351650
// name can be derived from the names of the returned types.

src/cmd/internal/objabi/reloctype.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ const (
9494
// This is a marker relocation (0-sized), for the linker's reachabililty
9595
// analysis.
9696
R_USEIFACE
97+
// R_USEIFACEMETHOD marks an interface method that is used in the function
98+
// this relocation is applied to. The target is an interface type descriptor.
99+
// The addend is the offset of the method in the type descriptor.
100+
// This is a marker relocation (0-sized), for the linker's reachabililty
101+
// analysis.
102+
R_USEIFACEMETHOD
97103
// R_METHODOFF resolves to a 32-bit offset from the beginning of the section
98104
// holding the data being relocated to the referenced symbol.
99105
// It is a variant of R_ADDROFF used when linking from the uncommonType of a

src/cmd/internal/objabi/reloctype_string.go

Lines changed: 34 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/link/internal/ld/deadcode.go

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -106,25 +106,16 @@ func (d *deadcodePass) flood() {
106106

107107
if isgotype {
108108
usedInIface = d.ldr.AttrUsedInIface(symIdx)
109-
p := d.ldr.Data(symIdx)
110-
if len(p) != 0 && decodetypeKind(d.ctxt.Arch, p)&kindMask == kindInterface {
111-
for _, sig := range d.decodeIfaceMethods(d.ldr, d.ctxt.Arch, symIdx, &relocs) {
112-
if d.ctxt.Debugvlog > 1 {
113-
d.ctxt.Logf("reached iface method: %v\n", sig)
114-
}
115-
d.ifaceMethod[sig] = true
116-
}
117-
}
118109
}
119110

120111
methods = methods[:0]
121112
for i := 0; i < relocs.Count(); i++ {
122113
r := relocs.At(i)
123114
t := r.Type()
124-
if t == objabi.R_WEAKADDROFF {
115+
switch t {
116+
case objabi.R_WEAKADDROFF:
125117
continue
126-
}
127-
if t == objabi.R_METHODOFF {
118+
case objabi.R_METHODOFF:
128119
if i+2 >= relocs.Count() {
129120
panic("expect three consecutive R_METHODOFF relocs")
130121
}
@@ -146,14 +137,12 @@ func (d *deadcodePass) flood() {
146137
}
147138
i += 2
148139
continue
149-
}
150-
if t == objabi.R_USETYPE {
140+
case objabi.R_USETYPE:
151141
// type symbol used for DWARF. we need to load the symbol but it may not
152142
// be otherwise reachable in the program.
153143
// do nothing for now as we still load all type symbols.
154144
continue
155-
}
156-
if t == objabi.R_USEIFACE {
145+
case objabi.R_USEIFACE:
157146
// R_USEIFACE is a marker relocation that tells the linker the type is
158147
// converted to an interface, i.e. should have UsedInIface set. See the
159148
// comment below for why we need to unset the Reachable bit and re-mark it.
@@ -166,6 +155,18 @@ func (d *deadcodePass) flood() {
166155
}
167156
}
168157
continue
158+
case objabi.R_USEIFACEMETHOD:
159+
// R_USEIFACEMETHOD is a marker relocation that marks an interface
160+
// method as used.
161+
rs := r.Sym()
162+
if d.ldr.SymType(rs) != sym.SDYNIMPORT { // don't decode DYNIMPORT symbol (we'll mark all exported methods anyway)
163+
m := d.decodeIfaceMethod(d.ldr, d.ctxt.Arch, rs, r.Add())
164+
if d.ctxt.Debugvlog > 1 {
165+
d.ctxt.Logf("reached iface method: %v\n", m)
166+
}
167+
d.ifaceMethod[m] = true
168+
}
169+
continue
169170
}
170171
rs := r.Sym()
171172
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
378379
return methods
379380
}
380381

381-
func (d *deadcodePass) decodeIfaceMethods(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs) []methodsig {
382+
// Decode the method of interface type symbol symIdx at offset off.
383+
func (d *deadcodePass) decodeIfaceMethod(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, off int64) methodsig {
382384
p := ldr.Data(symIdx)
383385
if decodetypeKind(arch, p)&kindMask != kindInterface {
384386
panic(fmt.Sprintf("symbol %q is not an interface", ldr.SymName(symIdx)))
385387
}
386-
rel := decodeReloc(ldr, symIdx, relocs, int32(commonsize(arch)+arch.PtrSize))
387-
s := rel.Sym()
388-
if s == 0 {
389-
return nil
390-
}
391-
if s != symIdx {
392-
panic(fmt.Sprintf("imethod slice pointer in %q leads to a different symbol", ldr.SymName(symIdx)))
393-
}
394-
off := int(rel.Add()) // array of reflect.imethod values
395-
numMethods := int(decodetypeIfaceMethodCount(arch, p))
396-
sizeofIMethod := 4 + 4
397-
return d.decodeMethodSig(ldr, arch, symIdx, relocs, off, sizeofIMethod, numMethods)
388+
relocs := ldr.Relocs(symIdx)
389+
var m methodsig
390+
m.name = decodetypeName(ldr, symIdx, &relocs, int(off))
391+
m.typ = decodeRelocSym(ldr, symIdx, &relocs, int32(off+4))
392+
return m
398393
}
399394

400395
func (d *deadcodePass) decodetypeMethods(ldr *loader.Loader, arch *sys.Arch, symIdx loader.Sym, relocs *loader.Relocs) []methodsig {

src/cmd/link/internal/ld/deadcode_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func TestDeadcode(t *testing.T) {
3333
{"ifacemethod", "", "main.T.M"},
3434
{"ifacemethod2", "main.T.M", ""},
3535
{"ifacemethod3", "main.S.M", ""},
36+
{"ifacemethod4", "", "main.T.M"},
3637
}
3738
for _, test := range tests {
3839
test := test
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2020 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// Test that a live type's method is not live even if
6+
// it matches an interface method, as long as the interface
7+
// method is not used.
8+
9+
package main
10+
11+
type T int
12+
13+
func (T) M() {}
14+
15+
type I interface{ M() }
16+
17+
var p *T
18+
var pp *I
19+
20+
func main() {
21+
p = new(T) // use type T
22+
pp = new(I) // use type I
23+
}

0 commit comments

Comments
 (0)