Linker unable to remove unused functions?

517 views
Skip to first unread message

unread,
Sep 15, 2010, 3:11:07 PM9/15/10
to golang-nuts
It seems to me that the final executable produced by the linker (8l)
contains functions I am not using at all. Are you also experiencing
this? If you do, do you happen to have some numbers showing by how
much it increases the final executable size?

Kai Backman

unread,
Sep 15, 2010, 3:24:11 PM9/15/10
to ⚛, golang-nuts
The linker is definitely supposed to remove non-used functions, at
least 5l does this and I presume 8l and 6l do the same. Can you be
more specific about which functions it doesn't remove and have you
double checked you aren't referring to them somewhere?

Kai

unread,
Sep 15, 2010, 3:52:20 PM9/15/10
to golang-nuts
For example, when using [http://github.com/0xe2-0x9a-0x9b/Go-SDL] and
the following source code:

----------
package main

import "⚛sdl"

func main() {
sdl.Init(sdl.INIT_VIDEO|sdl.INIT_AUDIO)
}
----------

$ 8g a.go && 8l a.8

A lot of functions are there for no reason. For example:

$ 6nm 8.out | grep UpdateRects
8054267 T %e2%9a%9bsdl.*Surface·UpdateRects
8054901 T %e2%9a%9bsdl._Cfunc_SDL_UpdateRects
805f514 D _cgo_Cfunc_SDL_UpdateRects
8062b8c D go.string."UpdateRects"
8060edc D string."UpdateRects"

Russ Cox

unread,
Sep 15, 2010, 4:41:03 PM9/15/10
to ⚛, golang-nuts
> A lot of functions are there for no reason. For example:
>
> $ 6nm 8.out | grep UpdateRects
>  8054267 T %e2%9a%9bsdl.*Surface·UpdateRects
>  8054901 T %e2%9a%9bsdl._Cfunc_SDL_UpdateRects
>  805f514 D _cgo_Cfunc_SDL_UpdateRects
>  8062b8c D go.string."UpdateRects"
>  8060edc D string."UpdateRects"

Unused functions should be discarded by the linker.
There's definitely a reason. It might be a bug, but it's a reason.
It would help us if you made a copy of your program and pulled
things out until those disappear, to get down to a minimal
program that exhibits the bug.

Thanks.
Russ

unread,
Sep 16, 2010, 5:20:02 AM9/16/10
to golang-nuts
$ ls
a1.go a2.go Makefile

=========

$ cat a1.go
package main

type T struct {}

func (t *T) Unused() {}

var t *T

func fn() {
if t != nil {}
}

func main() {}

=========

$ cat a2.go
package main

func init() {
fn()
}

=========

$ cat Makefile
include $(GOROOT)/src/Make.inc

a: a1.go a2.go
8g -o a.8 $^
8l -o a a.8
6nm a | grep Unused

CLEANFILES+=a

include $(GOROOT)/src/Make.cmd

=========

$ make clean ; make
rm -rf *.o *.a *.[568vq] [568vq].out a
8g -o a.8 a1.go a2.go
8l -o a a.8
6nm a | grep Unused
8055ec4 D go.string."Unused"
8048800 T main.*T·Unused
8054cb4 D string."Unused"

=========

Russ Cox

unread,
Sep 16, 2010, 10:24:26 PM9/16/10
to ⚛, golang-nuts
On Thu, Sep 16, 2010 at 05:20, ⚛ <0xe2.0x...@gmail.com> wrote:
> $ ls
> a1.go  a2.go  Makefile
>
> =========
>
> $ cat a1.go
> package main
>
> type T struct {}
>
> func (t *T) Unused() {}
>
> var t *T
>
> func fn() {
>        if t != nil {}
> }
>
> func main() {}
>
> =========
>
> $ cat a2.go
> package main
>
> func init() {
>        fn()
> }

I think I agree that in this case Unused should be dropped.
However, you're very near the edge. If t were, say,
stored in an interface value, then the type information
for t would include Unused, because some other package
might check and see if t satisfies interface { Unused() }.

I suspect the reason that Unused is kept is that the
same type information we'd store in the interface is
being saved in the debug information describing t's type.
That wasn't what I intended, but I suppose it would
let you 'print t.Unused()' in a sufficiently Go-aware
debugger.

Russ

unread,
Sep 17, 2010, 3:19:00 AM9/17/10
to golang-nuts


On Sep 17, 4:24 am, Russ Cox <r...@golang.org> wrote:
> On Thu, Sep 16, 2010 at 05:20, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:
> > $ ls
> > a1.go  a2.go  Makefile
>
> > =========
>
> > $ cat a1.go
> > package main
>
> > type T struct {}
>
> > func (t *T) Unused() {}
>
> > var t *T
>
> > func fn() {
> >        if t != nil {}
> > }
>
> > func main() {}
>
> > =========
>
> > $ cat a2.go
> > package main
>
> > func init() {
> >        fn()
> > }
>
> I think I agree that in this case Unused should be dropped.
> However, you're very near the edge.  If t were, say,
> stored in an interface value, then the type information
> for t would include Unused, because some other package
> might check and see if t satisfies interface { Unused() }.

I agree. The problem is that this coding pattern is "all over the
place". For example, in the Go package "rand", file "rand/rand.go"
there is a global variable "globalRand". If my source code contains
the statement "rand.Int31()", then it the linker will also preserve
the function "rand.*Rand·ExpFloat64". I checked that the function is
not used from any *.go in the whole Go distribution, and there does
not exist any interface { ExpFloat64() }, so it should be removed.

Another example (I did not test this one) would be when the program
passes a value to function "fmt.Printf". The passed value is converted
into interface{}, which in turn would mean that *any* method defined
on the type of the passed variable will be present in the final
executable.

I have no idea about the precise overhead caused by this issue (in
terms of unnecessary data present in final executables of "real-world"
programs). There is a chance the added overhead is non-trivial.

unread,
Sep 20, 2010, 5:11:21 AM9/20/10
to golang-nuts
Is somebody from the Go team going to make a statement about this
issue? Specifically, I would like to know which one from the following
set of options is most likely:

1. We are going to fix it.
2. We are thinking about it.
3. We concluded the issue is not important.

On Sep 17, 4:24 am, Russ Cox <r...@golang.org> wrote:

Andrew Gerrand

unread,
Sep 20, 2010, 7:52:01 AM9/20/10
to ⚛, golang-nuts
On 20 September 2010 19:11, ⚛ <0xe2.0x...@gmail.com> wrote:
> Is somebody from the Go team going to make a statement about this
> issue? Specifically, I would like to know which one from the following
> set of options is most likely:
>
> 1. We are going to fix it.
> 2. We are thinking about it.
> 3. We concluded the issue is not important.

I can't speak for the compiler developers, but I think it's safe to
assume that diagnosing and correctly handling this issue is a
relatively low priority. For instance, the size of the final binary is
not as important (to most Go developers) as having a better garbage
collector.

Fortunately, Go is an open source project with a liberal licence.
Motivated parties are welcome to investigate (as you have done),
provide detailed diagnoses for, and, in the best case, fix issues as
they arise.

Cheers,
Andrew

unread,
Sep 20, 2010, 8:33:00 AM9/20/10
to golang-nuts
On Sep 20, 1:52 pm, Andrew Gerrand <a...@golang.org> wrote:
> On 20 September 2010 19:11, ⚛ <0xe2.0x9a.0...@gmail.com> wrote:
>
> > Is somebody from the Go team going to make a statement about this
> > issue? Specifically, I would like to know which one from the following
> > set of options is most likely:
>
> > 1. We are going to fix it.
> > 2. We are thinking about it.
> > 3. We concluded the issue is not important.
>
> I can't speak for the compiler developers, but I think it's safe to
> assume that diagnosing and correctly handling this issue is a
> relatively low priority. For instance, the size of the final binary is
> not as important (to most Go developers) as having a better garbage
> collector.

Well, OK, but I would like to note that fixing this issue would
involve improving the compile-time garbage collector (which, although
maybe it is not explicitly mentioned anywhere, is present in the Go
linker). The GC roots are the main() and init() functions - anything
unreachable from these roots is "garbage". (Garbage-collection is a
pretty powerful idea on its own.)

Russ Cox

unread,
Sep 20, 2010, 11:47:15 AM9/20/10
to ⚛, golang-nuts
On Mon, Sep 20, 2010 at 02:11, ⚛ <0xe2.0x...@gmail.com> wrote:
> Is somebody from the Go team going to make a statement about this
> issue? Specifically, I would like to know which one from the following
> set of options is most likely:
>
> 1. We are going to fix it.
> 2. We are thinking about it.
> 3. We concluded the issue is not important.

#2 tending toward #1, but probably not soon.

Fixing the problem, assuming it is a problem, will
require changing the format of the debug information
passed along to the linker so that it does not contain
a list of methods. That's a loss in functionality.
We're also in the middle of making the debug information
available in a format that gdb understands and will
probably need to make changes for that too.
I intend to wait and see what changes gdb needs
and then probably fix this at the same time.

Russ

Cory Mainwaring

unread,
Sep 20, 2010, 1:33:30 PM9/20/10
to r...@golang.org, ⚛, golang-nuts
Keep functions that are referenced by Interfaces and/or used in the
scope of the compiling program, then strip everything else; how does
that involve debug info? Except for the possibility of debug functions
that are called by a debugger at the behest of a developer, but
couldn't that be simply solved by a -debug switch on the linker that
preserves all functions and methods?

Russ Cox

unread,
Sep 20, 2010, 1:36:59 PM9/20/10
to Cory Mainwaring, ⚛, golang-nuts
On Mon, Sep 20, 2010 at 10:33, Cory Mainwaring <olr...@gmail.com> wrote:
> Keep functions that are referenced by Interfaces and/or used in the
> scope of the compiling program, then strip everything else; how does
> that involve debug info?

Because the debug info is the reference to otherwise
unused functions that is keeping them in the binary.

Russ

Reply all
Reply to author
Forward
0 new messages