History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: JRUBY-1712
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Major Major
Assignee: Charles Oliver Nutter
Reporter: Marcin Mielzynski
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
JRuby

Captures local to the block ?

Created: 12/Dec/07 09:00 PM   Updated: 04/Oct/08 11:50 AM
Component/s: Core Classes/Modules
Affects Version/s: None
Fix Version/s: JRuby 1.1.5

Time Tracking:
Not Specified

Testcase included: yes


 Description  « Hide
here is the test case:
block = lambda{ puts $1; "c" =~ /(c)/; p $1 }
"a".sub(/(a)/, &block)
p $1
block.call
p $1

MRI:
a
"c"
"a"
a
"c"
"c"

JRuby:
a
"c"
"a"
c
"c"
"a"

I might have screwed frame selection during last opts, but it looks that captures are local to block frame instead of current method scope, which would be another issue.



 All   Comments   Work Log   Change History      Sort Order: Ascending order - Click to sort in descending order
Charles Oliver Nutter - 12/Dec/07 10:59 PM
Drat...this is somewhat problematic. So I guess when I went out on a limb and made $~ and $_ local to the frame rather than to the scope I may not have been correct in doing so. The original reason for them moving to the frame was to allow optimizing the scope away entirely. Because there are some special methods that make modifications to $~ (String and Regexp methods, mostly) and $_ (IO-related methods, mostly) their presence meant that without detecting that such methods are being called, all method calls would have to have a full heap-based scope available for those values. Additionally, by having them in the scope all scopes were two values wider. Moving them to the frame simplified the implementation of scopes and made it possible use stack-based variables in many cases.

To fix this, should we choose to do so, we have a couple options:

(Regardless of the approach, we would have to put them back on the scope)

  • Disable scopeless optimization across the board. This would work, but would also slow down execution substantially for many methods (most of which don't need $_ and $~)
  • Disable scopeless optimization only when $_ or $~ are present. This would require that methods which set $~ or $_ gain added smarts to know when they "cant". It's also not foolproof, since you can eval "$~" and get a result, but eval has to turn off scopeless anyway.
  • Disable scopeless optimization when $~ or $_ are present or any of the methods that set them are likely to be called. This adds more keyword-like behavior to some methods, and would require we tag such methods as requiring a caller scope, but it would generally be reliable. The same exceptions as for eval deoptimization would apply: if e.g. gets were aliased, we would fail to notice it, and the aliasing could happen "too late" for us to deoptimize some methods that may have been AOT compiled. But we could disable aliasing those methods or something. We could also in JIT mode track at runtimewhether the actual methods being invoked are the ones we expect, which would catch aliased methods (but also require another check for future changes to deoptimize and start over).

The frustrating detail here is that Ruby isn't consistently handling $~ and $_ as though they're local variables, since if the variable has not been assigned in a given child scope, Ruby checks the parent scope. Once set in the child scope, the parent scope is never used again. We've never had that behavior as far as I remember, and it's in my opinion a rather ugly wart.


Charles Oliver Nutter - 12/May/08 01:45 PM
Revisiting this again...

The move of $~ and $_ to Frame isn't entirely wrong. What Ruby does is store it on the scope, but only on a "topmost" scope in a given scope's parent hierarchy. So a normal block will still share $~ slots with its containing method.

I think the effect we're seeing here is that lambda is not reaching the correct frame on which to make these updates, which now produces the output:

nil
"a"
"c"
nil
"a"
"c"

...because the match only gets stored in the lambda's cloned frame, not in the original containing frame. And sub is also not storing the result into the correct frame.


Marcin Mielzynski - 03/Oct/08 04:16 PM
Works now.