I have been using dead code elimination as a way to circumvent the fact that Go does not support #ifdef. This generally works well but seems fragile so ,for closed source projects, it would be very helpful if there was a clear specification when code is assured to be eliminated.
Ideally the spec would include the elimination of logical expressions that can be evaluated as false during compilation ("ifdef" like patterns), as well as the elimination of parameters that are passed to empty functions (e.g. turn off logs as in log.h)
Gideon.
You may be able to achieve the same goal with the use of dependency inversion.
type Logger interface {
Info(message string)
}
At program start-up time, you can choose which implementation of Logger you want to use.
type ActualLogger struct {
}
func (l *ActualLogger) Info(message string) {
fmt.Println(message)
}
type BlackholeLogger struct {
}
func (l *BlackholeLogger) Info(message string) {
// Do nothing!
}
You can pass these dependencies explicitly to those parts of your code that output logs. You can also use a global variable:
var logger = ActualLogger{}
func doStuff() {
logger.Info("This log entry may either get printed or not")
}
I believe that what @gstupp is after is removing certain code - dead code - from binaries, since he mentioned closed source.
I believe the spec is being kept separate from the main compiler implementation on purpose. In fact, the Go team maintains two separate compilers; there's also gccgo.
But for a definite answer, you want someone like @griesemer or @ianlancetaylor.
I don't think we want to specify dead code elimination in the spec. For one, it's not observable within the language. Second, it would preclude simple implementations of the language (e.g. gopherjs: https://github.com/gopherjs/gopherjs).
When you say it "seems fragile", what do you mean exactly?
The current gc compiler should always eliminate code surrounded by if cond { ... } if cond is a constant (in the language spec sense) that evaluates to false.
I'm pretty sure this does not belong to the spec as it is not related to syntax nor semantics, but to a compiler implementation.
In addition to what the others have written:
You may find build tags to be a better way to simulate #ifdef. This is (more or less) the officially supported way, and even the current state of dead code elimination is far from perfect. For example, you can't use dead code elimination to add/remove top-level declarations like methods and exported variables, but you can use build tags. See the next paragraph for another example.
Even specifying what "dead code elimination" means is complicated. For example, it is required just that there be no instructions corresponding to the eliminable code? Or should other side-effects of the presence of the code be eliminated, such as impacting whether a function can be inlined, or whether variables escape? Prior to 1.9 (which is not even out yet), dead code still impacted inlining and escape analysis. This is definitely not the sort of thing we'd want to discuss in the spec.
Note that even in 1.9, there are multiple kinds of dead code elimination, some of which will remove these side-effects and others of which will not. The earlier DCE, which currently generally works on evaluation of constant expressions, will eliminate them, and the later DCE, which has access to far more sophisticated data flow analysis, will not. But this has and will change over time, and that's good.
You may find this blog post I wrote amusing (or horrifying) reading.
Furthermore, dead code detection is an undecidable problem, which basically means that there are arbitrarily good solutions to it. Including a random one of them in the spec doesn't sound like a particularly good idea.
I believe build tags as suggested by Josh are the right solution here - if some source code files aren't compiled at all as per the spec, there's no way the code can find its way to the final binary. And they're more powerful and predictable than dead code elimination.
@gstupp have you tried using build tags?
@josharian interesting blog post.
I am aware of the fact that DCE is undecidable in general and changes between implementations in practice (hence "fragile") and I do use tags when I can. But I did not find a simple solution for cases where fine-grained control is necessary. For example, to implement an internal debug log I set a constant bool according to the build tag
// +build EXTERNAL,!INTERNAL
const INTERNAL bool = false
and then encapsulate every call to the debuglog with a conditional to make sure that in external builds the strings that are passed to the log are removed from the binary.
func main() {
...
if INTERNAL {
debuglog.Println("Internal IP, must not be exposed")
}
...
}
This seems to be working for now, but there is no guarantee it will work in the future. Still, I don't know of any other way to do this since debug log messages are everywhere.
I think a better way to approach this issue may be
Rather than layering up the specification, I think this can be better
approached by filing specific bugs towards the standing high level goals of
smaller, faster, binaries -- both to compile and run.
On Mon, 14 Aug 2017, 17:28 gstupp notifications@github.com wrote:
@josharian https://github.com/josharian interesting blog post.
I am aware of the fact that DCE is undecidable in general and changes
between implementations in practice (hence "fragile") and I do use tags
when I can. But I did not find a simple solution for cases where
fine-grained control is necessary. For example, to implement an internal
debug log I set a constant bool according to the build tag// +build EXTERNAL,!INTERNALconst INTERNAL bool = false
and then encapsulate every call to the debuglog with a conditional to make
sure that in external builds the strings that are passed to the log are
removed from the binary.func main() {
...
if INTERNAL {
debuglog.Println("Internal IP, must not be exposed")
}
...}
This seems to be working for now, but there is no guarantee it will work
in the future. Still, I don't know of any other way to do this since debug
log messages are everywhere.—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/21424#issuecomment-322118064, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAAcAwdNQhFO0BKFkUKwvSWeGBrtMlBDks5sX_cPgaJpZM4O1qhe
.
I will follow your suggestion and open bugs if and when I hit DCE issues. But because a critical feature of my code depends on an unspecified behavior of the compiler I have to continue using the minimalist mechanism I have today.
Perhaps confirm consider maintaining separate branches of your project in the VCS.
@gstupp As others have already said, putting dead code elimination in the spec is a non-starter: it's really an implementation detail and as @randall77 pointed out, it's not observable in the language and thus doesn't belong into the language spec.
Using constant expressions (which are well defined in the spec) that evaluate to true or false and using those to conditionally "enable/disable" code via if statements is a standard way to control such behavior. The cmd/compile compiler does and will continue to support this.
Closing this as this is not an issue. If you meant this to be a proposal you can re-open it and tag it as proposal. Thanks.
Most helpful comment
In addition to what the others have written:
You may find build tags to be a better way to simulate #ifdef. This is (more or less) the officially supported way, and even the current state of dead code elimination is far from perfect. For example, you can't use dead code elimination to add/remove top-level declarations like methods and exported variables, but you can use build tags. See the next paragraph for another example.
Even specifying what "dead code elimination" means is complicated. For example, it is required just that there be no instructions corresponding to the eliminable code? Or should other side-effects of the presence of the code be eliminated, such as impacting whether a function can be inlined, or whether variables escape? Prior to 1.9 (which is not even out yet), dead code still impacted inlining and escape analysis. This is definitely not the sort of thing we'd want to discuss in the spec.
Note that even in 1.9, there are multiple kinds of dead code elimination, some of which will remove these side-effects and others of which will not. The earlier DCE, which currently generally works on evaluation of constant expressions, will eliminate them, and the later DCE, which has access to far more sophisticated data flow analysis, will not. But this has and will change over time, and that's good.
You may find this blog post I wrote amusing (or horrifying) reading.