Skip to content

Commit

Permalink
Showing 6 changed files with 241 additions and 152 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.nodes.supercall;

import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.dispatch.DispatchNode;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.methods.InternalMethod;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.source.SourceSection;

@NodeChildren({
@NodeChild("method"),
@NodeChild(value = "arguments", type = RubyNode[].class)
})
public abstract class CallMethodNode extends RubyNode {

public static int getCacheLimit() {
return DispatchNode.DISPATCH_POLYMORPHIC_MAX;
}

public CallMethodNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public abstract Object executeCallMethod(VirtualFrame frame, InternalMethod method, Object[] arguments);

@Specialization(
guards = "method == cachedMethod",
// TODO(eregon, 12 June 2015) we should maybe check an Assumption here to remove the cache entry when the lookup changes (redefined method, hierarchy changes)
limit = "getCacheLimit()")
protected Object callMethodCached(VirtualFrame frame, InternalMethod method, Object[] arguments,
@Cached("method") InternalMethod cachedMethod,
@Cached("create(cachedMethod.getCallTarget())") DirectCallNode callNode) {
return callNode.call(frame, arguments);
}

@Specialization
protected Object callMethodUncached(VirtualFrame frame, InternalMethod method, Object[] arguments,
@Cached("create()") IndirectCallNode indirectCallNode) {
return indirectCallNode.call(frame, method.getCallTarget(), arguments);
}

}
Original file line number Diff line number Diff line change
@@ -14,54 +14,58 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;

import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.core.array.ArrayNodes;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyProc;
import org.jruby.truffle.runtime.methods.InternalMethod;

/**
* Represents a super call - that is a call with self as the receiver, but the superclass of self
* used for lookup. Currently implemented without any caching, and needs to be replaced with the
* same caching mechanism as for normal calls without complicating the existing calls too much.
* Represents a super call with explicit arguments.
*/
public class GeneralSuperCallNode extends AbstractGeneralSuperCallNode {
public class GeneralSuperCallNode extends RubyNode {

private final boolean isSplatted;

@Child private RubyNode block;
@Children private final RubyNode[] arguments;

@Child LookupSuperMethodNode lookupSuperMethodNode;
@Child CallMethodNode callMethodNode;

public GeneralSuperCallNode(RubyContext context, SourceSection sourceSection, RubyNode block, RubyNode[] arguments, boolean isSplatted) {
super(context, sourceSection);
assert arguments != null;
assert !isSplatted || arguments.length == 1;
this.block = block;
this.arguments = arguments;
this.isSplatted = isSplatted;

lookupSuperMethodNode = LookupSuperMethodNodeGen.create(context, sourceSection, null);
callMethodNode = CallMethodNodeGen.create(context, sourceSection, null, new RubyNode[] {});
}

@ExplodeLoop
@Override
public final Object execute(VirtualFrame frame) {
CompilerAsserts.compilationConstant(arguments.length);

final Object self = RubyArguments.getSelf(frame.getArguments());

// Execute the arguments

final Object[] argumentsObjects = new Object[arguments.length];

CompilerAsserts.compilationConstant(arguments.length);
for (int i = 0; i < arguments.length; i++) {
argumentsObjects[i] = arguments[i].execute(frame);
}

// Execute the block

RubyProc blockObject;

final RubyProc blockObject;
if (block != null) {
final Object blockTempObject = block.execute(frame);

if (blockTempObject == nil()) {
blockObject = null;
} else {
@@ -71,23 +75,37 @@ public final Object execute(VirtualFrame frame) {
blockObject = null;
}

// Check we have a method and the module is unmodified
final Object[] argumentsArray;
if (isSplatted) {
// TODO(CS): need something better to splat the arguments array
argumentsArray = ArrayNodes.slowToArray((RubyArray) argumentsObjects[0]);
} else {
argumentsArray = argumentsObjects;
}

if (!guard(frame, self)) {
CompilerDirectives.transferToInterpreterAndInvalidate();
lookup(frame);
final InternalMethod superMethod = lookupSuperMethodNode.executeLookupSuperMethod(frame, self);

if (superMethod == null) {
CompilerDirectives.transferToInterpreter();
final String name = RubyArguments.getMethod(frame.getArguments()).getSharedMethodInfo().getName(); // use the original name
throw new RaiseException(getContext().getCoreLibrary().noMethodError(String.format("super: no superclass method `%s'", name), name, this));
}

// Call the method
final Object[] frameArguments = RubyArguments.pack(superMethod, superMethod.getDeclarationFrame(), self, blockObject, argumentsArray);

if (isSplatted) {
// TODO(CS): need something better to splat the arguments array
final RubyArray argumentsArray = (RubyArray) argumentsObjects[0];
return callNode.call(frame, RubyArguments.pack(superMethod, superMethod.getDeclarationFrame(), self, blockObject, ArrayNodes.slowToArray(argumentsArray)));
return callMethodNode.executeCallMethod(frame, superMethod, frameArguments);
}

@Override
public Object isDefined(VirtualFrame frame) {
final Object self = RubyArguments.getSelf(frame.getArguments());
final InternalMethod superMethod = lookupSuperMethodNode.executeLookupSuperMethod(frame, self);

if (superMethod == null) {
return nil();
} else {
return callNode.call(frame, RubyArguments.pack(superMethod, superMethod.getDeclarationFrame(), self, blockObject, argumentsObjects));
return createString("super");
}
}


}
Original file line number Diff line number Diff line change
@@ -9,52 +9,61 @@
*/
package org.jruby.truffle.nodes.supercall;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.SourceSection;

import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.core.array.ArrayNodes;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyProc;
import org.jruby.truffle.runtime.methods.InternalMethod;

public class GeneralSuperReCallNode extends AbstractGeneralSuperCallNode {
/**
* Represents a super call with implicit arguments (using the ones of the surrounding methods).
*/
public class GeneralSuperReCallNode extends RubyNode {

private final boolean inBlock;
private final boolean isSplatted;
@Children private final RubyNode[] reloadNodes;
@Child private RubyNode block;

@Child LookupSuperMethodNode lookupSuperMethodNode;
@Child CallMethodNode callMethodNode;

public GeneralSuperReCallNode(RubyContext context, SourceSection sourceSection, boolean inBlock, boolean isSplatted, RubyNode[] reloadNodes, RubyNode block) {
super(context, sourceSection);
this.inBlock = inBlock;
this.isSplatted = isSplatted;
this.reloadNodes = reloadNodes;
this.block = block;

lookupSuperMethodNode = LookupSuperMethodNodeGen.create(context, sourceSection, null);
callMethodNode = CallMethodNodeGen.create(context, sourceSection, null, new RubyNode[] {});
}

@ExplodeLoop
@Override
public final Object execute(VirtualFrame frame) {
final Object self = RubyArguments.getSelf(frame.getArguments());
CompilerAsserts.compilationConstant(reloadNodes.length);

if (!guard(frame, self)) {
CompilerDirectives.transferToInterpreterAndInvalidate();
lookup(frame);
}
final Object self = RubyArguments.getSelf(frame.getArguments());

final Object[] originalArguments;

if (inBlock) {
originalArguments = RubyArguments.getDeclarationFrame(frame.getArguments()).getArguments();
} else {
originalArguments = frame.getArguments();
}

// Reload the arguments
Object[] superArguments = new Object[reloadNodes.length];

for (int n = 0; n < superArguments.length; n++) {
superArguments[n] = reloadNodes[n].execute(frame);
}
@@ -66,24 +75,47 @@ public final Object execute(VirtualFrame frame) {
superArguments = ArrayNodes.slowToArray(((RubyArray) superArguments[0]));
}

Object blockObject;

// Execute or inherit the block
final RubyProc blockObject;
if (block != null) {
blockObject = block.execute(frame);

if (blockObject == nil()) {
final Object blockTempObject = block.execute(frame);
if (blockTempObject == nil()) {
blockObject = null;
} else {
blockObject = (RubyProc) blockTempObject;
}
} else {
blockObject = RubyArguments.getBlock(originalArguments);
}

return callNode.call(frame, RubyArguments.pack(
final InternalMethod superMethod = lookupSuperMethodNode.executeLookupSuperMethod(frame, self);

if (superMethod == null) {
CompilerDirectives.transferToInterpreter();
final String name = RubyArguments.getMethod(frame.getArguments()).getSharedMethodInfo().getName(); // use the original name
throw new RaiseException(getContext().getCoreLibrary().noMethodError(String.format("super: no superclass method `%s'", name), name, this));
}

final Object[] frameArguments = RubyArguments.pack(
superMethod,
RubyArguments.getDeclarationFrame(originalArguments),
RubyArguments.getSelf(originalArguments),
(RubyProc) blockObject,
superArguments));
blockObject,
superArguments);

return callMethodNode.executeCallMethod(frame, superMethod, frameArguments);
}

@Override
public Object isDefined(VirtualFrame frame) {
final Object self = RubyArguments.getSelf(frame.getArguments());
final InternalMethod superMethod = lookupSuperMethodNode.executeLookupSuperMethod(frame, self);

if (superMethod == null) {
return nil();
} else {
return createString("super");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2015 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.nodes.supercall;

import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.dispatch.DispatchNode;
import org.jruby.truffle.nodes.objects.MetaClassNode;
import org.jruby.truffle.nodes.objects.MetaClassNodeGen;
import org.jruby.truffle.runtime.ModuleOperations;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyClass;
import org.jruby.truffle.runtime.methods.InternalMethod;

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;

@NodeChild("self")
public abstract class LookupSuperMethodNode extends RubyNode {

public static int getCacheLimit() {
return DispatchNode.DISPATCH_POLYMORPHIC_MAX;
}

@Child MetaClassNode metaClassNode;

public LookupSuperMethodNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
metaClassNode = MetaClassNodeGen.create(context, sourceSection, null);
}

public abstract InternalMethod executeLookupSuperMethod(VirtualFrame frame, Object self);

// The check for same metaClass is overly restrictive,
// but seems the be the only reasonable check in term of performance.
// The ideal condition would be to check if both ancestor lists starting at
// the current method's module are identical, which is non-trivial
// if the current method's module is an (included) module and not a class.

@Specialization(
guards = {
"getCurrentMethod(frame) == currentMethod",
"metaClass(frame, self) == selfMetaClass"
},
assumptions = "selfMetaClass.getUnmodifiedAssumption()", // guards against include/prepend/method redefinition
limit = "getCacheLimit()")
protected InternalMethod lookupSuperMethodCached(VirtualFrame frame, Object self,
@Cached("getCurrentMethod(frame)") InternalMethod currentMethod,
@Cached("metaClass(frame, self)") RubyClass selfMetaClass,
@Cached("doLookup(currentMethod, selfMetaClass)") InternalMethod superMethod) {
return superMethod;
}

@Specialization
protected InternalMethod lookupSuperMethodUncached(VirtualFrame frame, Object self) {
InternalMethod currentMethod = getCurrentMethod(frame);
RubyClass selfMetaClass = metaClass(frame, self);
return doLookup(currentMethod, selfMetaClass);
}

protected InternalMethod getCurrentMethod(VirtualFrame frame) {
return RubyArguments.getMethod(frame.getArguments());
}

protected RubyClass metaClass(VirtualFrame frame, Object object) {
return metaClassNode.executeMetaClass(frame, object);
}

protected InternalMethod doLookup(InternalMethod currentMethod, RubyClass selfMetaClass) {
InternalMethod superMethod = ModuleOperations.lookupSuperMethod(currentMethod, selfMetaClass);
// TODO (eregon, 12 June 2015): Is this correct?
if (superMethod != null && superMethod.isUndefined()) {
superMethod = null;
}
return superMethod;
}

}
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;

import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyClass;
import org.jruby.truffle.runtime.core.RubyModule;
@@ -270,6 +271,12 @@ public static InternalMethod lookupMethod(RubyModule module, String name) {
return null;
}

public static InternalMethod lookupSuperMethod(InternalMethod currentMethod, RubyClass objectMetaClass) {
String name = currentMethod.getSharedMethodInfo().getName(); // use the original name

return lookupSuperMethod(currentMethod.getDeclaringModule(), name, objectMetaClass);
}

@TruffleBoundary
public static InternalMethod lookupSuperMethod(RubyModule declaringModule, String name, RubyClass objectMetaClass) {
CompilerAsserts.neverPartOfCompilation();

2 comments on commit d78023f

@chrisseaton
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this - the super call nodes were ancient and pretty bad.

@eregon
Copy link
Member Author

@eregon eregon commented on d78023f Jun 12, 2015

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I figured it would be a nice way to approach normal call dispatch nodes at a smaller scale.
This is a bit different than Get/LookupConstantNode in that the composition is simply done with two calls in each node, like the RubyCall example in Graal.

Please sign in to comment.