-
-
Notifications
You must be signed in to change notification settings - Fork 922
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
Local variable not updated in closure state #5439
Comments
This does not appear to be affected by Java integration; if I make |
This is definitely some oddity with IR. If I do any of the following, the error goes away:
Something about the ensure is causing the write to I think it may be eliminating the intermediate write to @subbuss @enebo When are heap variables written back to the heap? I know we do it around closure boundaries, since they need to see the most current state. In this case, after the update Here's the IR for the broken version, with the main body and fall-through ensure:
Note there's no If I add a puts to the ensure, it changes:
Looking at this I may have a theory: because there's no call immediately before |
Trivial reproduction that seems to support my theory: var = 0
pr = ->(){p var}
1.times do
var = 1
pr.call
ensure
var = 2
end
# => 0, not 2 because the ensure does not actually fire before pr.call |
This appears to be a regression. JRuby 9.2 properly updates var here. |
This is what I see locally with master
|
It has been so long that I no longer remember how to run specific passes :-) .. but, I found that ast --ir still runs and looks like Dead Code Elimination kills the "var = 1" assignment which means information about escaped variables has somehow been lost between 9.2 and master .. could be because of twiddling of flags on operations / instructions, etc .. for example. |
Reverting ae777f0 seems to fix it. |
I probably won't have time on plane to look at this but that removed irruntimehelper instr was enough magic/barrier in front of var to not eliminate it in DCE. I feel like something has been broken with lva for a long time but the likelihood of var being first before call or other unknown instr which is call or side effect. |
It is like lva is correct that the local scope can see var is not used but does not know other scopes have access as the call right after var = 1 should be enough to know someone may see var with that value |
yay for plane wifi.... Not a solution but an explanation why the revert @subbuss did worked. When RESTORE_EXCEPTION instr is added the BB that contains it gains an addition exception edge to the ensure which bypasses the fallthrough BB which contains the var = 2. Because it can jump past this BB it cannot eliminate the first var assignment. I feel a bit more confident that there is a generic issue with LVA which does not seem to know the variable is potentially being read from another scope (which would make any call or sideeffect invalidate marking the assignment dead). I may even be able to show this is broken without an ensure is present. |
The more I read this the more I don't understand how if we are in a closure and we see any call that we cannot just require all non-local-scope lvars as unconditionally alive. in LiveVariableNode.applyTransferFunction we do explicitly look for variables leaving in a call/closureaccepting and then add them to living if they are explicitly passed to the call but since we don't know what the actual call is we don't really know if that method already has access to any of these variables. |
@enebo I think we had this debate before, weighing every write going to heap versus some tricky logic making sure var updates are visible on heap at the right time (like on call boundaries etc). It might not be too hard to measure how many reads/writes we eliminate in reality, since everything is a call (i.e. maybe we don't gain much versus the complexity). |
I don't think you need to be that pessimistic about it. This is probably missing some check specific to when the scope being analyzed is a closure. For the method scope case, if there is a closure, the analysis conservatively assumes all things being live. I'll take a look at the code.
Once you start inlining, this will begin to matter more. |
@subbuss we are in a closure and it definitely assumes all variables are live on exit (which we pessimistically assume). The issue is within that closure we have no information on how those variables which are defined outside the closure are accessed. In the case of this bug a second closure is executed and it happens to ask for the value. If we knew no other closure accessed the variable them perhaps it is safe for us to remove var = 1. Perhaps the method itself using those variables are not important? |
There is this code in LVA which indicates that 'var' should have been marked live before the call. So, the qn. is why didn't that if check succeed (assuming that is what is failing).
|
I missed this earlier, but the IR output shows: |
This is an interesting bug. :-) So the dynscope elimination is the right thing since it doesn't have its own scope hasn't escaped and is not needed. So, getScopeDepth() can be zero and refer to outer scope vars if dynscope has been eliminated .. so, we could add an additional condition there instead of just checking scope depth. Alternatively we could suppress elimination of dynscope if any of the ancestor scopes has escaped (as in this example), but I don't think that makes sense. So, I am leaning to abstracting that check to ... ((LocalVariable)x).isOuterScopeVariable(scope). |
Ha! There is already the 'isDefinedLocally' flag in ClosureLocalVariable which does exactly this (added in d8e4959) and used in Load/Store passes. I just added something in LocalVariable instead ... I'll consolidate the two and replace all checks of getScopeDepth() > 0 or getScopeDepth() == 0 with the new method. All those other depth > 0 and depth = 0 checks are also problematic! Commit coming later tonight. |
* There already was definedLocally boolean in ClosureLocalVariable but I renamed it to isOuterScopeVar and moved it to LocalVariable. We don't consistently use ClosureLocalVariable when the local variable use is in a closure. So, moving it to LocalVariable ensures that we catch all uses independent of whether we build a LocalVariable or a ClosureLocalVariable. Separately, investigate why we did make this distincting between these two variables and if there is any code that critically depends on this distinction because as I found in this patch, we cannot simply assume lvar uses in closures will use a ClosureLocalVariable instance. * Used isOuterScopeVar() method instead of the buggy comparison of a var's scope depth with 0 multiple places in the codebase. Replaced isDefinedLocally() with !isOuterScopeVar() wherever it was used.
@enebo Maybe check whether this patch also fixes your inliner woes. |
I didn't run tests because I don't know / remember how to run tests anymore, but hopefully Travis will call me out if I broke something. In addition, we should definitely add new regression tests. Can one of you do that? I will need to page in how to do that since it has been a while and I cannot do that till much later in the week. |
@subbu_ss I'll take care of tests. Thank you for the fix! |
This does not appear to be completely solved. Running with JRuby master, 7b14404, the following script still fails to update def foo
var = 0
pr = ->(){@var = var}
1.times do
var = 1
pr.call
ensure
var = 2
end
end
foo
p @var Under JIT, this outputs "0", while interpreted it outputs "1". The IR shows that
|
Here is the spec I wrote: describe "A value written to a variable read by a proc" do
it "is visible to that proc" do
o = Object.new
def o.foo
var = 0
pr = ->(){@var = var}
1.times do
var = 1
pr.call
ensure
var = 2
end
end
o.foo
expect(o.instance_variable_get(:@var)).to eq(1)
end
end |
False alarm...my local env got switched to 9.2.1.0 somehow. |
Environment
jruby 9.2.3.0 (2.5.0) 2018-11-09 5833e2d Java HotSpot(TM) 64-Bit Server VM 25.191-b12 on 1.8.0_191-b12 +jit [mswin32-x86_64]
Expected Behavior
test.rb
Actual Behavior
The text was updated successfully, but these errors were encountered: