Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/link: linker should be able to remove init functions with no side-effects #19533

Open
rasky opened this issue Mar 13, 2017 · 6 comments
Open
Labels
binary-size compiler/runtime Issues related to the Go compiler and/or runtime.
Milestone

Comments

@rasky
Copy link
Member

rasky commented Mar 13, 2017

In this example:

testlinker/a/a.go

package a

type DieDieType struct {
	Test map[string]int
}

var DieDieVar = &DieDieType{
	Test: make(map[string]int),
}

var Constant = 5

testlinker/main.go

package main

import (
	"fmt"
	"testlinker/a"
)

func main() {
	fmt.Println(a.Constant)
}

the linker is unable to remove references to DieDieType because it is referenced by a.init. But this init function, generated by the compiler to initialize DieDieValue, is actually useless, because it has no side-effects and only initializes a global variable that is then unreferenced. So the init function could be dropped, and this in turn would remove references to the types.

This is distilled from the analysis in #19523, where just referencing a constant in a package causes a very large growth of binary size because of a deep chain of dependencies which are not used by the program. I haven't fully tracked everything so I can't swear that all init functions are side-effects-free in that case, but I think there's a reasonable chance that it would fix that instance.

@ianlancetaylor
Copy link
Contributor

I'm not opposed to this idea but I think it's going to be difficult to implement effectively as a linker function. Compiler generated init functions are going to make function calls. For the linker to eliminate a compiler generated init function, it will have to traverse the entire transitive set of function calls made by the init function to make sure that they do not change any referenced global variables. In this case we are going to see function calls to runtime.makemapandruntime.newobject`, which do of course change referenced global variables, so the linker is going to have to have a list of functions that should be ignored during this transitive walk.

I think a simpler approach might be for the compiler to generate separate init functions for each global variable that requires one, and to annotate the init function in some way to say that this init function does not do anything important other than initialize a global variable. Then it would be easy for the linker to not count references from that function when deciding whether a global variable is used, and to discard the init function along with the global variable if it is not used.

That approach would of course not be as general, but I think it would be simpler to implement, more likely to be correct, and I suspect it would catch many of the cases where variables can actually be eliminated.

@bradfitz bradfitz added this to the Unplanned milestone Mar 13, 2017
@rasky
Copy link
Member Author

rasky commented Mar 13, 2017

I tried to keep the example minimal for the sake of focusing the problem, but I have a few more complex cases in mind, for the sake of analyzing what the correct solution would be. For instance, what about this:

package main

import (
	"os"
	"math"
	"fmt"
	"strconv"
)

var Data1 float64
var Data2 float64

func init() {
	value := os.Getenv("TEST")
	val, _ := strconv.ParseFloat(value, 64)
	Data1 = math.Sqrt(val)
}

func init() {
	Data2 = Data1 + 1.0
}

func init() {
	Data1 = Data2 + 1.0
}

In this case, if Data1 and Data2 are not referenced, the init functions could probably be dropped as well. This is why I used the term "not having side effects": maybe it's not the right term, and I don't know if this property already exists in the compiler for other uses (e.g.: dead code elimination).

For instance, I assume that the Go language specification tells us that, instead, those init functions couldn't be dropped if the printed something or possibly panicked (with no way for the compiler to prove that the panic is never triggered).

@ianlancetaylor
Copy link
Contributor

In that example, your init function calls os.Getenv which calls syscall.Getenv which calls envOnce.Do(copyenv) where copyenv changes the package variable syscall.env. The chances that the linker can safely and reliably conclude that the init function can be eliminated seem minimal to me.

@randall77
Copy link
Contributor

See #9355 #7599 #10081 for some light reading related to this issue.
They are mostly about replacing compiler-generated init functions with link-time operations so that unused variables can be dropped. I don't think we're going to be able to handle user-written init functions any time soon.

@rasky
Copy link
Member Author

rasky commented Oct 23, 2017

Another instance of this issue is flag definitions for programs that don't use flags. See for instance https://go-review.googlesource.com/c/go/+/49251 where code was changed into non-idiomatic way to avoid unused dependencies.

@mvdan
Copy link
Member

mvdan commented Jul 7, 2018

Is this a duplicate of #14840?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
binary-size compiler/runtime Issues related to the Go compiler and/or runtime.
Projects
None yet
Development

No branches or pull requests

6 participants