Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: a9b54928be63
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 9a1ce58570f5
Choose a head ref
  • 2 commits
  • 10 files changed
  • 1 contributor

Commits on Dec 13, 2016

  1. Simplify Kernel#lambda spec

    eregon committed Dec 13, 2016
    Copy the full SHA
    9ba1ffd View commit details
  2. [Truffle] Fix semantics of Kernel#lambda.

    * If the passed block is a literal block, then it creates a lambda-style Proc.
    * If an existing Proc is passed, it just passes through,
      regardless of whether it is lambda-style or proc-style.
    * This implementation takes advantage of cloning/splitting when running on Graal,
      and otherwise looks up the call node to see if it has a literal block argument.
    eregon committed Dec 13, 2016
    Copy the full SHA
    9a1ce58 View commit details
8 changes: 4 additions & 4 deletions spec/ruby/core/kernel/fixtures/classes.rb
Original file line number Diff line number Diff line change
@@ -323,16 +323,16 @@ class Grandchild < Child

# for testing lambda
class Lambda
def outer(meth)
inner(meth)
def outer
inner
end

def mp(&b); b; end

def inner(meth)
def inner
b = mp { return :good }

pr = send(meth) { |x| x.call }
pr = lambda { |x| x.call }

pr.call(b)

4 changes: 2 additions & 2 deletions spec/ruby/core/kernel/lambda_spec.rb
Original file line number Diff line number Diff line change
@@ -72,15 +72,15 @@
it "returns from the lambda itself, not the creation site of the lambda" do
@reached_end_of_method = nil
def test
send(@method) { return }.call
send(:lambda) { return }.call
@reached_end_of_method = true
end
test
@reached_end_of_method.should be_true
end

it "allows long returns to flow through it" do
KernelSpecs::Lambda.new.outer(@method).should == :good
KernelSpecs::Lambda.new.outer.should == :good
end
end

1 change: 0 additions & 1 deletion spec/truffle/tags/core/kernel/lambda_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:Kernel.lambda allows long returns to flow through it
fails:Kernel.lambda returned the passed Proc if given an existing Proc
8 changes: 8 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/CoreLibrary.java
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleOptions;
@@ -287,6 +288,7 @@ public class CoreLibrary {

private final Map<Errno, DynamicObject> errnoClasses = new HashMap<>();

@CompilationFinal private boolean cloningEnabled;
@CompilationFinal private InternalMethod basicObjectSendMethod;
@CompilationFinal private InternalMethod truffleBootMainMethod;

@@ -820,6 +822,8 @@ public void addCoreMethods(PrimitiveManager primitiveManager) {

basicObjectSendMethod = getMethod(basicObjectClass, "__send__");
truffleBootMainMethod = getMethod(node.getSingletonClass(truffleBootModule), "main");

cloningEnabled = Truffle.getRuntime().createDirectCallNode(getMethod(kernelModule, "lambda").getCallTarget()).isCallTargetCloningAllowed();
}

private InternalMethod getMethod(DynamicObject module, String name) {
@@ -1533,6 +1537,10 @@ public boolean isTruffleBootMainMethod(NamedSharedMethodInfo info) {
return info == truffleBootMainMethod.getNamedSharedMethodInfo();
}

public boolean isCloningEnabled() {
return cloningEnabled;
}

public DynamicObjectFactory getIntRangeFactory() {
return intRangeFactory;
}
Original file line number Diff line number Diff line change
@@ -24,6 +24,8 @@ public ProcOrNullNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public abstract RubyNode getChild();

public abstract DynamicObject executeProcOrNull(Object proc);

@Specialization
Original file line number Diff line number Diff line change
@@ -105,8 +105,10 @@
import org.jruby.truffle.language.dispatch.DispatchAction;
import org.jruby.truffle.language.dispatch.DispatchHeadNode;
import org.jruby.truffle.language.dispatch.DispatchHeadNodeFactory;
import org.jruby.truffle.language.dispatch.DispatchNode;
import org.jruby.truffle.language.dispatch.DoesRespondDispatchHeadNode;
import org.jruby.truffle.language.dispatch.MissingBehavior;
import org.jruby.truffle.language.dispatch.RubyCallNode;
import org.jruby.truffle.language.loader.CodeLoader;
import org.jruby.truffle.language.loader.RequireNode;
import org.jruby.truffle.language.loader.SourceLoader;
@@ -1169,28 +1171,69 @@ public abstract static class LambdaNode extends CoreMethodArrayArgumentsNode {
@TruffleBoundary
@Specialization
public DynamicObject lambda(NotProvided block) {
final Frame parentFrame = getContext().getCallStack().getCallerFrameIgnoringSend().getFrame(FrameAccess.READ_ONLY, true);
final Frame parentFrame = getContext().getCallStack().getCallerFrameIgnoringSend(0).getFrame(FrameAccess.READ_ONLY, true);
final DynamicObject parentBlock = RubyArguments.getBlock(parentFrame);

if (parentBlock == null) {
throw new RaiseException(coreExceptions().argumentError("tried to create Proc object without a block", this));
}

return lambda(parentBlock);
Node callNode = getContext().getCallStack().getCallerFrameIgnoringSend(1).getCallNode();
if (isLiteralBlock(callNode)) {
return lambdaFromBlock(parentBlock);
} else {
return parentBlock;
}
}

@Specialization
public DynamicObject lambda(DynamicObject block) {
@Specialization(guards = {"isCloningEnabled()", "isLiteralBlock"})
public DynamicObject lambdaFromBlockCloning(DynamicObject block,
@Cached("isLiteralBlock(block)") boolean isLiteralBlock) {
return lambdaFromBlock(block);
}

@Specialization(guards = {"isCloningEnabled()", "!isLiteralBlock"})
public DynamicObject lambdaFromExistingProcCloning(DynamicObject block,
@Cached("isLiteralBlock(block)") boolean isLiteralBlock) {
return block;
}

@Specialization(guards = "isLiteralBlock(block)")
public DynamicObject lambdaFromBlock(DynamicObject block) {
return ProcOperations.createRubyProc(
coreLibrary().getProcFactory(),
ProcType.LAMBDA,
Layouts.PROC.getNamedSharedMethodInfo(block),
Layouts.PROC.getCallTargetForLambdas(block),
Layouts.PROC.getCallTargetForLambdas(block),
Layouts.PROC.getDeclarationFrame(block),
Layouts.PROC.getMethod(block),
Layouts.PROC.getSelf(block),
Layouts.PROC.getBlock(block));
coreLibrary().getProcFactory(),
ProcType.LAMBDA,
Layouts.PROC.getNamedSharedMethodInfo(block),
Layouts.PROC.getCallTargetForLambdas(block),
Layouts.PROC.getCallTargetForLambdas(block),
Layouts.PROC.getDeclarationFrame(block),
Layouts.PROC.getMethod(block),
Layouts.PROC.getSelf(block),
Layouts.PROC.getBlock(block));
}

@Specialization(guards = "!isLiteralBlock(block)")
public DynamicObject lambdaFromExistingProc(DynamicObject block) {
return block;
}

protected boolean isLiteralBlock(DynamicObject block) {
Node callNode = getContext().getCallStack().getCallerFrameIgnoringSend().getCallNode();
return isLiteralBlock(callNode);
}

private boolean isLiteralBlock(Node callNode) {
if (callNode.getParent() instanceof DispatchNode) {
RubyCallNode rubyCallNode = ((DispatchNode) callNode.getParent()).findRubyCallNode();
if (rubyCallNode != null) {
return rubyCallNode.hasLiteralBlock();
}
}
return false;
}

protected boolean isCloningEnabled() {
return coreLibrary().isCloningEnabled();
}
}

Original file line number Diff line number Diff line change
@@ -43,36 +43,49 @@ public CallStackManager(RubyContext context) {

private static final Object STOP_ITERATING = new Object();

@TruffleBoundary
public FrameInstance getCallerFrameIgnoringSend() {
FrameInstance callerFrame = Truffle.getRuntime().getCallerFrame();
if (callerFrame == null) {
return null;
}
InternalMethod method = getMethod(callerFrame);
return getCallerFrameIgnoringSend(0);
}

if (method == null) { // Not a Ruby frame
return null;
} else if (!context.getCoreLibrary().isSend(method)) {
return callerFrame;
@TruffleBoundary
public FrameInstance getCallerFrameIgnoringSend(int skip) {
// Try first using getCallerFrame() as it's the common case
if (skip == 0) {
FrameInstance callerFrame = Truffle.getRuntime().getCallerFrame();
if (callerFrame == null) {
return null;
}
InternalMethod method = getMethod(callerFrame);

if (method == null) { // Not a Ruby frame
return null;
} else if (!context.getCoreLibrary().isSend(method)) {
return callerFrame;
}
}

// Need to iterate further
final Object frame = Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Object>() {
int depth = 0;
int skipped = 0;

@Override
public Object visitFrame(FrameInstance frameInstance) {
depth++;
if (depth < 2) {
if (depth == 1) { // Skip top frame
return null;
}

InternalMethod method = getMethod(frameInstance);
if (method == null) {
return STOP_ITERATING;
} else if (!context.getCoreLibrary().isSend(method)) {
return frameInstance;
if (skipped >= skip) {
return frameInstance;
} else {
skipped++;
return null;
}
} else {
return null;
}
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
package org.jruby.truffle.language.dispatch;

import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.object.DynamicObject;
import org.jruby.truffle.RubyContext;
@@ -75,6 +76,16 @@ protected DispatchHeadNode getHeadNode() {
return NodeUtil.findParent(this, DispatchHeadNode.class);
}

public RubyCallNode findRubyCallNode() {
DispatchHeadNode headNode = getHeadNode();
Node parent = headNode.getParent();
if (parent instanceof RubyCallNode) {
return (RubyCallNode) parent;
} else {
return null;
}
}

@Override
public final Object execute(VirtualFrame frame) {
throw new IllegalStateException("do not call execute on dispatch nodes");
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
import org.jruby.truffle.core.module.ModuleOperations;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.arguments.RubyArguments;
import org.jruby.truffle.language.methods.BlockDefinitionNode;
import org.jruby.truffle.language.methods.InternalMethod;

public class RubyCallNode extends RubyNode {
@@ -201,4 +202,9 @@ public boolean isVCall() {
return isVCall;
}

public boolean hasLiteralBlock() {
assert block != null;
return block.getChild() instanceof BlockDefinitionNode;
}

}
Original file line number Diff line number Diff line change
@@ -24,9 +24,8 @@
import org.jruby.truffle.language.locals.ReadFrameSlotNodeGen;

/**
* Create a RubyProc to pass as a block to the called method.
* The literal block is represented as call targets and a SharedMethodInfo.
* This is executed at the call site just before dispatch.
* Create a Ruby Proc to pass as a block to the called method. The literal block is represented as
* call targets and a SharedMethodInfo. This is executed at the call site just before dispatch.
*/
public class BlockDefinitionNode extends RubyNode {