Description
I've been doing a bunch of work on binary size reduction and a common problem that comes up is that the linker's dead code elimination can remove all the references to a package, but the one remaining reference is to the package's inittask
symbol, which is then heavy & brings it a bunch more.
That is:
package main
import "crypto/x509"
func main() {
if false {
x509.NewCertPool()
}
}
Is equivalent today to:
package main
import _ "crypto/x509"
func main() {}
... which slurps in a ton of packages and their init load and extra 1 MiB binary size increase, from 1.3 MiB to 2.3 MiB:
dev:big $ go build --ldflags=--dumpdep x.go 2>&1 | grep inittask
runtime.main -> runtime..inittask
runtime.main -> main..inittask
main..inittask -> crypto/x509..inittask
crypto/x509..inittask -> encoding/pem..inittask
crypto/x509..inittask -> errors..inittask
crypto/x509..inittask -> crypto/aes..inittask
crypto/x509..inittask -> crypto/cipher..inittask
crypto/x509..inittask -> crypto/des..inittask
crypto/x509..inittask -> crypto/md5..inittask
crypto/x509..inittask -> encoding/hex..inittask
crypto/x509..inittask -> io..inittask
crypto/x509..inittask -> strings..inittask
crypto/x509..inittask -> sync..inittask
crypto/x509..inittask -> crypto/rsa..inittask
crypto/x509..inittask -> encoding/asn1..inittask
crypto/x509..inittask -> math/big..inittask
crypto/x509..inittask -> crypto/ecdsa..inittask
crypto/x509..inittask -> crypto/ed25519..inittask
crypto/x509..inittask -> crypto/x509/pkix..inittask
crypto/x509..inittask -> fmt..inittask
crypto/x509..inittask -> io/ioutil..inittask
crypto/x509..inittask -> os..inittask
crypto/x509..inittask -> crypto/elliptic..inittask
crypto/x509..inittask -> bytes..inittask
crypto/x509..inittask -> net..inittask
crypto/x509..inittask -> net/url..inittask
crypto/x509..inittask -> reflect..inittask
crypto/x509..inittask -> time..inittask
crypto/x509..inittask -> crypto..inittask
crypto/x509..inittask -> crypto/dsa..inittask
crypto/x509..inittask -> crypto/sha1..inittask
crypto/x509..inittask -> crypto/sha256..inittask
crypto/x509..inittask -> crypto/sha512..inittask
crypto/x509..inittask -> strconv..inittask
crypto/x509..inittask -> vendor/golang.org/x/crypto/cryptobyte..inittask
crypto/x509..inittask -> crypto/x509.init
encoding/pem..inittask -> encoding/base64..inittask
encoding/pem..inittask -> sort..inittask
encoding/pem..inittask -> encoding/base64..inittask
encoding/pem..inittask -> sort..inittask
encoding/pem..inittask -> encoding/base64..inittask
encoding/pem..inittask -> sort..inittask
errors..inittask -> internal/reflectlite..inittask
errors..inittask -> errors.init
runtime..inittask -> internal/bytealg..inittask
runtime..inittask -> runtime.init
runtime..inittask -> runtime.init.0
runtime..inittask -> runtime.init.3
runtime..inittask -> runtime.init.4
runtime..inittask -> runtime.init.5
runtime..inittask -> runtime.init.6
crypto/aes..inittask -> encoding/binary..inittask
crypto/aes..inittask -> crypto/aes.init
crypto/cipher..inittask -> crypto/cipher.init
crypto/md5..inittask -> hash..inittask
crypto/md5..inittask -> crypto/md5.init.0
encoding/hex..inittask -> encoding/hex.init
io..inittask -> io.init
strings..inittask -> unicode..inittask
sync..inittask -> sync.init
sync..inittask -> sync.init.0
sync..inittask -> sync.init.1
crypto/rsa..inittask -> crypto/internal/randutil..inittask
crypto/rsa..inittask -> crypto/rand..inittask
crypto/rsa..inittask -> math..inittask
crypto/rsa..inittask -> crypto/rsa.init
encoding/asn1..inittask -> encoding/asn1.init
math/big..inittask -> math/rand..inittask
math/big..inittask -> math/big.init
crypto/ecdsa..inittask -> crypto/ecdsa.init
crypto/ed25519..inittask -> crypto/ed25519/internal/edwards25519..inittask
crypto/x509/pkix..inittask -> crypto/x509/pkix.init
fmt..inittask -> internal/fmtsort..inittask
fmt..inittask -> fmt.init
io/ioutil..inittask -> path/filepath..inittask
io/ioutil..inittask -> io/ioutil.init
os..inittask -> syscall..inittask
os..inittask -> internal/oserror..inittask
os..inittask -> internal/poll..inittask
os..inittask -> internal/syscall/execenv..inittask
os..inittask -> internal/syscall/unix..inittask
os..inittask -> os.init
os..inittask -> os.init.0
bytes..inittask -> bytes.init
net..inittask -> context..inittask
net..inittask -> vendor/golang.org/x/net/dns/dnsmessage..inittask
net..inittask -> internal/singleflight..inittask
net..inittask -> net.init
net..inittask -> net.init.0
reflect..inittask -> reflect.init
time..inittask -> time.init
crypto..inittask -> crypto.init
crypto/dsa..inittask -> crypto/dsa.init
crypto/sha1..inittask -> crypto/sha1.init
crypto/sha1..inittask -> crypto/sha1.init.0
crypto/sha256..inittask -> crypto/sha256.init
crypto/sha256..inittask -> crypto/sha256.init.0
crypto/sha512..inittask -> crypto/sha512.init
crypto/sha512..inittask -> crypto/sha512.init.0
strconv..inittask -> strconv.init
vendor/golang.org/x/crypto/cryptobyte..inittask -> vendor/golang.org/x/crypto/cryptobyte.init
encoding/base64..inittask -> encoding/base64.init
internal/bytealg..inittask -> internal/bytealg.init.0
encoding/binary..inittask -> encoding/binary.init
unicode..inittask -> unicode.init
crypto/rand..inittask -> bufio..inittask
crypto/rand..inittask -> crypto/rand.init
crypto/rand..inittask -> crypto/rand.init.0
crypto/rand..inittask -> crypto/rand.init.1
crypto/rand..inittask -> crypto/rand.init.2
math..inittask -> math.init
math/rand..inittask -> math/rand.init
path/filepath..inittask -> path/filepath.init
syscall..inittask -> syscall.init
internal/oserror..inittask -> internal/oserror.init
internal/poll..inittask -> internal/poll.init
context..inittask -> context.init
context..inittask -> context.init.0
vendor/golang.org/x/net/dns/dnsmessage..inittask -> vendor/golang.org/x/net/dns/dnsmessage.init
bufio..inittask -> bufio.init
The only way to get around that is with build tags and more conditional compilation, which is gross.
What I'd like instead is a way to declare to the toolchain that for a given imported package that I'm fine with that package's init-time side effects (like normal) if I need them, but I'm also cool with omitting them if the linker decides that's fine.
That is, I want something like this this strawman syntax:
package main
import (
"crypto/x509" // go:lazyinit
"fmt"
)
func main() {
if false {
NewCertPool()
}
fmt.Println("hi")
}
... so the x509 init (nor its deps) is never run if the toolchain's DCE doesn't want to.
The go:lazyinit
is saying that I'm not depending on any init-time work happening there for the import on that line. (Or perhaps it should be on the line before to be consistent with other //go:
comments, or it shouldn't use comments)
(Arguably this should be the default behavior and imports with side effects would need the declaration, but we can't for backwards compatibility anyway, so not worth considering.)