Skip to content

Commit 7875a92

Browse files
committed
[GR-17173] [GR-17194] Fix ReadCallerFrameNode to still optimize for multiple callers and fix $~ in grep and grep_v.
PullRequest: truffleruby/955
2 parents 99fb4a4 + 90c3685 commit 7875a92

File tree

8 files changed

+161
-50
lines changed

8 files changed

+161
-50
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Bug fixes:
1111
* Allow `Time#strftime` to be called with binary format strings.
1212
* Do not modify the argument passed to `IO#write` when the encoding does not match (#1714).
1313
* Use the class where the method was defined to check if an `UnboundMethod` can be used for `#define_method` (#1710).
14+
* Fixed setting `$~` for `Enumerable` and `Enumerator::Lazy`'s `#grep` and `#grep_v`.
1415

1516
Compatibility:
1617

spec/ruby/core/enumerable/grep_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@ class EnumerableSpecGrep2; def ===(obj); /^ca/ =~ obj; end; end
2929
ary.grep(/a(b)a/) { $1 }.should == ["b", "b"]
3030
end
3131

32+
it "sets $~ in the block" do
33+
"z" =~ /z/ # Reset $~
34+
["abc", "def"].grep(/b/) { |e|
35+
e.should == "abc"
36+
$&.should == "b"
37+
}
38+
39+
# Set by the failed match of "def"
40+
$~.should == nil
41+
end
42+
43+
it "sets $~ to the last match when given no block" do
44+
"z" =~ /z/ # Reset $~
45+
["abc", "def"].grep(/b/).should == ["abc"]
46+
47+
# Set by the failed match of "def"
48+
$~.should == nil
49+
50+
["abc", "def"].grep(/e/)
51+
$&.should == "e"
52+
end
53+
3254
describe "with a block" do
3355
before :each do
3456
@numerous = EnumerableSpecs::Numerous.new(*(0..9).to_a)

spec/ruby/core/enumerable/grep_v_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@ def (@odd_matcher = BasicObject.new).===(obj)
99
end
1010
end
1111

12+
it "sets $~ in the block" do
13+
"z" =~ /z/ # Reset $~
14+
["abc", "def"].grep_v(/e/) { |e|
15+
e.should == "abc"
16+
$~.should == nil
17+
}
18+
19+
# Set by the match of "def"
20+
$&.should == "e"
21+
end
22+
23+
it "sets $~ to the last match when given no block" do
24+
"z" =~ /z/ # Reset $~
25+
["abc", "def"].grep_v(/e/).should == ["abc"]
26+
27+
# Set by the match of "def"
28+
$&.should == "e"
29+
30+
["abc", "def"].grep_v(/b/)
31+
$&.should == nil
32+
end
33+
1234
describe "without block" do
1335
it "returns an Array of matched elements" do
1436
@numerous.grep_v(@odd_matcher).should == [0, 2, 4, 6, 8]

spec/ruby/core/enumerator/lazy/grep_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,39 @@
3333
Enumerator::Lazy.new(Object.new, 100) {}.grep(Object).size.should == nil
3434
end
3535

36+
it "sets $~ in the block" do
37+
"z" =~ /z/ # Reset $~
38+
["abc", "def"].lazy.grep(/b/) { |e|
39+
e.should == "abc"
40+
$&.should == "b"
41+
}.force
42+
43+
# Set by the failed match of "def"
44+
$~.should == nil
45+
end
46+
47+
it "sets $~ in the next block with each" do
48+
"z" =~ /z/ # Reset $~
49+
["abc", "def"].lazy.grep(/b/).each { |e|
50+
e.should == "abc"
51+
$&.should == "b"
52+
}
53+
54+
# Set by the failed match of "def"
55+
$~.should == nil
56+
end
57+
58+
it "sets $~ in the next block with map" do
59+
"z" =~ /z/ # Reset $~
60+
["abc", "def"].lazy.grep(/b/).map { |e|
61+
e.should == "abc"
62+
$&.should == "b"
63+
}.force
64+
65+
# Set by the failed match of "def"
66+
$~.should == nil
67+
end
68+
3669
describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
3770
it "stops after specified times when not given a block" do
3871
(0..Float::INFINITY).lazy.grep(Integer).first(3).should == [0, 1, 2]

spec/ruby/core/enumerator/lazy/grep_v_spec.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,39 @@
3131
Enumerator::Lazy.new(Object.new, 100) {}.grep_v(Object).size.should == nil
3232
end
3333

34+
it "sets $~ in the block" do
35+
"z" =~ /z/ # Reset $~
36+
["abc", "def"].lazy.grep_v(/e/) { |e|
37+
e.should == "abc"
38+
$~.should == nil
39+
}.force
40+
41+
# Set by the match of "def"
42+
$&.should == "e"
43+
end
44+
45+
it "sets $~ in the next block with each" do
46+
"z" =~ /z/ # Reset $~
47+
["abc", "def"].lazy.grep_v(/e/).each { |e|
48+
e.should == "abc"
49+
$~.should == nil
50+
}
51+
52+
# Set by the match of "def"
53+
$&.should == "e"
54+
end
55+
56+
it "sets $~ in the next block with map" do
57+
"z" =~ /z/ # Reset $~
58+
["abc", "def"].lazy.grep_v(/e/).map { |e|
59+
e.should == "abc"
60+
$~.should == nil
61+
}.force
62+
63+
# Set by the match of "def"
64+
$&.should == "e"
65+
end
66+
3467
describe "when the returned lazy enumerator is evaluated by Enumerable#first" do
3568
it "stops after specified times when not given a block" do
3669
(0..Float::INFINITY).lazy.grep_v(3..5).first(3).should == [0, 1, 2]

src/main/java/org/truffleruby/language/arguments/ReadCallerFrameNode.java

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,43 +25,49 @@
2525
public class ReadCallerFrameNode extends RubyBaseNode {
2626

2727
private final ConditionProfile callerFrameProfile = ConditionProfile.createBinaryProfile();
28-
@CompilationFinal private volatile boolean firstCall = true;
28+
@CompilationFinal private volatile boolean deoptWhenNotPassedCallerFrame = true;
2929

3030
public static ReadCallerFrameNode create() {
3131
return new ReadCallerFrameNode();
3232
}
3333

3434
public MaterializedFrame execute(VirtualFrame frame) {
35-
// Avoid polluting the profile for the first call which has to use getCallerFrame()
36-
if (firstCall) {
37-
CompilerDirectives.transferToInterpreterAndInvalidate();
38-
firstCall = false;
39-
notifyCallerToSendFrame();
40-
return getCallerFrame();
41-
}
42-
4335
final MaterializedFrame callerFrame = RubyArguments.getCallerFrame(frame);
4436

4537
if (callerFrameProfile.profile(callerFrame != null)) {
4638
return callerFrame;
4739
} else {
40+
// Every time the caller of the method using ReadCallerFrameNode changes,
41+
// we need to notify the caller's CachedDispatchNode to pass us the frame next time.
42+
if (deoptWhenNotPassedCallerFrame) {
43+
// Invalidate because deoptWhenNotPassedCallerFrame might change and require recompilation
44+
CompilerDirectives.transferToInterpreterAndInvalidate();
45+
}
4846
return getCallerFrame();
4947
}
5048
}
5149

52-
private void notifyCallerToSendFrame() {
50+
@TruffleBoundary
51+
private MaterializedFrame getCallerFrame() {
52+
if (!notifyCallerToSendFrame()) {
53+
// If we fail to notify the call node (e.g., because it is a UncachedDispatchNode which is not handled yet),
54+
// we don't want to deoptimize this CallTarget on every call.
55+
deoptWhenNotPassedCallerFrame = false;
56+
}
57+
return getContext().getCallStack().getCallerFrameIgnoringSend().getFrame(FrameInstance.FrameAccess.MATERIALIZE).materialize();
58+
}
59+
60+
private boolean notifyCallerToSendFrame() {
5361
final Node callerNode = getContext().getCallStack().getCallerNode(0, false);
5462
if (callerNode instanceof DirectCallNode) {
5563
final Node parent = callerNode.getParent();
5664
if (parent instanceof CachedDispatchNode) {
5765
((CachedDispatchNode) parent).startSendingOwnFrame();
66+
return true;
5867
}
5968
}
60-
}
6169

62-
@TruffleBoundary
63-
private MaterializedFrame getCallerFrame() {
64-
return getContext().getCallStack().getCallerFrameIgnoringSend().getFrame(FrameInstance.FrameAccess.MATERIALIZE).materialize();
70+
return false;
6571
}
6672

6773
}

src/main/ruby/truffleruby/core/enumerable.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,9 @@ def grep(pattern, &block)
357357
if block_given?
358358
each do
359359
o = Truffle.single_block_arg
360-
if pattern === o
361-
Truffle::RegexpOperations.set_last_match($~, block.binding)
360+
matches = pattern === o
361+
Truffle::RegexpOperations.set_last_match($~, block.binding)
362+
if matches
362363
ary << yield(o)
363364
end
364365
end
@@ -376,25 +377,27 @@ def grep(pattern, &block)
376377
ary
377378
end
378379

379-
def grep_v(pattern)
380+
def grep_v(pattern, &block)
380381
ary = []
381382

382383
if block_given?
383384
each do
384385
o = Truffle.single_block_arg
385-
unless pattern === o
386-
# Regexp.set_block_last_match # TODO BJF Aug 1, 2016 Investigate for removal
386+
matches = pattern === o
387+
Truffle::RegexpOperations.set_last_match($~, block.binding)
388+
unless matches
387389
ary << yield(o)
388390
end
389391
end
390392
else
391393
each do
392394
o = Truffle.single_block_arg
393395
unless pattern === o
394-
# Regexp.set_block_last_match # TODO BJF Aug 1, 2016 Investigate for removal
395396
ary << o
396397
end
397398
end
399+
400+
Truffle::RegexpOperations.set_last_match($~, Truffle.invoke_primitive(:caller_binding))
398401
end
399402

400403
ary

src/main/ruby/truffleruby/core/enumerator.rb

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -391,44 +391,35 @@ def reject
391391
end
392392

393393
def grep(pattern, &block)
394-
if block_given?
395-
Lazy.new(self, nil) do |yielder, *args|
396-
val = args.length >= 2 ? args : args.first
397-
if pattern === val
398-
Truffle::RegexpOperations.set_last_match($~, block.binding)
394+
binding = block ? block.binding : Truffle.invoke_primitive(:caller_binding)
395+
396+
Lazy.new(self, nil) do |yielder, *args|
397+
val = args.length >= 2 ? args : args.first
398+
matches = pattern === val
399+
Truffle::RegexpOperations.set_last_match($~, binding)
400+
401+
if matches
402+
if block
399403
yielder.yield yield(val)
400-
end
401-
end
402-
else
403-
lazy = Lazy.new(self, nil) do |yielder, *args|
404-
val = args.length >= 2 ? args : args.first
405-
if pattern === val
404+
else
406405
yielder.yield val
407406
end
408-
409-
Truffle::RegexpOperations.set_last_match($~, Truffle.invoke_primitive(:caller_binding))
410407
end
411-
412-
Truffle::RegexpOperations.set_last_match($~, Truffle.invoke_primitive(:caller_binding))
413-
414-
lazy
415408
end
416409
end
417410

418-
def grep_v(pattern)
419-
if block_given?
420-
Lazy.new(self, nil) do |yielder, *args|
421-
val = args.length >= 2 ? args : args.first
422-
unless pattern === val
423-
# Regexp.set_block_last_match # TODO BJF Aug 2, 2016 Investigate for removal
411+
def grep_v(pattern, &block)
412+
binding = block ? block.binding : Truffle.invoke_primitive(:caller_binding)
413+
414+
Lazy.new(self, nil) do |yielder, *args|
415+
val = args.length >= 2 ? args : args.first
416+
matches = pattern === val
417+
Truffle::RegexpOperations.set_last_match($~, binding)
418+
419+
unless matches
420+
if block
424421
yielder.yield yield(val)
425-
end
426-
end
427-
else
428-
Lazy.new(self, nil) do |yielder, *args|
429-
val = args.length >= 2 ? args : args.first
430-
unless pattern === val
431-
# Regexp.set_block_last_match # TODO BJF Aug 2, 2016 Investigate for removal
422+
else
432423
yielder.yield val
433424
end
434425
end

0 commit comments

Comments
 (0)