-
Notifications
You must be signed in to change notification settings - Fork 18k
cmd/compile: must flush dead but modified arguments back to stack before each call #15277
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
Comments
FWIW, adding
at the top of func f in the test program makes the test pass (because then SSA knows x is needed at various points in the function). Another variant on the test program, making sure that writes are flushed even when they're about to be overwritten, if code between the write and the overwrite might panic.
|
https://go-review.googlesource.com/22365 is a start at this. The first is this:
So the So spill
The Long story short, this is tricky. Continuing to think about it. Note that the defer trick Russ added does work, but only because it (implicitly) takes the address of the argument. I don't think we want to mark all input args as addrtaken to fix this problem. By the way, we don't spill other variables to PARAM slots (any more). The only variables that can share a stack slot are two AUTO variables. |
Here's another tricky case:
The Forcing the compiler to write back the value of Not sure what to do about this either. Yet more trickiness... |
Another fun example
This is similar to the |
Do we want a special
NFE would be a minor pain to implement because of its interactions with CSE, register allocation, and deadcode, but at least we would be communicating our intent clearly. There's also need to be some sort of a "don't get cute" rule (a go vet check?) for use of gc.NilForEffect passed as a parameter or flowing into a conditional assignment. |
For Go 1.7, what if you just treat |
This is not a maybe. This is a critical bug that needs to be fixed for Go 1.7. |
I don't think we should special-case nils. There are other writes that could happen too. What is the downside to marking all the arguments and results addrtaken for Go 1.7? |
I just mailed a potential fix for this. It will need some discussion. Basically, it implements runtime.KeepAlive internally and calls it for pointer arguments after each call and at each return. Pointer arguments only, because those are the ones that matter for finalizers, and compound objects (slices, structs, ...) are a much more invasive change. The only extra code this CL will end up generating is spills for otherwise dead values. It does not explicitly write back nil to argument slots, instead it marks the input argument slot as not live at the appropriate safe points. This requires turning off the "inputs are always live everywhere" code you will see commented out in the CL. We need to decide whether that code matters. (The comment says it is there to help with unnamed input parameters, not sure how much we care about those.) |
CL https://golang.org/cl/22365 mentions this issue. |
I am still seeing memory leaks in the production code from which my first test case was distilled. Here is a slightly different distillation that still demonstrates the leaks.
This program runs fine with The code is trying to allocate a local variable x1 initialized from the parameter x and then nil out x, under the theory that, in contrast to x, the local variable will not be kept alive for the lifetime of the function. In particular, after the call to g, x1 is no longer needed and should stop being live, allowing the underlying allocation to be reclaimed. The theory works in the old back end. In the SSA back end, there's a leak. Looking at the liveness analysis that runs on the SSA-generated assembly:
All those live x are supposed to be OK because I set x to nil. But it appears that even though SSA needs x1 across the initial calls to inuse and printxxx, it does not put x1 itself on the stack. Instead it chooses not to nil x out and simply reload x1 from x later. I infer this from the fact that x1 must be saved somewhere yet does not appear in the liveness bitmaps, and therefore x1 is being reloaded from x rather than being saved. But that has the effect of never nilling out x, and then since x is live for the entire function, so is the allocation I'm trying to make available for garbage collection. I don't believe we can reasonably drop the whole-function-lifetime liveness of function parameters at this point in the Go 1.7 cycle, but it's clear we probably need to do that at the start of the Go 1.8 cycle. This is too confusing. (On the other hand, I expect subtle keepalive problems in Go 1.8.) I can fix my program by inserting _ = &x at the top of the function. That records x as having its address taken, at which point SSA is no longer comfortable assuming that x1 can be reloaded from x later in the function nor that the store of nil can be elided, and then all the "right" things start happening. I will do that, taking this off any kind of critical path and giving us a pattern to suggest to others who run into this. |
I've been working on timely reclamation of some large buffers in a local program using Go 1.6. One source of retention is function arguments (including receiver). We've marked the arguments as permanently live, which means that for the purposes of garbage collection it is important to be able to nil them out and have that take effect. This works fine in Go 1.6, but it looks like the SSA backend does not flush back writes to arguments that SSA believes are dead. For the current liveness analysis, they're never dead, at least in the sense that the GC looks at them, so they should always be flushed back.
Consider:
@dr2chase mentioned that perhaps local variables can get flushed into "dead" argument slots too. If that's true, that needs to be fixed too. It might be fixed automatically by making SSA understand args are always live, but if not it needs a separate fix. Because the args are always live, it's especially important not to put local variables there. Local variables are how programmers can switch to something with more limited liveness, as in:
It would be bad here if x1 simply got allocated to x's slot and ended up pinned live.
There is a separate question of whether the args should be permanently live, but that ship may have sailed. Changing that would get into issues about finalizers and early reclamation that we've basically avoided by having a mechanism for something that stays live even though the compiler can't quite see why. Abandoning that would require a lot of testing and care. For Go 1.7 we just need to make sure that SSA understands the historical liveness rules and lets programmers keep programming against them.
Here is a variant of the above program suitable for testing:
/cc @randall77 @dr2chase
The text was updated successfully, but these errors were encountered: