RubyCallNode.java
- /*
- * Copyright (c) 2013, 2014 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;
- 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 com.oracle.truffle.api.utilities.BranchProfile;
- import org.jruby.truffle.nodes.cast.BooleanCastNode;
- import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
- import org.jruby.truffle.nodes.cast.ProcOrNullNode;
- import org.jruby.truffle.nodes.cast.ProcOrNullNodeFactory;
- import org.jruby.truffle.nodes.dispatch.Dispatch;
- import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
- import org.jruby.truffle.runtime.ModuleOperations;
- import org.jruby.truffle.runtime.RubyArguments;
- import org.jruby.truffle.runtime.RubyContext;
- import org.jruby.truffle.runtime.core.RubyArray;
- import org.jruby.truffle.runtime.core.RubyProc;
- import org.jruby.truffle.runtime.methods.RubyMethod;
- import org.jruby.truffle.runtime.util.ArrayUtils;
- import java.util.Arrays;
- public class RubyCallNode extends RubyNode {
- private final String methodName;
- @Child protected RubyNode receiver;
- @Child protected ProcOrNullNode block;
- @Children protected final RubyNode[] arguments;
- private final boolean isSplatted;
- private final boolean isVCall;
- @Child protected DispatchHeadNode dispatchHead;
- private final BranchProfile splatNotArrayProfile = BranchProfile.create();
- @CompilerDirectives.CompilationFinal private boolean seenNullInUnsplat = false;
- @CompilerDirectives.CompilationFinal private boolean seenIntegerFixnumInUnsplat = false;
- @CompilerDirectives.CompilationFinal private boolean seenLongFixnumInUnsplat = false;
- @CompilerDirectives.CompilationFinal private boolean seenObjectInUnsplat = false;
- @Child protected DispatchHeadNode respondToMissing;
- @Child protected BooleanCastNode respondToMissingCast;
- private final boolean ignoreVisibility;
- public RubyCallNode(RubyContext context, SourceSection section, String methodName, RubyNode receiver, RubyNode block, boolean isSplatted, RubyNode... arguments) {
- this(context, section, methodName, receiver, block, isSplatted, false, false, arguments);
- }
- public RubyCallNode(RubyContext context, SourceSection section, String methodName, RubyNode receiver, RubyNode block, boolean isSplatted, boolean ignoreVisibility, boolean rubiniusPrimitive, RubyNode... arguments) {
- this(context, section, methodName, receiver, block, isSplatted, false, ignoreVisibility, rubiniusPrimitive, arguments);
- }
- public RubyCallNode(RubyContext context, SourceSection section, String methodName, RubyNode receiver, RubyNode block, boolean isSplatted, boolean isVCall, boolean ignoreVisibility, boolean rubiniusPrimitive, RubyNode... arguments) {
- super(context, section);
- this.methodName = methodName;
- this.receiver = receiver;
- if (block == null) {
- this.block = null;
- } else {
- this.block = ProcOrNullNodeFactory.create(context, section, block);
- }
- this.arguments = arguments;
- this.isSplatted = isSplatted;
- this.isVCall = isVCall;
- dispatchHead = new DispatchHeadNode(context, ignoreVisibility, false, Dispatch.MissingBehavior.CALL_METHOD_MISSING);
- respondToMissing = new DispatchHeadNode(context, true, Dispatch.MissingBehavior.RETURN_MISSING);
- respondToMissingCast = BooleanCastNodeFactory.create(context, section, null);
- this.ignoreVisibility = ignoreVisibility;
- }
- @Override
- public Object execute(VirtualFrame frame) {
- final Object receiverObject = receiver.execute(frame);
- final Object[] argumentsObjects = executeArguments(frame);
- final RubyProc blockObject = executeBlock(frame);
- return dispatchHead.call(frame, receiverObject, methodName, blockObject, argumentsObjects);
- }
- private RubyProc executeBlock(VirtualFrame frame) {
- if (block != null) {
- return block.executeRubyProc(frame);
- } else {
- return null;
- }
- }
- @ExplodeLoop
- private Object[] executeArguments(VirtualFrame frame) {
- final Object[] argumentsObjects = new Object[arguments.length];
- for (int i = 0; i < arguments.length; i++) {
- argumentsObjects[i] = arguments[i].execute(frame);
- }
- if (isSplatted) {
- return splat(argumentsObjects[0]);
- } else {
- return argumentsObjects;
- }
- }
- private Object[] splat(Object argument) {
- // TODO(CS): what happens if isn't just one argument, or it isn't an Array?
- if (!(argument instanceof RubyArray)) {
- splatNotArrayProfile.enter();
- notDesignedForCompilation();
- throw new UnsupportedOperationException();
- }
- final RubyArray array = (RubyArray) argument;
- final int size = array.getSize();
- final Object store = array.getStore();
- if (seenNullInUnsplat && store == null) {
- return new Object[]{};
- } else if (seenIntegerFixnumInUnsplat && store instanceof int[]) {
- return ArrayUtils.boxUntil((int[]) store, size);
- } else if (seenLongFixnumInUnsplat && store instanceof long[]) {
- return ArrayUtils.boxUntil((long[]) store, size);
- } else if (seenObjectInUnsplat && store instanceof Object[]) {
- return Arrays.copyOfRange((Object[]) store, 0, size);
- }
- CompilerDirectives.transferToInterpreterAndInvalidate();
- if (store == null) {
- seenNullInUnsplat = true;
- return new Object[]{};
- } else if (store instanceof int[]) {
- seenIntegerFixnumInUnsplat = true;
- return ArrayUtils.boxUntil((int[]) store, size);
- } else if (store instanceof long[]) {
- seenLongFixnumInUnsplat = true;
- return ArrayUtils.boxUntil((long[]) store, size);
- } else if (store instanceof Object[]) {
- seenObjectInUnsplat = true;
- return Arrays.copyOfRange((Object[]) store, 0, size);
- }
- throw new UnsupportedOperationException();
- }
- @Override
- public Object isDefined(VirtualFrame frame) {
- notDesignedForCompilation();
- if (receiver.isDefined(frame) == getContext().getCoreLibrary().getNilObject()) {
- return getContext().getCoreLibrary().getNilObject();
- }
- for (RubyNode argument : arguments) {
- if (argument.isDefined(frame) == getContext().getCoreLibrary().getNilObject()) {
- return getContext().getCoreLibrary().getNilObject();
- }
- }
- final RubyContext context = getContext();
- Object receiverObject;
- try {
- /*
- * TODO(CS): Getting a node via an accessor like this doesn't work with Truffle at the
- * moment and will cause frame escape errors, so we don't use it in compilation mode.
- */
- CompilerAsserts.neverPartOfCompilation();
- receiverObject = receiver.execute(frame);
- } catch (Exception e) {
- return getContext().getCoreLibrary().getNilObject();
- }
- // TODO(CS): this lookup should be cached
- final RubyMethod method = ModuleOperations.lookupMethod(context.getCoreLibrary().getMetaClass(receiverObject), methodName);
- final Object self = RubyArguments.getSelf(frame.getArguments());
- if (method == null) {
- final Object r = respondToMissing.call(frame, receiverObject, "respond_to_missing?", null, context.makeString(methodName));
- if (r != Dispatch.MISSING && !respondToMissingCast.executeBoolean(frame, r)) {
- return getContext().getCoreLibrary().getNilObject();
- }
- } else if (method.isUndefined()) {
- return getContext().getCoreLibrary().getNilObject();
- } else if (!ignoreVisibility && !method.isVisibleTo(this, context.getCoreLibrary().getMetaClass(self))) {
- return getContext().getCoreLibrary().getNilObject();
- }
- return context.makeString("method");
- }
- public String getName() {
- return methodName;
- }
- public boolean isVCall() {
- return isVCall;
- }
- }