Follow up to #38685 that seems to have solved a simple case but does not eliminate an unused interface method across packages
go version)?[rojer@nbd /tmp]$ GOROOT=/home/rojer/go/go-git /home/rojer/go/go-git/bin/go version go version devel +b7e0adfee2 Fri Nov 6 07:55:52 2020 +0000 linux/amd64
yes
go env)?go env Output
[rojer@nbd /tmp]$ GOROOT=/home/rojer/go/go-git /home/rojer/go/go-git/bin/go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/rojer/.cache/go-build"
GOENV="/home/rojer/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/rojer/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/rojer/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/home/rojer/go/go-git"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/rojer/go/go-git/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build351127229=/tmp/go-build -gno-record-gcc-switches"
source:
[rojer@nbd /tmp]$ find foobar/
foobar/
foobar/go.mod
foobar/foo
foobar/foo/foo.go
foobar/bar
foobar/bar/bar.go
[rojer@nbd /tmp]$ cat foobar/foo/foo.go
package main
import (
"foobar/bar"
)
func main() {
// Test unused method elimination.
var b bar.Interface
b = bar.Bar{}
b.UsedInterfaceMethod()
}
[rojer@nbd /tmp]$ cat foobar/bar/bar.go
package bar
import "fmt"
type Interface interface {
UsedInterfaceMethod()
UnusedInterfaceMethod()
}
type Bar struct{}
func (Bar) UsedInterfaceMethod() {
fmt.Println("one")
}
// This method is unused but cannot be eliminated yet.
// https://github.com/golang/go/issues/38685
func (Bar) UnusedInterfaceMethod() {
fmt.Println("two")
}
// This method is unused and should be eliminated.
func (Bar) UnusedNonInterfaceMethod() {
fmt.Println("three")
}
foobar/bar.(*Bar).UnusedInterfaceMethod should not make it into the final binary.
in fact, neither should foobar/bar.(*Bar).UsedInterfaceMethod because pointer variant is not used.
in other words, all that should be left from bar.Bar should be bar.Bar.UsedInterfaceMethod.
interesting, that foobar/bar.Bar.UnusedInterfaceMethod does get eliminated but not the bar.*Bar variant.
why is it even generated in the first place?
[rojer@nbd /tmp/foobar/foo]$ GOROOT=/home/rojer/go/go-git /home/rojer/go/go-git/bin/go build
[rojer@nbd /tmp/foobar/foo]$ GOROOT=/home/rojer/go/go-git /home/rojer/go/go-git/bin/go tool nm -size foo | grep foobar/bar
497080 187 T foobar/bar.(*Bar).UnusedInterfaceMethod
497140 187 T foobar/bar.(*Bar).UsedInterfaceMethod
535e20 32 D foobar/bar..inittask
496fe0 138 T foobar/bar.Bar.UsedInterfaceMethod
4da288 40 R go.itab.foobar/bar.Bar,foobar/bar.Interface
Thanks. Interesting example.
I think the unused method is linked in because it is referenced from the itab.
If you change foo.go to do
b = interface{}(bar.Bar{}).(bar.Interface)
then UnusedInterfaceMethod disappears.
I agree this is awkward, and we probably could fix the itab case. But it's too late for 1.16. Will try in 1.17. Thanks.
I'm surprised there is any case in which an exported method is eliminated from a compiled binary. Reflection can reach it without ever naming it: https://play.golang.org/p/zCIdBYzv8zo. I think that's the reason the (Bar) version is eliminated but the (*Bar) version isn't. The non-pointer method is never used, but the pointer-shaped wrapper could be needed through an interface, especially by reflection. (If I'm wrong, please correct me. I'm still learning the internals of cmd/compile.)
@zephyrtronium unused method elimination is disabled if use of reflection is detected. the approach to dead code elimination is described here.
projects that care about binary size have to avoid using reflection in that way (and we do).
Change https://golang.org/cl/268479 mentions this issue: cmd/compile, cmd/link: use weak reference in itab
Most helpful comment
@zephyrtronium unused method elimination is disabled if use of reflection is detected. the approach to dead code elimination is described here.
projects that care about binary size have to avoid using reflection in that way (and we do).