/** Read the rest of arguments after a certain point into an array. */ public class ReadRestArgumentNode extends RubyNode { private final int startIndex; private final int negativeEndIndex; private final boolean keywordArguments; private final BranchProfile noArgumentsLeftProfile = BranchProfile.create(); private final BranchProfile subsetOfArgumentsProfile = BranchProfile.create(); public ReadRestArgumentNode( RubyContext context, SourceSection sourceSection, int startIndex, int negativeEndIndex, boolean keywordArguments) { super(context, sourceSection); this.startIndex = startIndex; this.negativeEndIndex = negativeEndIndex; this.keywordArguments = keywordArguments; } @Override public Object execute(VirtualFrame frame) { final RubyClass arrayClass = getContext().getCoreLibrary().getArrayClass(); int count = RubyArguments.getUserArgumentsCount(frame.getArguments()); int endIndex = count + negativeEndIndex; if (keywordArguments) { final Object lastArgument = RubyArguments.getUserArgument( frame.getArguments(), RubyArguments.getUserArgumentsCount(frame.getArguments()) - 1); if (lastArgument instanceof RubyHash) { endIndex -= 1; } } final int length = endIndex - startIndex; if (startIndex == 0) { final Object[] arguments = RubyArguments.extractUserArguments(frame.getArguments()); return new RubyArray(arrayClass, arguments, length); } else { if (startIndex >= endIndex) { noArgumentsLeftProfile.enter(); return new RubyArray(arrayClass); } else { subsetOfArgumentsProfile.enter(); final Object[] arguments = RubyArguments.extractUserArguments(frame.getArguments()); // TODO(CS): risk here of widening types too much - always going to be Object[] - does seem // to be something that does happen return new RubyArray( arrayClass, ArrayUtils.extractRange(arguments, startIndex, endIndex), length); } } } }
@CoreMethod( names = "each", needsBlock = true, lowerFixnumSelf = true, returnsEnumeratorIfNoBlock = true) public abstract static class EachNode extends YieldingCoreMethodNode { private final BranchProfile breakProfile = BranchProfile.create(); private final BranchProfile nextProfile = BranchProfile.create(); private final BranchProfile redoProfile = BranchProfile.create(); public EachNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); } public EachNode(EachNode prev) { super(prev); } @Specialization public Object each(VirtualFrame frame, RubyRange.IntegerFixnumRange range, RubyProc block) { final int exclusiveEnd = range.getExclusiveEnd(); int count = 0; try { outer: for (int n = range.getBegin(); n < exclusiveEnd; n++) { while (true) { if (CompilerDirectives.inInterpreter()) { count++; } try { yield(frame, block, n); continue outer; } catch (BreakException e) { breakProfile.enter(); return e.getResult(); } catch (NextException e) { nextProfile.enter(); continue outer; } catch (RedoException e) { redoProfile.enter(); } } } } finally { if (CompilerDirectives.inInterpreter()) { getRootNode().reportLoopCount(count); } } return range; } }
/** * Catch a {@code break} from a call with a block containing a break or inside a while/until loop. */ public class CatchBreakNode extends RubyNode { @Child private RubyNode body; private final BreakID breakID; private final BranchProfile breakProfile = BranchProfile.create(); private final ConditionProfile matchingBreakProfile = ConditionProfile.createCountingProfile(); public CatchBreakNode( RubyContext context, SourceSection sourceSection, RubyNode body, BreakID breakID) { super(context, sourceSection); this.body = body; this.breakID = breakID; } @Override public Object execute(VirtualFrame frame) { try { return body.execute(frame); } catch (BreakException e) { breakProfile.enter(); if (matchingBreakProfile.profile(e.getBreakID() == breakID)) { return e.getResult(); } else { throw e; } } } }
/** Switches between loading arguments as normal and doing a destructure. */ public class ShouldDestructureNode extends RubyNode { private final Arity arity; @Child private RespondToNode respondToCheck; private final BranchProfile checkRespondProfile = BranchProfile.create(); public ShouldDestructureNode( RubyContext context, SourceSection sourceSection, Arity arity, RespondToNode respondToCheck) { super(context, sourceSection); this.arity = arity; this.respondToCheck = respondToCheck; } @Override public boolean executeBoolean(VirtualFrame frame) { // TODO(CS): express this using normal nodes? // If we don't accept any arguments, there's never any need to destructure // TODO(CS): is this guaranteed by the translator anyway? if (!arity.allowsMore() && arity.getRequired() == 0 && arity.getOptional() == 0) { return false; } // If we only accept one argument, there's never any need to destructure if (!arity.allowsMore() && arity.getRequired() == 1 && arity.getOptional() == 0) { return false; } // If the caller supplied no arguments, or more than one argument, there's no need to // destructure this time if (RubyArguments.getUserArgumentsCount(frame.getArguments()) != 1) { return false; } // If the single argument is a RubyArray, destructure // TODO(CS): can we not just reply on the respondToCheck? Should experiment. if (RubyGuards.isRubyArray(RubyArguments.getUserArgument(frame.getArguments(), 0))) { return true; } // If the single argument responds to #to_ary, then destructure checkRespondProfile.enter(); return respondToCheck.executeBoolean(frame); } @Override public Object execute(VirtualFrame frame) { return executeBoolean(frame); } }
@CoreMethod(names = ">>", required = 1, lowerFixnumParameters = 0) public abstract static class RightShiftNode extends BignumCoreMethodNode { private final BranchProfile bLessThanZero = BranchProfile.create(); public RightShiftNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); } @Specialization public Object leftShift(DynamicObject a, int b) { if (b >= 0) { return fixnumOrBignum(Layouts.BIGNUM.getValue(a).shiftRight(b)); } else { bLessThanZero.enter(); return fixnumOrBignum(Layouts.BIGNUM.getValue(a).shiftLeft(-b)); } } }
/** Read pre-optional argument. */ public class ReadPreArgumentNode extends RubyNode { private final int index; private final BranchProfile outOfRangeProfile = BranchProfile.create(); private final MissingArgumentBehaviour missingArgumentBehaviour; private final ValueProfile argumentValueProfile = ValueProfile.createPrimitiveProfile(); public ReadPreArgumentNode( RubyContext context, SourceSection sourceSection, int index, MissingArgumentBehaviour missingArgumentBehaviour) { super(context, sourceSection); this.index = index; this.missingArgumentBehaviour = missingArgumentBehaviour; } @Override public Object execute(VirtualFrame frame) { if (index >= RubyArguments.getUserArgumentsCount(frame.getArguments())) { outOfRangeProfile.enter(); switch (missingArgumentBehaviour) { case RUNTIME_ERROR: break; case UNDEFINED: return UndefinedPlaceholder.INSTANCE; case NIL: return nil(); } } return argumentValueProfile.profile(RubyArguments.getUserArgument(frame.getArguments(), index)); } }
public class ExceptionTranslatingNode extends RubyNode { private final UnsupportedOperationBehavior unsupportedOperationBehavior; @Child private RubyNode child; private final BranchProfile controlProfile = BranchProfile.create(); private final BranchProfile rethrowProfile = BranchProfile.create(); public ExceptionTranslatingNode( RubyContext context, SourceSection sourceSection, RubyNode child) { this(context, sourceSection, child, UnsupportedOperationBehavior.TYPE_ERROR); } public ExceptionTranslatingNode( RubyContext context, SourceSection sourceSection, RubyNode child, UnsupportedOperationBehavior unsupportedOperationBehavior) { super(context, sourceSection); this.child = child; this.unsupportedOperationBehavior = unsupportedOperationBehavior; } @Override public Object execute(VirtualFrame frame) { try { assert assertArgumentsShouldBeVisible(frame); final Object result = child.execute(frame); assert shouldObjectBeVisible(result) : "result@" + getEncapsulatingSourceSection().getShortDescription(); return result; } catch (StackOverflowError error) { // TODO: we might want to do sth smarter here to avoid consuming frames when we are almost out // of it. CompilerDirectives.transferToInterpreter(); throw new RaiseException(translate(error)); } catch (TruffleFatalException | ThreadExitException exception) { throw exception; } catch (ControlFlowException exception) { controlProfile.enter(); throw exception; } catch (RaiseException exception) { rethrowProfile.enter(); throw exception; } catch (MainExitException exception) { CompilerDirectives.transferToInterpreter(); throw exception; } catch (ArithmeticException exception) { CompilerDirectives.transferToInterpreter(); throw new RaiseException(translate(exception)); } catch (UnsupportedSpecializationException exception) { CompilerDirectives.transferToInterpreter(); throw new RaiseException(translate(exception)); } catch (org.jruby.exceptions.RaiseException e) { CompilerDirectives.transferToInterpreter(); throw new RaiseException(getContext().toTruffle(e.getException(), this)); } catch (Throwable exception) { CompilerDirectives.transferToInterpreter(); throw new RaiseException(translate(exception)); } } private DynamicObject translate(ArithmeticException exception) { if (getContext().getOptions().EXCEPTIONS_PRINT_JAVA) { exception.printStackTrace(); } return getContext().getCoreLibrary().zeroDivisionError(this); } private DynamicObject translate(UnsupportedSpecializationException exception) { if (getContext().getOptions().EXCEPTIONS_PRINT_JAVA) { exception.printStackTrace(); } final StringBuilder builder = new StringBuilder(); builder.append("Truffle doesn't have a case for the "); builder.append(exception.getNode().getClass().getName()); builder.append(" node with values of type "); for (Object value : exception.getSuppliedValues()) { builder.append(" "); if (value == null) { builder.append("null"); } else if (value instanceof DynamicObject) { builder.append( Layouts.MODULE .getFields(Layouts.BASIC_OBJECT.getLogicalClass(((DynamicObject) value))) .getName()); builder.append("("); builder.append(value.getClass().getName()); builder.append(")"); if (RubyGuards.isRubyArray(value)) { final DynamicObject array = (DynamicObject) value; builder.append("["); if (Layouts.ARRAY.getStore(array) == null) { builder.append("null"); } else { builder.append(Layouts.ARRAY.getStore(array).getClass().getName()); } builder.append(","); builder.append(Layouts.ARRAY.getSize(array)); builder.append("]"); } else if (RubyGuards.isRubyHash(value)) { final Object store = Layouts.HASH.getStore((DynamicObject) value); if (store == null) { builder.append("[null]"); } else { builder.append("["); builder.append(store.getClass().getName()); builder.append("]"); } } } else { builder.append(value.getClass().getName()); } if (value instanceof Number || value instanceof Boolean) { builder.append("="); builder.append(value.toString()); } } switch (unsupportedOperationBehavior) { case TYPE_ERROR: return getContext().getCoreLibrary().typeError(builder.toString(), this); case ARGUMENT_ERROR: return getContext().getCoreLibrary().argumentError(builder.toString(), this); default: throw new UnsupportedOperationException(); } } public DynamicObject translate(Throwable throwable) { if (getContext().getOptions().EXCEPTIONS_PRINT_JAVA || (boolean) getContext().getOptions().EXCEPTIONS_PRINT_UNCAUGHT_JAVA) { throwable.printStackTrace(); } if (throwable.getStackTrace().length > 0) { return getContext() .getCoreLibrary() .internalError( String.format( "%s %s %s", throwable.getClass().getSimpleName(), throwable.getMessage(), throwable.getStackTrace()[0].toString()), this); } else { return getContext() .getCoreLibrary() .internalError( String.format( "%s %s ???", throwable.getClass().getSimpleName(), throwable.getMessage()), this); } } private boolean shouldObjectBeVisible(Object object) { return object instanceof TruffleObject || object instanceof Boolean || object instanceof Integer || object instanceof Long || object instanceof Double; } private boolean assertArgumentsShouldBeVisible(VirtualFrame frame) { final Object self = RubyArguments.getSelf(frame.getArguments()); assert shouldObjectBeVisible(self) : "self=" + (self == null ? "null" : self.getClass()) + "@" + getEncapsulatingSourceSection().getShortDescription(); final Object[] arguments = RubyArguments.extractUserArguments(frame.getArguments()); for (int n = 0; n < arguments.length; n++) { final Object argument = arguments[n]; assert shouldObjectBeVisible(argument) : "arg[" + n + "]=" + (argument == null ? "null" : argument.getClass() + "=" + toString(argument)) + "@" + getEncapsulatingSourceSection().getShortDescription(); } return true; } private String toString(Object object) { if (object instanceof Object[]) { return Arrays.toString((Object[]) object); } else { return object.toString(); } } }
/** * Represents a block of code run with exception handlers. There's no {@code try} keyword in Ruby - * it's implicit - but it's similar to a try statement in any other language. */ public class TryNode extends RubyNode { @Child protected ExceptionTranslatingNode tryPart; @Children final RescueNode[] rescueParts; @Child protected RubyNode elsePart; @Child protected WriteInstanceVariableNode clearExceptionVariableNode; private final BranchProfile elseProfile = BranchProfile.create(); private final BranchProfile controlFlowProfile = BranchProfile.create(); private final BranchProfile raiseExceptionProfile = BranchProfile.create(); public TryNode( RubyContext context, SourceSection sourceSection, ExceptionTranslatingNode tryPart, RescueNode[] rescueParts, RubyNode elsePart) { super(context, sourceSection); this.tryPart = tryPart; this.rescueParts = rescueParts; this.elsePart = elsePart; clearExceptionVariableNode = new WriteInstanceVariableNode( context, sourceSection, "$!", new ObjectLiteralNode( context, sourceSection, context.getCoreLibrary().getGlobalVariablesObject()), new ObjectLiteralNode(context, sourceSection, context.getCoreLibrary().getNilObject()), true); } @Override public Object execute(VirtualFrame frame) { while (true) { getContext().getSafepointManager().poll(); Object result; try { result = tryPart.execute(frame); } catch (ControlFlowException exception) { controlFlowProfile.enter(); throw exception; } catch (RaiseException exception) { raiseExceptionProfile.enter(); try { return handleException(frame, exception); } catch (RetryException e) { continue; } } finally { clearExceptionVariableNode.execute(frame); } elseProfile.enter(); elsePart.executeVoid(frame); return result; } } @ExplodeLoop private Object handleException(VirtualFrame frame, RaiseException exception) { CompilerAsserts.neverPartOfCompilation(); notDesignedForCompilation(); getContext() .getCoreLibrary() .getGlobalVariablesObject() .getOperations() .setInstanceVariable( getContext().getCoreLibrary().getGlobalVariablesObject(), "$!", exception.getRubyException()); for (RescueNode rescue : rescueParts) { if (rescue.canHandle(frame, exception.getRubyException())) { return rescue.execute(frame); } } throw exception; } }
@ImportStatic(HashGuards.class) @NodeChildren({ @NodeChild(value = "hash", type = RubyNode.class), @NodeChild(value = "key", type = RubyNode.class), @NodeChild(value = "value", type = RubyNode.class), @NodeChild(value = "byIdentity", type = RubyNode.class) }) public abstract class SetNode extends RubyNode { @Child private HashNode hashNode; @Child private CallDispatchHeadNode eqlNode; @Child private BasicObjectNodes.ReferenceEqualNode equalNode; @Child private LookupEntryNode lookupEntryNode; private final ConditionProfile byIdentityProfile = ConditionProfile.createBinaryProfile(); private final BranchProfile extendProfile = BranchProfile.create(); private final ConditionProfile strategyProfile = ConditionProfile.createBinaryProfile(); public SetNode(RubyContext context, SourceSection sourceSection) { super(context, sourceSection); hashNode = new HashNode(context, sourceSection); eqlNode = DispatchHeadNodeFactory.createMethodCall(context); equalNode = BasicObjectNodesFactory.ReferenceEqualNodeFactory.create( context, sourceSection, null, null); } public abstract Object executeSet( VirtualFrame frame, DynamicObject hash, Object key, Object value, boolean byIdentity); @Specialization(guards = {"isNullHash(hash)", "!isRubyString(key)"}) public Object setNull( VirtualFrame frame, DynamicObject hash, Object key, Object value, boolean byIdentity) { HashNodes.setStore( hash, PackedArrayStrategy.createStore(hashNode.hash(frame, key), key, value), 1, null, null); assert HashNodes.verifyStore(hash); return value; } @Specialization(guards = {"isNullHash(hash)", "byIdentity", "isRubyString(key)"}) public Object setNullByIdentity( VirtualFrame frame, DynamicObject hash, DynamicObject key, Object value, boolean byIdentity) { return setNull(frame, hash, (Object) key, value, byIdentity); } @Specialization(guards = {"isNullHash(hash)", "!byIdentity", "isRubyString(key)"}) public Object setNullNotByIdentity( VirtualFrame frame, DynamicObject hash, DynamicObject key, Object value, boolean byIdentity) { return setNull( frame, hash, ruby(frame, "key.frozen? ? key : key.dup.freeze", "key", key), value, byIdentity); } @ExplodeLoop @Specialization(guards = {"isPackedHash(hash)", "!isRubyString(key)"}) public Object setPackedArray( VirtualFrame frame, DynamicObject hash, Object key, Object value, boolean byIdentity) { assert HashNodes.verifyStore(hash); final int hashed = hashNode.hash(frame, key); final Object[] store = (Object[]) Layouts.HASH.getStore(hash); final int size = Layouts.HASH.getSize(hash); for (int n = 0; n < PackedArrayStrategy.MAX_ENTRIES; n++) { if (n < size) { if (hashed == PackedArrayStrategy.getHashed(store, n)) { final boolean equal; if (byIdentityProfile.profile(byIdentity)) { equal = equalNode.executeReferenceEqual(frame, key, PackedArrayStrategy.getKey(store, n)); } else { equal = eqlNode.callBoolean(frame, key, "eql?", null, PackedArrayStrategy.getKey(store, n)); } if (equal) { PackedArrayStrategy.setValue(store, n, value); assert HashNodes.verifyStore(hash); return value; } } } } extendProfile.enter(); if (strategyProfile.profile(size + 1 <= PackedArrayStrategy.MAX_ENTRIES)) { PackedArrayStrategy.setHashedKeyValue(store, size, hashed, key, value); Layouts.HASH.setSize(hash, size + 1); return value; } else { PackedArrayStrategy.promoteToBuckets(hash, store, size); BucketsStrategy.addNewEntry(hash, hashed, key, value); } assert HashNodes.verifyStore(hash); return value; } @Specialization(guards = {"isPackedHash(hash)", "byIdentity", "isRubyString(key)"}) public Object setPackedArrayByIdentity( VirtualFrame frame, DynamicObject hash, DynamicObject key, Object value, boolean byIdentity) { return setPackedArray(frame, hash, key, value, byIdentity); } @Specialization(guards = {"isPackedHash(hash)", "!byIdentity", "isRubyString(key)"}) public Object setPackedArrayNotByIdentity( VirtualFrame frame, DynamicObject hash, DynamicObject key, Object value, boolean byIdentity) { return setPackedArray( frame, hash, ruby(frame, "key.frozen? ? key : key.dup.freeze", "key", key), value, byIdentity); } // Can't be @Cached yet as we call from the RubyString specialisation private final ConditionProfile foundProfile = ConditionProfile.createBinaryProfile(); private final ConditionProfile bucketCollisionProfile = ConditionProfile.createBinaryProfile(); private final ConditionProfile appendingProfile = ConditionProfile.createBinaryProfile(); private final ConditionProfile resizeProfile = ConditionProfile.createBinaryProfile(); @Specialization(guards = {"isBucketHash(hash)", "!isRubyString(key)"}) public Object setBuckets( VirtualFrame frame, DynamicObject hash, Object key, Object value, boolean byIdentity) { assert HashNodes.verifyStore(hash); if (lookupEntryNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); lookupEntryNode = insert(new LookupEntryNode(getContext(), getEncapsulatingSourceSection())); } final HashLookupResult result = lookupEntryNode.lookup(frame, hash, key); final Entry entry = result.getEntry(); if (foundProfile.profile(entry == null)) { final Entry[] entries = (Entry[]) Layouts.HASH.getStore(hash); final Entry newEntry = new Entry(result.getHashed(), key, value); if (bucketCollisionProfile.profile(result.getPreviousEntry() == null)) { entries[result.getIndex()] = newEntry; } else { result.getPreviousEntry().setNextInLookup(newEntry); } final Entry lastInSequence = Layouts.HASH.getLastInSequence(hash); if (appendingProfile.profile(lastInSequence == null)) { Layouts.HASH.setFirstInSequence(hash, newEntry); } else { lastInSequence.setNextInSequence(newEntry); newEntry.setPreviousInSequence(lastInSequence); } Layouts.HASH.setLastInSequence(hash, newEntry); final int newSize = Layouts.HASH.getSize(hash) + 1; Layouts.HASH.setSize(hash, newSize); // TODO CS 11-May-15 could store the next size for resize instead of doing a float operation // each time if (resizeProfile.profile(newSize / (double) entries.length > BucketsStrategy.LOAD_FACTOR)) { BucketsStrategy.resize(hash); } } else { entry.setKeyValue(result.getHashed(), key, value); } assert HashNodes.verifyStore(hash); return value; } @Specialization(guards = {"isBucketHash(hash)", "byIdentity", "isRubyString(key)"}) public Object setBucketsByIdentity( VirtualFrame frame, DynamicObject hash, DynamicObject key, Object value, boolean byIdentity) { return setBuckets(frame, hash, (Object) key, value, byIdentity); } @Specialization(guards = {"isBucketHash(hash)", "!byIdentity", "isRubyString(key)"}) public Object setBucketsNotByIdentity( VirtualFrame frame, DynamicObject hash, DynamicObject key, Object value, boolean byIdentity) { return setBuckets( frame, hash, ruby(frame, "key.frozen? ? key : key.dup.freeze", "key", key), value, byIdentity); } }