2015-02-27 22:57:28 -05:00
|
|
|
// Copyright 2009 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.
|
|
|
|
|
|
2015-04-24 20:24:49 -07:00
|
|
|
// go-specific code shared across loaders (5l, 6l, 8l).
|
|
|
|
|
|
2015-02-27 22:57:28 -05:00
|
|
|
package ld
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2016-04-06 21:45:29 -07:00
|
|
|
"cmd/internal/bio"
|
2015-02-27 22:57:28 -05:00
|
|
|
"cmd/internal/obj"
|
|
|
|
|
"fmt"
|
2016-04-08 18:19:10 +02:00
|
|
|
"io"
|
2015-02-27 22:57:28 -05:00
|
|
|
"os"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// go-specific code shared across loaders (5l, 6l, 8l).
|
|
|
|
|
|
|
|
|
|
// replace all "". with pkg.
|
|
|
|
|
func expandpkg(t0 string, pkg string) string {
|
|
|
|
|
return strings.Replace(t0, `"".`, pkg+".", -1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
|
// generate debugging section in binary.
|
|
|
|
|
// once the dust settles, try to move some code to
|
|
|
|
|
// libmach, so that other linkers and ar can share.
|
|
|
|
|
|
2016-04-08 19:14:03 +10:00
|
|
|
func ldpkg(f *bio.Reader, pkg string, length int64, filename string, whence int) {
|
2015-02-27 22:57:28 -05:00
|
|
|
var p0, p1 int
|
|
|
|
|
|
|
|
|
|
if Debug['g'] != 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if int64(int(length)) != length {
|
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: too much pkg data in %s\n", os.Args[0], filename)
|
|
|
|
|
if Debug['u'] != 0 {
|
2015-04-09 07:37:17 -04:00
|
|
|
errorexit()
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-17 15:10:25 -05:00
|
|
|
// In a __.PKGDEF, we only care about the package name.
|
|
|
|
|
// Don't read all the export data.
|
|
|
|
|
if length > 1000 && whence == Pkgdef {
|
|
|
|
|
length = 1000
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-02 12:35:15 -05:00
|
|
|
bdata := make([]byte, length)
|
2016-04-08 18:19:10 +02:00
|
|
|
if _, err := io.ReadFull(f, bdata); err != nil {
|
2015-02-27 22:57:28 -05:00
|
|
|
fmt.Fprintf(os.Stderr, "%s: short pkg read %s\n", os.Args[0], filename)
|
|
|
|
|
if Debug['u'] != 0 {
|
2015-04-09 07:37:17 -04:00
|
|
|
errorexit()
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2015-03-02 12:35:15 -05:00
|
|
|
data := string(bdata)
|
2015-02-27 22:57:28 -05:00
|
|
|
|
2016-04-26 21:50:59 -04:00
|
|
|
// process header lines
|
|
|
|
|
isSafe := false
|
|
|
|
|
isMain := false
|
|
|
|
|
for data != "" {
|
|
|
|
|
var line string
|
|
|
|
|
if i := strings.Index(data, "\n"); i >= 0 {
|
|
|
|
|
line, data = data[:i], data[i+1:]
|
|
|
|
|
} else {
|
|
|
|
|
line, data = data, ""
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
2016-04-26 21:50:59 -04:00
|
|
|
if line == "safe" {
|
|
|
|
|
isSafe = true
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
2016-04-26 21:50:59 -04:00
|
|
|
if line == "main" {
|
|
|
|
|
isMain = true
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
2016-04-26 21:50:59 -04:00
|
|
|
if line == "" {
|
|
|
|
|
break
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
2016-04-26 21:50:59 -04:00
|
|
|
}
|
2015-02-27 22:57:28 -05:00
|
|
|
|
2016-04-26 21:50:59 -04:00
|
|
|
if whence == Pkgdef || whence == FileObj {
|
|
|
|
|
if pkg == "main" && !isMain {
|
|
|
|
|
Exitf("%s: not package main", filename)
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
2016-04-26 21:50:59 -04:00
|
|
|
if Debug['u'] != 0 && whence != ArchiveObj && !isSafe {
|
|
|
|
|
Exitf("load of unsafe package %s", filename)
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// __.PKGDEF has no cgo section - those are in the C compiler-generated object files.
|
|
|
|
|
if whence == Pkgdef {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// look for cgo section
|
2016-04-26 21:50:59 -04:00
|
|
|
p0 = strings.Index(data, "\n$$ // cgo")
|
2015-02-27 22:57:28 -05:00
|
|
|
if p0 >= 0 {
|
|
|
|
|
p0 += p1
|
|
|
|
|
i := strings.IndexByte(data[p0+1:], '\n')
|
|
|
|
|
if i < 0 {
|
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: found $$ // cgo but no newline in %s\n", os.Args[0], filename)
|
|
|
|
|
if Debug['u'] != 0 {
|
2015-04-09 07:37:17 -04:00
|
|
|
errorexit()
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
p0 += 1 + i
|
|
|
|
|
|
|
|
|
|
p1 = strings.Index(data[p0:], "\n$$")
|
|
|
|
|
if p1 < 0 {
|
|
|
|
|
p1 = strings.Index(data[p0:], "\n!\n")
|
|
|
|
|
}
|
|
|
|
|
if p1 < 0 {
|
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: cannot find end of // cgo section in %s\n", os.Args[0], filename)
|
|
|
|
|
if Debug['u'] != 0 {
|
2015-04-09 07:37:17 -04:00
|
|
|
errorexit()
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
p1 += p0
|
|
|
|
|
|
|
|
|
|
loadcgo(filename, pkg, data[p0:p1])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func loadcgo(file string, pkg string, p string) {
|
|
|
|
|
var next string
|
|
|
|
|
var q string
|
|
|
|
|
var f []string
|
|
|
|
|
var local string
|
|
|
|
|
var remote string
|
|
|
|
|
var lib string
|
2016-08-19 11:35:54 -04:00
|
|
|
var s *Symbol
|
2015-02-27 22:57:28 -05:00
|
|
|
|
2015-03-02 12:35:15 -05:00
|
|
|
p0 := ""
|
2015-02-27 22:57:28 -05:00
|
|
|
for ; p != ""; p = next {
|
|
|
|
|
if i := strings.Index(p, "\n"); i >= 0 {
|
|
|
|
|
p, next = p[:i], p[i+1:]
|
|
|
|
|
} else {
|
|
|
|
|
next = ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p0 = p // save for error message
|
|
|
|
|
f = tokenize(p)
|
|
|
|
|
if len(f) == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f[0] == "cgo_import_dynamic" {
|
|
|
|
|
if len(f) < 2 || len(f) > 4 {
|
|
|
|
|
goto err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local = f[1]
|
|
|
|
|
remote = local
|
|
|
|
|
if len(f) > 2 {
|
|
|
|
|
remote = f[2]
|
|
|
|
|
}
|
|
|
|
|
lib = ""
|
|
|
|
|
if len(f) > 3 {
|
|
|
|
|
lib = f[3]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if Debug['d'] != 0 {
|
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: %s: cannot use dynamic imports with -d flag\n", os.Args[0], file)
|
|
|
|
|
nerrors++
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if local == "_" && remote == "_" {
|
|
|
|
|
// allow #pragma dynimport _ _ "foo.so"
|
|
|
|
|
// to force a link of foo.so.
|
|
|
|
|
havedynamic = 1
|
|
|
|
|
|
2015-05-12 16:07:05 +12:00
|
|
|
if HEADTYPE == obj.Hdarwin {
|
|
|
|
|
Machoadddynlib(lib)
|
|
|
|
|
} else {
|
|
|
|
|
dynlib = append(dynlib, lib)
|
|
|
|
|
}
|
2015-02-27 22:57:28 -05:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local = expandpkg(local, pkg)
|
|
|
|
|
q = ""
|
|
|
|
|
if i := strings.Index(remote, "#"); i >= 0 {
|
|
|
|
|
remote, q = remote[:i], remote[i+1:]
|
|
|
|
|
}
|
|
|
|
|
s = Linklookup(Ctxt, local, 0)
|
|
|
|
|
if local != f[1] {
|
|
|
|
|
}
|
2015-04-19 19:33:58 -07:00
|
|
|
if s.Type == 0 || s.Type == obj.SXREF || s.Type == obj.SHOSTOBJ {
|
2015-02-27 22:57:28 -05:00
|
|
|
s.Dynimplib = lib
|
|
|
|
|
s.Extname = remote
|
|
|
|
|
s.Dynimpvers = q
|
2015-04-19 19:33:58 -07:00
|
|
|
if s.Type != obj.SHOSTOBJ {
|
|
|
|
|
s.Type = obj.SDYNIMPORT
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
havedynamic = 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f[0] == "cgo_import_static" {
|
|
|
|
|
if len(f) != 2 {
|
|
|
|
|
goto err
|
|
|
|
|
}
|
|
|
|
|
local = f[1]
|
|
|
|
|
s = Linklookup(Ctxt, local, 0)
|
2015-04-19 19:33:58 -07:00
|
|
|
s.Type = obj.SHOSTOBJ
|
2015-02-27 22:57:28 -05:00
|
|
|
s.Size = 0
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f[0] == "cgo_export_static" || f[0] == "cgo_export_dynamic" {
|
|
|
|
|
if len(f) < 2 || len(f) > 3 {
|
|
|
|
|
goto err
|
|
|
|
|
}
|
|
|
|
|
local = f[1]
|
|
|
|
|
if len(f) > 2 {
|
|
|
|
|
remote = f[2]
|
|
|
|
|
} else {
|
|
|
|
|
remote = local
|
|
|
|
|
}
|
|
|
|
|
local = expandpkg(local, pkg)
|
|
|
|
|
s = Linklookup(Ctxt, local, 0)
|
|
|
|
|
|
2015-04-09 10:44:05 -04:00
|
|
|
switch Buildmode {
|
|
|
|
|
case BuildmodeCShared, BuildmodeCArchive:
|
|
|
|
|
if s == Linklookup(Ctxt, "main", 0) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// export overrides import, for openbsd/cgo.
|
|
|
|
|
// see issue 4878.
|
|
|
|
|
if s.Dynimplib != "" {
|
|
|
|
|
s.Dynimplib = ""
|
|
|
|
|
s.Extname = ""
|
|
|
|
|
s.Dynimpvers = ""
|
|
|
|
|
s.Type = 0
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-02 07:59:49 -05:00
|
|
|
if !s.Attr.CgoExport() {
|
2015-02-27 22:57:28 -05:00
|
|
|
s.Extname = remote
|
|
|
|
|
dynexp = append(dynexp, s)
|
|
|
|
|
} else if s.Extname != remote {
|
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: conflicting cgo_export directives: %s as %s and %s\n", os.Args[0], s.Name, s.Extname, remote)
|
|
|
|
|
nerrors++
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f[0] == "cgo_export_static" {
|
2016-03-02 07:59:49 -05:00
|
|
|
s.Attr |= AttrCgoExportStatic
|
2015-02-27 22:57:28 -05:00
|
|
|
} else {
|
2016-03-02 07:59:49 -05:00
|
|
|
s.Attr |= AttrCgoExportDynamic
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
if local != f[1] {
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f[0] == "cgo_dynamic_linker" {
|
|
|
|
|
if len(f) != 2 {
|
|
|
|
|
goto err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if Debug['I'] == 0 {
|
|
|
|
|
if interpreter != "" && interpreter != f[1] {
|
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: conflict dynlinker: %s and %s\n", os.Args[0], interpreter, f[1])
|
|
|
|
|
nerrors++
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interpreter = f[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if f[0] == "cgo_ldflag" {
|
|
|
|
|
if len(f) != 2 {
|
|
|
|
|
goto err
|
|
|
|
|
}
|
|
|
|
|
ldflag = append(ldflag, f[1])
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: %s: invalid dynimport line: %s\n", os.Args[0], file, p0)
|
|
|
|
|
nerrors++
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-12 15:59:15 +12:00
|
|
|
var seenlib = make(map[string]bool)
|
2015-05-12 15:40:13 +12:00
|
|
|
|
|
|
|
|
func adddynlib(lib string) {
|
2015-05-12 16:07:05 +12:00
|
|
|
if seenlib[lib] || Linkmode == LinkExternal {
|
2015-05-12 15:40:13 +12:00
|
|
|
return
|
|
|
|
|
}
|
2015-05-12 15:59:15 +12:00
|
|
|
seenlib[lib] = true
|
2015-05-12 15:40:13 +12:00
|
|
|
|
|
|
|
|
if Iself {
|
|
|
|
|
s := Linklookup(Ctxt, ".dynstr", 0)
|
|
|
|
|
if s.Size == 0 {
|
|
|
|
|
Addstring(s, "")
|
|
|
|
|
}
|
|
|
|
|
Elfwritedynent(Linklookup(Ctxt, ".dynamic", 0), DT_NEEDED, uint64(Addstring(s, lib)))
|
|
|
|
|
} else {
|
|
|
|
|
Diag("adddynlib: unsupported binary format")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-19 11:35:54 -04:00
|
|
|
func Adddynsym(ctxt *Link, s *Symbol) {
|
2015-05-12 16:07:05 +12:00
|
|
|
if s.Dynid >= 0 || Linkmode == LinkExternal {
|
2015-05-12 15:59:15 +12:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if Iself {
|
|
|
|
|
Elfadddynsym(ctxt, s)
|
|
|
|
|
} else if HEADTYPE == obj.Hdarwin {
|
|
|
|
|
Diag("adddynsym: missed symbol %s (%s)", s.Name, s.Extname)
|
|
|
|
|
} else if HEADTYPE == obj.Hwindows {
|
|
|
|
|
// already taken care of
|
|
|
|
|
} else {
|
|
|
|
|
Diag("adddynsym: unsupported binary format")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
cmd/link: prune unused methods
Today the linker keeps all methods of reachable types. This is
necessary if a program uses reflect.Value.Call. But while use of
reflection is widespread in Go for encoders and decoders, using
it to call a method is rare.
This CL looks for the use of reflect.Value.Call in a program, and
if it is absent, adopts a (reasonably conservative) method pruning
strategy as part of dead code elimination. Any method that is
directly called is kept, and any method that matches a used
interface's method signature is kept.
Whether or not a method body is kept is determined by the relocation
from its receiver's *rtype to its *rtype. A small change in the
compiler marks these relocations as R_METHOD so they can be easily
collected and manipulated by the linker.
As a bonus, this technique removes the text segment of methods that
have been inlined. Looking at the output of building cmd/objdump with
-ldflags=-v=2 shows that inlined methods like
runtime.(*traceAllocBlockPtr).ptr are removed from the program.
Relatively little work is necessary to do this. Linking two
examples, jujud and cmd/objdump show no more than +2% link time.
Binaries that do not use reflect.Call.Value drop 4 - 20% in size:
addr2line: -793KB (18%)
asm: -346KB (8%)
cgo: -490KB (10%)
compile: -564KB (4%)
dist: -736KB (17%)
fix: -404KB (12%)
link: -328KB (7%)
nm: -827KB (19%)
objdump: -712KB (16%)
pack: -327KB (14%)
yacc: -350KB (10%)
Binaries that do use reflect.Call.Value see a modest size decrease
of 2 - 6% thanks to pruning of unexported methods:
api: -151KB (3%)
cover: -222KB (4%)
doc: -106KB (2.5%)
pprof: -314KB (3%)
trace: -357KB (4%)
vet: -187KB (2.7%)
jujud: -4.4MB (5.8%)
cmd/go: -384KB (3.4%)
The trivial Hello example program goes from 2MB to 1.68MB:
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Method pruning also helps when building small binaries with
"-ldflags=-s -w". The above program goes from 1.43MB to 1.2MB.
Unfortunately the linker can only tell if reflect.Value.Call has been
statically linked, not if it is dynamically used. And while use is
rare, it is linked into a very common standard library package,
text/template. The result is programs like cmd/go, which don't use
reflect.Value.Call, see limited benefit from this CL. If binary size
is important enough it may be possible to address this in future work.
For #6853.
Change-Id: Iabe90e210e813b08c3f8fd605f841f0458973396
Reviewed-on: https://go-review.googlesource.com/20483
Reviewed-by: Russ Cox <rsc@golang.org>
2016-03-07 23:45:04 -05:00
|
|
|
func fieldtrack(ctxt *Link) {
|
2015-02-27 22:57:28 -05:00
|
|
|
// record field tracking references
|
2015-03-03 22:20:16 -05:00
|
|
|
var buf bytes.Buffer
|
cmd/link: prune unused methods
Today the linker keeps all methods of reachable types. This is
necessary if a program uses reflect.Value.Call. But while use of
reflection is widespread in Go for encoders and decoders, using
it to call a method is rare.
This CL looks for the use of reflect.Value.Call in a program, and
if it is absent, adopts a (reasonably conservative) method pruning
strategy as part of dead code elimination. Any method that is
directly called is kept, and any method that matches a used
interface's method signature is kept.
Whether or not a method body is kept is determined by the relocation
from its receiver's *rtype to its *rtype. A small change in the
compiler marks these relocations as R_METHOD so they can be easily
collected and manipulated by the linker.
As a bonus, this technique removes the text segment of methods that
have been inlined. Looking at the output of building cmd/objdump with
-ldflags=-v=2 shows that inlined methods like
runtime.(*traceAllocBlockPtr).ptr are removed from the program.
Relatively little work is necessary to do this. Linking two
examples, jujud and cmd/objdump show no more than +2% link time.
Binaries that do not use reflect.Call.Value drop 4 - 20% in size:
addr2line: -793KB (18%)
asm: -346KB (8%)
cgo: -490KB (10%)
compile: -564KB (4%)
dist: -736KB (17%)
fix: -404KB (12%)
link: -328KB (7%)
nm: -827KB (19%)
objdump: -712KB (16%)
pack: -327KB (14%)
yacc: -350KB (10%)
Binaries that do use reflect.Call.Value see a modest size decrease
of 2 - 6% thanks to pruning of unexported methods:
api: -151KB (3%)
cover: -222KB (4%)
doc: -106KB (2.5%)
pprof: -314KB (3%)
trace: -357KB (4%)
vet: -187KB (2.7%)
jujud: -4.4MB (5.8%)
cmd/go: -384KB (3.4%)
The trivial Hello example program goes from 2MB to 1.68MB:
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Method pruning also helps when building small binaries with
"-ldflags=-s -w". The above program goes from 1.43MB to 1.2MB.
Unfortunately the linker can only tell if reflect.Value.Call has been
statically linked, not if it is dynamically used. And while use is
rare, it is linked into a very common standard library package,
text/template. The result is programs like cmd/go, which don't use
reflect.Value.Call, see limited benefit from this CL. If binary size
is important enough it may be possible to address this in future work.
For #6853.
Change-Id: Iabe90e210e813b08c3f8fd605f841f0458973396
Reviewed-on: https://go-review.googlesource.com/20483
Reviewed-by: Russ Cox <rsc@golang.org>
2016-03-07 23:45:04 -05:00
|
|
|
for _, s := range ctxt.Allsym {
|
2015-02-27 22:57:28 -05:00
|
|
|
if strings.HasPrefix(s.Name, "go.track.") {
|
2016-03-02 07:59:49 -05:00
|
|
|
s.Attr |= AttrSpecial // do not lay out in data segment
|
|
|
|
|
s.Attr |= AttrHidden
|
|
|
|
|
if s.Attr.Reachable() {
|
2015-03-03 22:20:16 -05:00
|
|
|
buf.WriteString(s.Name[9:])
|
2016-03-02 15:59:38 -05:00
|
|
|
for p := s.Reachparent; p != nil; p = p.Reachparent {
|
2015-03-03 22:20:16 -05:00
|
|
|
buf.WriteString("\t")
|
|
|
|
|
buf.WriteString(p.Name)
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
2015-03-03 22:20:16 -05:00
|
|
|
buf.WriteString("\n")
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
|
2015-04-19 19:33:58 -07:00
|
|
|
s.Type = obj.SCONST
|
2015-02-27 22:57:28 -05:00
|
|
|
s.Value = 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if tracksym == "" {
|
|
|
|
|
return
|
|
|
|
|
}
|
cmd/link: prune unused methods
Today the linker keeps all methods of reachable types. This is
necessary if a program uses reflect.Value.Call. But while use of
reflection is widespread in Go for encoders and decoders, using
it to call a method is rare.
This CL looks for the use of reflect.Value.Call in a program, and
if it is absent, adopts a (reasonably conservative) method pruning
strategy as part of dead code elimination. Any method that is
directly called is kept, and any method that matches a used
interface's method signature is kept.
Whether or not a method body is kept is determined by the relocation
from its receiver's *rtype to its *rtype. A small change in the
compiler marks these relocations as R_METHOD so they can be easily
collected and manipulated by the linker.
As a bonus, this technique removes the text segment of methods that
have been inlined. Looking at the output of building cmd/objdump with
-ldflags=-v=2 shows that inlined methods like
runtime.(*traceAllocBlockPtr).ptr are removed from the program.
Relatively little work is necessary to do this. Linking two
examples, jujud and cmd/objdump show no more than +2% link time.
Binaries that do not use reflect.Call.Value drop 4 - 20% in size:
addr2line: -793KB (18%)
asm: -346KB (8%)
cgo: -490KB (10%)
compile: -564KB (4%)
dist: -736KB (17%)
fix: -404KB (12%)
link: -328KB (7%)
nm: -827KB (19%)
objdump: -712KB (16%)
pack: -327KB (14%)
yacc: -350KB (10%)
Binaries that do use reflect.Call.Value see a modest size decrease
of 2 - 6% thanks to pruning of unexported methods:
api: -151KB (3%)
cover: -222KB (4%)
doc: -106KB (2.5%)
pprof: -314KB (3%)
trace: -357KB (4%)
vet: -187KB (2.7%)
jujud: -4.4MB (5.8%)
cmd/go: -384KB (3.4%)
The trivial Hello example program goes from 2MB to 1.68MB:
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Method pruning also helps when building small binaries with
"-ldflags=-s -w". The above program goes from 1.43MB to 1.2MB.
Unfortunately the linker can only tell if reflect.Value.Call has been
statically linked, not if it is dynamically used. And while use is
rare, it is linked into a very common standard library package,
text/template. The result is programs like cmd/go, which don't use
reflect.Value.Call, see limited benefit from this CL. If binary size
is important enough it may be possible to address this in future work.
For #6853.
Change-Id: Iabe90e210e813b08c3f8fd605f841f0458973396
Reviewed-on: https://go-review.googlesource.com/20483
Reviewed-by: Russ Cox <rsc@golang.org>
2016-03-07 23:45:04 -05:00
|
|
|
s := Linklookup(ctxt, tracksym, 0)
|
2016-03-02 07:59:49 -05:00
|
|
|
if !s.Attr.Reachable() {
|
2015-02-27 22:57:28 -05:00
|
|
|
return
|
|
|
|
|
}
|
2015-03-03 22:20:16 -05:00
|
|
|
addstrdata(tracksym, buf.String())
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func addexport() {
|
2015-04-19 19:33:58 -07:00
|
|
|
if HEADTYPE == obj.Hdarwin {
|
2015-02-27 22:57:28 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-12 16:07:05 +12:00
|
|
|
for _, exp := range dynexp {
|
|
|
|
|
Adddynsym(Ctxt, exp)
|
|
|
|
|
}
|
|
|
|
|
for _, lib := range dynlib {
|
|
|
|
|
adddynlib(lib)
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Pkg struct {
|
2015-03-05 17:45:11 -08:00
|
|
|
mark bool
|
|
|
|
|
checked bool
|
|
|
|
|
path string
|
2015-02-27 22:57:28 -05:00
|
|
|
impby []*Pkg
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-09 15:04:45 +10:00
|
|
|
var pkgall []*Pkg
|
2015-02-27 22:57:28 -05:00
|
|
|
|
2015-03-05 17:45:11 -08:00
|
|
|
func (p *Pkg) cycle() *Pkg {
|
|
|
|
|
if p.checked {
|
2015-02-27 22:57:28 -05:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-05 17:45:11 -08:00
|
|
|
if p.mark {
|
2015-02-27 22:57:28 -05:00
|
|
|
nerrors++
|
|
|
|
|
fmt.Printf("import cycle:\n")
|
2015-03-05 17:45:11 -08:00
|
|
|
fmt.Printf("\t%s\n", p.path)
|
2015-02-27 22:57:28 -05:00
|
|
|
return p
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-05 17:45:11 -08:00
|
|
|
p.mark = true
|
|
|
|
|
for _, q := range p.impby {
|
|
|
|
|
if bad := q.cycle(); bad != nil {
|
|
|
|
|
p.mark = false
|
|
|
|
|
p.checked = true
|
|
|
|
|
fmt.Printf("\timports %s\n", p.path)
|
2015-02-27 22:57:28 -05:00
|
|
|
if bad == p {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return bad
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-05 17:45:11 -08:00
|
|
|
p.checked = true
|
|
|
|
|
p.mark = false
|
2015-02-27 22:57:28 -05:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func importcycles() {
|
2015-03-05 17:45:11 -08:00
|
|
|
for _, p := range pkgall {
|
|
|
|
|
p.cycle()
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setlinkmode(arg string) {
|
|
|
|
|
if arg == "internal" {
|
|
|
|
|
Linkmode = LinkInternal
|
|
|
|
|
} else if arg == "external" {
|
|
|
|
|
Linkmode = LinkExternal
|
|
|
|
|
} else if arg == "auto" {
|
|
|
|
|
Linkmode = LinkAuto
|
|
|
|
|
} else {
|
2015-04-09 07:37:17 -04:00
|
|
|
Exitf("unknown link mode -linkmode %s", arg)
|
2015-02-27 22:57:28 -05:00
|
|
|
}
|
|
|
|
|
}
|