• David Crawshaw's avatar
    cmd/link: prune unused methods · 862b9ddd
    David Crawshaw authored
    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/20483Reviewed-by: 's avatarRuss Cox <rsc@golang.org>
    862b9ddd
pobj.go 7.39 KB