RubyCallNode.java

  1. /*
  2.  * Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved. This
  3.  * code is released under a tri EPL/GPL/LGPL license. You can use it,
  4.  * redistribute it and/or modify it under the terms of the:
  5.  *
  6.  * Eclipse Public License version 1.0
  7.  * GNU General Public License version 2
  8.  * GNU Lesser General Public License version 2.1
  9.  */
  10. package org.jruby.truffle.nodes;

  11. import com.oracle.truffle.api.CompilerAsserts;
  12. import com.oracle.truffle.api.CompilerDirectives;
  13. import com.oracle.truffle.api.frame.VirtualFrame;
  14. import com.oracle.truffle.api.nodes.ExplodeLoop;
  15. import com.oracle.truffle.api.source.SourceSection;
  16. import com.oracle.truffle.api.utilities.BranchProfile;
  17. import org.jruby.truffle.nodes.cast.BooleanCastNode;
  18. import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
  19. import org.jruby.truffle.nodes.cast.ProcOrNullNode;
  20. import org.jruby.truffle.nodes.cast.ProcOrNullNodeFactory;
  21. import org.jruby.truffle.nodes.dispatch.Dispatch;
  22. import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
  23. import org.jruby.truffle.runtime.ModuleOperations;
  24. import org.jruby.truffle.runtime.RubyArguments;
  25. import org.jruby.truffle.runtime.RubyContext;
  26. import org.jruby.truffle.runtime.core.RubyArray;
  27. import org.jruby.truffle.runtime.core.RubyProc;
  28. import org.jruby.truffle.runtime.methods.RubyMethod;
  29. import org.jruby.truffle.runtime.util.ArrayUtils;

  30. import java.util.Arrays;

  31. public class RubyCallNode extends RubyNode {

  32.     private final String methodName;

  33.     @Child protected RubyNode receiver;
  34.     @Child protected ProcOrNullNode block;
  35.     @Children protected final RubyNode[] arguments;

  36.     private final boolean isSplatted;
  37.     private final boolean isVCall;

  38.     @Child protected DispatchHeadNode dispatchHead;

  39.     private final BranchProfile splatNotArrayProfile = BranchProfile.create();

  40.     @CompilerDirectives.CompilationFinal private boolean seenNullInUnsplat = false;
  41.     @CompilerDirectives.CompilationFinal private boolean seenIntegerFixnumInUnsplat = false;
  42.     @CompilerDirectives.CompilationFinal private boolean seenLongFixnumInUnsplat = false;
  43.     @CompilerDirectives.CompilationFinal private boolean seenObjectInUnsplat = false;

  44.     @Child protected DispatchHeadNode respondToMissing;
  45.     @Child protected BooleanCastNode respondToMissingCast;

  46.     private final boolean ignoreVisibility;

  47.     public RubyCallNode(RubyContext context, SourceSection section, String methodName, RubyNode receiver, RubyNode block, boolean isSplatted, RubyNode... arguments) {
  48.         this(context, section, methodName, receiver, block, isSplatted, false, false, arguments);
  49.     }


  50.     public RubyCallNode(RubyContext context, SourceSection section, String methodName, RubyNode receiver, RubyNode block, boolean isSplatted, boolean ignoreVisibility, boolean rubiniusPrimitive, RubyNode... arguments) {
  51.         this(context, section, methodName, receiver, block, isSplatted, false, ignoreVisibility, rubiniusPrimitive, arguments);
  52.     }

  53.     public RubyCallNode(RubyContext context, SourceSection section, String methodName, RubyNode receiver, RubyNode block, boolean isSplatted, boolean isVCall, boolean ignoreVisibility, boolean rubiniusPrimitive, RubyNode... arguments) {
  54.         super(context, section);

  55.         this.methodName = methodName;

  56.         this.receiver = receiver;

  57.         if (block == null) {
  58.             this.block = null;
  59.         } else {
  60.             this.block = ProcOrNullNodeFactory.create(context, section, block);
  61.         }

  62.         this.arguments = arguments;
  63.         this.isSplatted = isSplatted;
  64.         this.isVCall = isVCall;

  65.         dispatchHead = new DispatchHeadNode(context, ignoreVisibility, false, Dispatch.MissingBehavior.CALL_METHOD_MISSING);
  66.         respondToMissing = new DispatchHeadNode(context, true, Dispatch.MissingBehavior.RETURN_MISSING);
  67.         respondToMissingCast = BooleanCastNodeFactory.create(context, section, null);

  68.         this.ignoreVisibility = ignoreVisibility;
  69.     }

  70.     @Override
  71.     public Object execute(VirtualFrame frame) {
  72.         final Object receiverObject = receiver.execute(frame);
  73.         final Object[] argumentsObjects = executeArguments(frame);
  74.         final RubyProc blockObject = executeBlock(frame);

  75.         return dispatchHead.call(frame, receiverObject, methodName, blockObject, argumentsObjects);
  76.     }

  77.     private RubyProc executeBlock(VirtualFrame frame) {
  78.         if (block != null) {
  79.             return block.executeRubyProc(frame);
  80.         } else {
  81.             return null;
  82.         }
  83.     }

  84.     @ExplodeLoop
  85.     private Object[] executeArguments(VirtualFrame frame) {
  86.         final Object[] argumentsObjects = new Object[arguments.length];

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

  90.         if (isSplatted) {
  91.             return splat(argumentsObjects[0]);
  92.         } else {
  93.             return argumentsObjects;
  94.         }
  95.     }

  96.     private Object[] splat(Object argument) {
  97.         // TODO(CS): what happens if isn't just one argument, or it isn't an Array?

  98.         if (!(argument instanceof RubyArray)) {
  99.             splatNotArrayProfile.enter();
  100.             notDesignedForCompilation();
  101.             throw new UnsupportedOperationException();
  102.         }

  103.         final RubyArray array = (RubyArray) argument;
  104.         final int size = array.getSize();
  105.         final Object store = array.getStore();

  106.         if (seenNullInUnsplat && store == null) {
  107.             return new Object[]{};
  108.         } else if (seenIntegerFixnumInUnsplat && store instanceof int[]) {
  109.             return ArrayUtils.boxUntil((int[]) store, size);
  110.         } else if (seenLongFixnumInUnsplat && store instanceof long[]) {
  111.             return ArrayUtils.boxUntil((long[]) store, size);
  112.         } else if (seenObjectInUnsplat && store instanceof Object[]) {
  113.             return Arrays.copyOfRange((Object[]) store, 0, size);
  114.         }

  115.         CompilerDirectives.transferToInterpreterAndInvalidate();

  116.         if (store == null) {
  117.             seenNullInUnsplat = true;
  118.             return new Object[]{};
  119.         } else if (store instanceof int[]) {
  120.             seenIntegerFixnumInUnsplat = true;
  121.             return ArrayUtils.boxUntil((int[]) store, size);
  122.         } else if (store instanceof long[]) {
  123.             seenLongFixnumInUnsplat = true;
  124.             return ArrayUtils.boxUntil((long[]) store, size);
  125.         } else if (store instanceof Object[]) {
  126.             seenObjectInUnsplat = true;
  127.             return Arrays.copyOfRange((Object[]) store, 0, size);
  128.         }

  129.         throw new UnsupportedOperationException();
  130.     }

  131.     @Override
  132.     public Object isDefined(VirtualFrame frame) {
  133.         notDesignedForCompilation();

  134.         if (receiver.isDefined(frame) == getContext().getCoreLibrary().getNilObject()) {
  135.             return getContext().getCoreLibrary().getNilObject();
  136.         }

  137.         for (RubyNode argument : arguments) {
  138.             if (argument.isDefined(frame) == getContext().getCoreLibrary().getNilObject()) {
  139.                 return getContext().getCoreLibrary().getNilObject();
  140.             }
  141.         }

  142.         final RubyContext context = getContext();

  143.         Object receiverObject;

  144.         try {
  145.             /*
  146.              * TODO(CS): Getting a node via an accessor like this doesn't work with Truffle at the
  147.              * moment and will cause frame escape errors, so we don't use it in compilation mode.
  148.              */

  149.             CompilerAsserts.neverPartOfCompilation();

  150.             receiverObject = receiver.execute(frame);
  151.         } catch (Exception e) {
  152.             return getContext().getCoreLibrary().getNilObject();
  153.         }

  154.         // TODO(CS): this lookup should be cached

  155.         final RubyMethod method = ModuleOperations.lookupMethod(context.getCoreLibrary().getMetaClass(receiverObject), methodName);

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

  157.         if (method == null) {
  158.             final Object r = respondToMissing.call(frame, receiverObject, "respond_to_missing?", null, context.makeString(methodName));

  159.             if (r != Dispatch.MISSING && !respondToMissingCast.executeBoolean(frame, r)) {
  160.                 return getContext().getCoreLibrary().getNilObject();
  161.             }
  162.         } else if (method.isUndefined()) {
  163.             return getContext().getCoreLibrary().getNilObject();
  164.         } else if (!ignoreVisibility && !method.isVisibleTo(this, context.getCoreLibrary().getMetaClass(self))) {
  165.             return getContext().getCoreLibrary().getNilObject();
  166.         }

  167.         return context.makeString("method");
  168.     }

  169.     public String getName() {
  170.         return methodName;
  171.     }

  172.     public boolean isVCall() {
  173.         return isVCall;
  174.     }

  175. }