Package org.jruby.truffle.nodes.core

Source Code of org.jruby.truffle.nodes.core.HashNodes$KeysNode

/*
* 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.core;

import java.util.*;

import com.oracle.truffle.api.*;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.source.*;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.utilities.BranchProfile;
import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyRootNode;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.nodes.yield.YieldDispatchHeadNode;
import org.jruby.truffle.runtime.*;
import org.jruby.truffle.runtime.core.*;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyHash;

@CoreClass(name = "Hash")
public abstract class HashNodes {

    @CoreMethod(names = "==", required = 1)
    public abstract static class EqualNode extends HashCoreMethodNode {

        @Child protected DispatchHeadNode equalNode;

        public EqualNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            equalNode = new DispatchHeadNode(context);
        }

        public EqualNode(EqualNode prev) {
            super(prev);
            equalNode = prev.equalNode;
        }

        @Specialization(guards = {"isNull", "isOtherNull"})
        public boolean equalNull(RubyHash a, RubyHash b) {
            return true;
        }

        @Specialization(guards = {"isObjectArray", "isOtherObjectArray"})
        public boolean equalObjectArray(VirtualFrame frame, RubyHash a, RubyHash b) {
            notDesignedForCompilation();

            if (a == b) {
                return true;
            }

            final Object[] aStore = (Object[]) a.getStore();
            final int aSize = a.getStoreSize();

            final Object[] bStore = (Object[]) b.getStore();
            final int bSize = b.getStoreSize();

            if (aSize != bSize) {
                return false;
            }

            for (int n = 0; n < aSize * 2; n++) {
                if (!equalNode.callIsTruthy(frame, aStore[n], "==", null, bStore[n])) {
                    return false;
                }
            }

            return true;
        }

        @Specialization(guards = {"isObjectLinkedHashMap", "isOtherObjectLinkedHashMap"})
        public boolean equalObjectLinkedHashMap(RubyHash a, RubyHash b) {
            notDesignedForCompilation();
            throw new UnsupportedOperationException();
        }

        @Specialization(guards = "!isHash(arguments[1])")
        public boolean equal(RubyHash a, Object b) {
            notDesignedForCompilation();
            return false;
        }

        protected boolean isHash(Object object) {
            return object instanceof RubyHash;
        }
    }

    @CoreMethod(names = "[]", onSingleton = true, argumentsAsArray = true)
    public abstract static class ConstructNode extends HashCoreMethodNode {

        private final BranchProfile singleObject = new BranchProfile();
        private final BranchProfile singleArray = new BranchProfile();
        private final BranchProfile objectArray = new BranchProfile();
        private final BranchProfile smallObjectArray = new BranchProfile();
        private final BranchProfile largeObjectArray = new BranchProfile();
        private final BranchProfile otherArray = new BranchProfile();
        private final BranchProfile singleOther = new BranchProfile();
        private final BranchProfile keyValues = new BranchProfile();

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

        public ConstructNode(ConstructNode prev) {
            super(prev);
        }

        @ExplodeLoop
        @Specialization
        public RubyHash construct(Object[] args) {
            if (args.length == 1) {
                singleObject.enter();

                final Object arg = args[0];

                if (arg instanceof RubyArray) {
                    singleArray.enter();

                    final RubyArray array = (RubyArray) arg;

                    if (array.getStore() instanceof Object[]) {
                        objectArray.enter();

                        final Object[] store = (Object[]) array.getStore();

                        // TODO(CS): zero length arrays might be a good specialisation

                        if (store.length <= RubyHash.HASHES_SMALL) {
                            smallObjectArray.enter();

                            final int size = store.length;
                            final Object[] newStore = new Object[RubyHash.HASHES_SMALL * 2];

                            for (int n = 0; n < RubyHash.HASHES_SMALL; n++) {
                                if (n < size) {
                                    final Object pair = store[n];

                                    if (!(pair instanceof RubyArray)) {
                                        CompilerDirectives.transferToInterpreter();
                                        throw new UnsupportedOperationException();
                                    }

                                    final RubyArray pairArray = (RubyArray) pair;

                                    if (!(pairArray.getStore() instanceof Object[])) {
                                        CompilerDirectives.transferToInterpreter();
                                        throw new UnsupportedOperationException();
                                    }

                                    final Object[] pairStore = (Object[]) pairArray.getStore();

                                    newStore[n * 2] = pairStore[0];
                                    newStore[n * 2 + 1] = pairStore[1];
                                }
                            }

                            return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, newStore, size);
                        } else {
                            largeObjectArray.enter();
                            throw new UnsupportedOperationException();
                        }
                    } else {
                        otherArray.enter();
                        throw new UnsupportedOperationException();
                    }
                } else {
                    singleOther.enter();
                    throw new UnsupportedOperationException();
                }
            } else {
                keyValues.enter();
                // Slow because we don't want the PE to see the hash map at all
                return constructObjectLinkedMapMap(args);
            }
        }

        @CompilerDirectives.SlowPath
        public RubyHash constructObjectLinkedMapMap(Object[] args) {
            final LinkedHashMap<Object, Object> store = new LinkedHashMap<>();

            for (int n = 0; n < args.length; n += 2) {
                store.put(args[n], args[n + 1]);
            }

            return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, store, 0);
        }

    }

    @CoreMethod(names = "[]", required = 1)
    public abstract static class GetIndexNode extends HashCoreMethodNode {

        @Child protected DispatchHeadNode eqlNode;
        @Child protected YieldDispatchHeadNode yield;

        private final BranchProfile notInHashProfile = new BranchProfile();
        private final BranchProfile useDefaultProfile = new BranchProfile();

        public GetIndexNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            eqlNode = new DispatchHeadNode(context);
            yield = new YieldDispatchHeadNode(context);
        }

        public GetIndexNode(GetIndexNode prev) {
            super(prev);
            eqlNode = prev.eqlNode;
            yield = prev.yield;
        }

        @Specialization(guards = "isNull")
        public Object getNull(VirtualFrame frame, RubyHash hash, Object key) {
            notDesignedForCompilation();

            if (hash.getDefaultBlock() != null) {
                return yield.dispatch(frame, hash.getDefaultBlock(), hash, key);
            } else if (hash.getDefaultValue() != null) {
                return hash.getDefaultValue();
            } else {
                return getContext().getCoreLibrary().getNilObject();
            }
        }

        @ExplodeLoop
        @Specialization(guards = "isObjectArray")
        public Object getObjectArray(VirtualFrame frame, RubyHash hash, Object key) {
            final Object[] store = (Object[]) hash.getStore();
            final int size = hash.getStoreSize();

            for (int n = 0; n < RubyHash.HASHES_SMALL; n++) {
                if (n < size && eqlNode.callIsTruthy(frame, store[n * 2], "eql?", null, key)) {
                    return store[n * 2 + 1];
                }
            }

            notInHashProfile.enter();

            if (hash.getDefaultBlock() != null) {
                useDefaultProfile.enter();
                return yield.dispatch(frame, hash.getDefaultBlock(), hash, key);
            }

            if (hash.getDefaultValue() != null) {
                return hash.getDefaultValue();
            }

            return getContext().getCoreLibrary().getNilObject();

        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public Object getObjectLinkedHashMap(VirtualFrame frame, RubyHash hash, Object key) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            // TODO(CS): not correct - using Java's Object#equals

            final Object value = store.get(key);

            if (value == null) {
                if (hash.getDefaultBlock() != null) {
                    return yield.dispatch(frame, hash.getDefaultBlock(), hash, key);
                } else if (hash.getDefaultValue() != null) {
                    return hash.getDefaultValue();
                } else {
                    return getContext().getCoreLibrary().getNilObject();
                }
            }

            return value;
        }

    }

    @CoreMethod(names = "[]=", required = 2)
    public abstract static class SetIndexNode extends HashCoreMethodNode {

        @Child protected DispatchHeadNode eqlNode;

        private final BranchProfile considerExtendProfile = new BranchProfile();
        private final BranchProfile extendProfile = new BranchProfile();
        private final BranchProfile transitionToLinkedHashMapProfile = new BranchProfile();

        public SetIndexNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            eqlNode = new DispatchHeadNode(context);
        }

        public SetIndexNode(SetIndexNode prev) {
            super(prev);
            eqlNode = prev.eqlNode;
        }

        @Specialization(guards = "isNull")
        public Object setNull(RubyHash hash, Object key, Object value) {
            hash.checkFrozen(this);
            final Object[] store = new Object[RubyHash.HASHES_SMALL * 2];
            store[0] = key;
            store[1] = value;
            hash.setStore(store, 1);
            return value;
        }

        @ExplodeLoop
        @Specialization(guards = "isObjectArray")
        public Object setObjectArray(VirtualFrame frame, RubyHash hash, Object key, Object value) {
            hash.checkFrozen(this);

            final Object[] store = (Object[]) hash.getStore();
            final int size = hash.getStoreSize();

            for (int n = 0; n < RubyHash.HASHES_SMALL; n++) {
                if (n < size && eqlNode.callIsTruthy(frame, store[n * 2], "eql?", null, key)) {
                    store[n * 2 + 1] = value;
                    return value;
                }
            }

            considerExtendProfile.enter();

            final int newSize = size + 1;

            if (newSize <= RubyHash.HASHES_SMALL) {
                extendProfile.enter();
                store[size * 2] = key;
                store[size * 2 + 1] = value;
                hash.setStoreSize(newSize);
                return value;
            }


            transitionToLinkedHashMapProfile.enter();

            transitionToLinkedHashMap(hash, store, key, value);
            return value;
        }

        @CompilerDirectives.SlowPath
        private void transitionToLinkedHashMap(RubyHash hash, Object[] oldStore, Object key, Object value) {
            final LinkedHashMap<Object, Object> newStore = new LinkedHashMap<>();

            for (int n = 0; n < oldStore.length; n += 2) {
                newStore.put(oldStore[n], oldStore[n + 1]);
            }

            newStore.put(key, value);
            hash.setStore(newStore, 0);
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public Object setObjectLinkedHashMap(RubyHash hash, Object key, Object value) {
            notDesignedForCompilation();

            hash.checkFrozen(this);

            // TODO(CS): not correct - using Java's Object#equals

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();
            store.put(key, value);
            return value;
        }

    }

    @CoreMethod(names = "delete", required = 1)
    public abstract static class DeleteNode extends HashCoreMethodNode {

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

        public DeleteNode(DeleteNode prev) {
            super(prev);
        }

        @Specialization(guards = "isNull")
        public RubyNilClass deleteNull(RubyHash hash, Object key) {
            hash.checkFrozen(this);
            return getContext().getCoreLibrary().getNilObject();
        }

        @Specialization(guards = "isObjectArray")
        public Object deleteObjectArray(RubyHash hash, Object key) {
            notDesignedForCompilation();

            // TODO(CS): seriously not correct

            hash.checkFrozen(this);

            final Object[] oldStore = (Object[]) hash.getStore();

            final LinkedHashMap<Object, Object> newStore = new LinkedHashMap<>();
            hash.setStore(newStore, 0);

            for (int n = 0; n < hash.getStoreSize(); n++) {
                newStore.put(oldStore[n * 2], oldStore[n * 2 + 1]);
            }

            // TODO(CS): seriously not correct - using Java's Object#equals

            final Object removed = newStore.remove(key);

            if (removed == null) {
                return getContext().getCoreLibrary().getNilObject();
            } else {
                return removed;
            }
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public Object delete(RubyHash hash, Object key) {
            notDesignedForCompilation();

            hash.checkFrozen(this);

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            // TODO(CS): seriously not correct - using Java's Object#equals

            final Object removed = store.remove(key);

            if (removed == null) {
                return getContext().getCoreLibrary().getNilObject();
            } else {
                return removed;
            }
        }

    }

    @CoreMethod(names = "each", needsBlock = true)
    @ImportGuards(HashGuards.class)
    public abstract static class EachNode extends YieldingCoreMethodNode {

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

        public EachNode(EachNode prev) {
            super(prev);
        }

        @Specialization(guards = "isNull")
        public RubyHash eachNull(RubyHash hash, RubyProc block) {
            notDesignedForCompilation();

            return hash;
        }

        @ExplodeLoop
        @Specialization(guards = "isObjectArray")
        public RubyHash eachObjectArray(VirtualFrame frame, RubyHash hash, RubyProc block) {
            notDesignedForCompilation();

            final Object[] store = (Object[]) hash.getStore();
            final int size = hash.getStoreSize();

            int count = 0;

            try {
                for (int n = 0; n < RubyHash.HASHES_SMALL; n++) {
                    if (CompilerDirectives.inInterpreter()) {
                        count++;
                    }

                    if (n < size) {
                        yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), store[n * 2], store[n * 2 + 1]));
                    }
                }
            } finally {
                if (CompilerDirectives.inInterpreter()) {
                    ((RubyRootNode) getRootNode()).reportLoopCountThroughBlocks(count);
                }
            }

            return hash;
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public RubyHash eachObjectLinkedHashMap(VirtualFrame frame, RubyHash hash, RubyProc block) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            int count = 0;

            try {
                for (Map.Entry<Object, Object> entry : store.entrySet()) {
                    if (CompilerDirectives.inInterpreter()) {
                        count++;
                    }

                    yield(frame, block, RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), entry.getKey(), entry.getValue()));
                }
            } finally {
                if (CompilerDirectives.inInterpreter()) {
                    ((RubyRootNode) getRootNode()).reportLoopCountThroughBlocks(count);
                }
            }

            return hash;
        }

    }

    @CoreMethod(names = "empty?")
    public abstract static class EmptyNode extends HashCoreMethodNode {

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

        public EmptyNode(EmptyNode prev) {
            super(prev);
        }

        @Specialization(guards = "isNull")
        public boolean emptyNull(RubyHash hash) {
            return true;
        }

        @Specialization(guards = "isObjectArray")
        public boolean emptyObjectArray(RubyHash hash) {
            return hash.getStoreSize() == 0;
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public boolean emptyObjectLinkedHashMap(RubyHash hash) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();
            return store.isEmpty();
        }

    }

    @CoreMethod(names = "initialize", needsBlock = true, optional = 1)
    public abstract static class InitializeNode extends HashCoreMethodNode {

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

        public InitializeNode(InitializeNode prev) {
            super(prev);
        }

        @Specialization
        public RubyNilClass initialize(RubyHash hash, UndefinedPlaceholder defaultValue, UndefinedPlaceholder block) {
            notDesignedForCompilation();
            hash.setStore(null, 0);
            hash.setDefaultBlock(null);
            return getContext().getCoreLibrary().getNilObject();
        }

        @Specialization
        public RubyNilClass initialize(RubyHash hash, UndefinedPlaceholder defaultValue, RubyProc block) {
            notDesignedForCompilation();
            hash.setStore(null, 0);
            hash.setDefaultBlock(block);
            return getContext().getCoreLibrary().getNilObject();
        }

        @Specialization
        public RubyNilClass initialize(RubyHash hash, Object defaultValue, UndefinedPlaceholder block) {
            notDesignedForCompilation();
            hash.setDefaultValue(defaultValue);
            return getContext().getCoreLibrary().getNilObject();
        }

    }

    @CoreMethod(names = "initialize_copy", visibility = Visibility.PRIVATE, required = 1)
    public abstract static class InitializeCopyNode extends HashCoreMethodNode {

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

        public InitializeCopyNode(InitializeCopyNode prev) {
            super(prev);
        }

        @Specialization(guards = "isOtherNull")
        public RubyHash dupNull(RubyHash self, RubyHash from) {
            notDesignedForCompilation();

            if (self == from) {
                return self;
            }

            self.setDefaultBlock(from.getDefaultBlock());
            self.setDefaultValue(from.getDefaultValue());
            self.setStore(null, 0);

            return self;
        }

        @Specialization(guards = "isOtherObjectArray")
        public RubyHash dupObjectArray(RubyHash self, RubyHash from) {
            notDesignedForCompilation();

            if (self == from) {
                return self;
            }

            final Object[] store = (Object[]) from.getStore();
            self.setStore(Arrays.copyOf(store, RubyHash.HASHES_SMALL * 2), store.length);
            self.setDefaultBlock(from.getDefaultBlock());
            self.setDefaultValue(from.getDefaultValue());

            return self;
        }

        @Specialization(guards = "isOtherObjectLinkedHashMap")
        public RubyHash dupObjectLinkedHashMap(RubyHash self, RubyHash from) {
            notDesignedForCompilation();

            if (self == from) {
                return self;
            }

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) from.getStore();
            self.setStore(new LinkedHashMap<>(store), store.size());
            self.setDefaultBlock(from.getDefaultBlock());
            self.setDefaultValue(from.getDefaultValue());

            return self;
        }

    }

    @CoreMethod(names = {"inspect", "to_s"})
    public abstract static class InspectNode extends HashCoreMethodNode {

        @Child protected DispatchHeadNode inspect;

        public InspectNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            inspect = new DispatchHeadNode(context);
        }

        public InspectNode(InspectNode prev) {
            super(prev);
            inspect = prev.inspect;
        }

        @Specialization(guards = "isNull")
        public RubyString inspectNull(RubyHash hash) {
            notDesignedForCompilation();

            return getContext().makeString("{}");
        }

        @Specialization(guards = "isObjectArray")
        public RubyString inspectObjectArray(VirtualFrame frame, RubyHash hash) {
            notDesignedForCompilation();

            final Object[] store = (Object[]) hash.getStore();

            final StringBuilder builder = new StringBuilder();

            builder.append("{");

            for (int n = 0; n < hash.getStoreSize(); n++) {
                if (n > 0) {
                    builder.append(", ");
                }

                // TODO(CS): to string

                builder.append(inspect.call(frame, store[n * 2], "inspect", null));
                builder.append("=>");
                builder.append(inspect.call(frame, store[n * 2 + 1], "inspect", null));
            }

            builder.append("}");

            return getContext().makeString(builder.toString());
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public RubyString inspectObjectLinkedHashMap(VirtualFrame frame, RubyHash hash) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            final StringBuilder builder = new StringBuilder();

            builder.append("{");

            boolean first = true;

            for (Map.Entry<Object, Object> entry : store.entrySet()) {
                if (first) {
                    first = false;
                } else {
                    builder.append(", ");
                }

                builder.append(inspect.call(frame, entry.getKey(), "inspect", null));
                builder.append("=>");
                builder.append(inspect.call(frame, entry.getValue(), "inspect", null));
            }

            builder.append("}");

            return getContext().makeString(builder.toString());
        }

    }

    @CoreMethod(names = "key?", required = 1)
    public abstract static class KeyNode extends HashCoreMethodNode {

        @Child protected DispatchHeadNode eqlNode;

        public KeyNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            eqlNode = new DispatchHeadNode(context);
        }

        public KeyNode(KeyNode prev) {
            super(prev);
            eqlNode = prev.eqlNode;
        }

        @Specialization(guards = "isNull")
        public boolean keyNull(RubyHash hash, Object key) {
            return false;
        }

        @Specialization(guards = "isObjectArray")
        public boolean keyObjectArray(VirtualFrame frame, RubyHash hash, Object key) {
            notDesignedForCompilation();

            final int size = hash.getStoreSize();
            final Object[] store = (Object[]) hash.getStore();

            for (int n = 0; n < store.length; n += 2) {
                if (n < size && eqlNode.callIsTruthy(frame, store[n], "eql?", null, key)) {
                    return true;
                }
            }

            return false;
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public boolean keyObjectLinkedHashMap(RubyHash hash, Object key) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            // TODO(CS): seriously not correct - using Java's Object#equals

            return store.containsKey(key);
        }

    }

    @CoreMethod(names = "keys")
    public abstract static class KeysNode extends HashCoreMethodNode {

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

        public KeysNode(KeysNode prev) {
            super(prev);
        }

        @Specialization(guards = "isNull")
        public RubyArray keysNull(RubyHash hash) {
            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0);
        }

        @Specialization(guards = "isObjectArray")
        public RubyArray keysObjectArray(RubyHash hash) {
            notDesignedForCompilation();

            final Object[] store = (Object[]) hash.getStore();

            final Object[] keys = new Object[hash.getStoreSize()];

            for (int n = 0; n < keys.length; n++) {
                keys[n] = store[n * 2];
            }

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), keys, keys.length);
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public RubyArray keysObjectLinkedHashMap(RubyHash hash) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            final Object[] keys = new Object[store.size()];

            int n = 0;

            for (Object key : store.keySet()) {
                keys[n] = key;
                n++;
            }

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), keys, keys.length);
        }

    }

    @CoreMethod(names = {"map", "collect"}, needsBlock = true)
    @ImportGuards(HashGuards.class)
    public abstract static class MapNode extends YieldingCoreMethodNode {

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

        public MapNode(MapNode prev) {
            super(prev);
        }

        @ExplodeLoop
        @Specialization(guards = "isObjectArray")
        public RubyArray mapObjectArray(VirtualFrame frame, RubyHash hash, RubyProc block) {
            final Object[] store = (Object[]) hash.getStore();
            final int size = hash.getStoreSize();

            final int resultSize = store.length / 2;
            final Object[] result = new Object[resultSize];

            int count = 0;

            try {
                for (int n = 0; n < RubyHash.HASHES_SMALL; n++) {
                    if (n < size) {
                        final Object key = store[n * 2];
                        final Object value = store[n * 2 + 1];
                        result[n] = yield(frame, block, key, value);

                        if (CompilerDirectives.inInterpreter()) {
                            count++;
                        }
                    }
                }
            } finally {
                if (CompilerDirectives.inInterpreter()) {
                    ((RubyRootNode) getRootNode()).reportLoopCountThroughBlocks(count);
                }
            }

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), result, resultSize);
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public RubyArray mapObjectLinkedHashMap(VirtualFrame frame, RubyHash hash, RubyProc block) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            final RubyArray result = new RubyArray(getContext().getCoreLibrary().getArrayClass());

            int count = 0;

            try {
                for (Map.Entry<Object, Object> entry : store.entrySet()) {
                    if (CompilerDirectives.inInterpreter()) {
                        count++;
                    }

                    result.slowPush(yield(frame, block, entry.getKey(), entry.getValue()));
                }
            } finally {
                if (CompilerDirectives.inInterpreter()) {
                    ((RubyRootNode) getRootNode()).reportLoopCountThroughBlocks(count);
                }
            }

            return result;
        }

    }

    @CoreMethod(names = "merge", required = 1)
    public abstract static class MergeNode extends HashCoreMethodNode {

        @Child protected DispatchHeadNode eqlNode;

        private final BranchProfile nothingFromFirstProfile = new BranchProfile();
        private final BranchProfile considerNothingFromSecondProfile = new BranchProfile();
        private final BranchProfile nothingFromSecondProfile = new BranchProfile();
        private final BranchProfile considerResultIsSmallProfile = new BranchProfile();
        private final BranchProfile resultIsSmallProfile = new BranchProfile();

        private final int smallHashSize = RubyHash.HASHES_SMALL;

        public MergeNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection);
            eqlNode = new DispatchHeadNode(context);
        }

        public MergeNode(MergeNode prev) {
            super(prev);
            eqlNode = prev.eqlNode;
        }

        @Specialization(guards = {"isObjectArray", "isOtherNull"})
        public RubyHash mergeObjectArrayNull(RubyHash hash, RubyHash other) {
            final Object[] store = (Object[]) hash.getStore();
            final Object[] copy = Arrays.copyOf(store, RubyHash.HASHES_SMALL * 2);

            return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), copy, hash.getStoreSize());
        }

        @ExplodeLoop
        @Specialization(guards = {"isObjectArray", "isOtherObjectArray"})
        public RubyHash mergeObjectArrayObjectArray(VirtualFrame frame, RubyHash hash, RubyHash other) {
            // TODO(CS): what happens with the default block here? Which side does it get merged from?

            final Object[] storeA = (Object[]) hash.getStore();
            final int storeASize = hash.getStoreSize();

            final Object[] storeB = (Object[]) other.getStore();
            final int storeBSize = hash.getStoreSize();

            final boolean[] mergeFromA = new boolean[storeASize];
            int mergeFromACount = 0;

            for (int a = 0; a < RubyHash.HASHES_SMALL; a++) {
                if (a < storeASize) {
                    boolean merge = true;

                    for (int b = 0; b < RubyHash.HASHES_SMALL; b++) {
                        if (b < storeBSize) {
                            if (eqlNode.callIsTruthy(frame, storeA[a * 2], "eql?", null, storeB[b * 2])) {
                                merge = false;
                                break;
                            }
                        }
                    }

                    if (merge) {
                        mergeFromACount++;
                    }

                    mergeFromA[a] = merge;
                }
            }

            if (mergeFromACount == 0) {
                nothingFromFirstProfile.enter();
                return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize);
            }

            considerNothingFromSecondProfile.enter();

            if (mergeFromACount == storeB.length) {
                nothingFromSecondProfile.enter();
                return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), Arrays.copyOf(storeB, RubyHash.HASHES_SMALL * 2), storeBSize);
            }

            considerResultIsSmallProfile.enter();

            final int mergedSize = storeBSize + mergeFromACount;

            if (storeBSize + mergeFromACount <= smallHashSize) {
                resultIsSmallProfile.enter();

                final Object[] merged = new Object[RubyHash.HASHES_SMALL * 2];

                int index = 0;

                for (int n = 0; n < storeASize; n++) {
                    if (mergeFromA[n]) {
                        merged[index] = storeA[n * 2];
                        merged[index + 1] = storeA[n * 2 + 1];
                        index += 2;
                    }
                }

                for (int n = 0; n < storeBSize; n++) {
                    merged[index] = storeB[n * 2];
                    merged[index + 1] = storeB[n * 2 + 1];
                    index += 2;
                }

                return new RubyHash(getContext().getCoreLibrary().getHashClass(), hash.getDefaultBlock(), hash.getDefaultValue(), merged, mergedSize);
            }

            CompilerDirectives.transferToInterpreter();
            throw new UnsupportedOperationException();
        }

    }

    @CoreMethod(names = "size")
    public abstract static class SizeNode extends HashCoreMethodNode {

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

        public SizeNode(SizeNode prev) {
            super(prev);
        }

        @Specialization(guards = "isNull")
        public int sizeNull(RubyHash hash) {
            return 0;
        }

        @Specialization(guards = "isObjectArray")
        public int sizeObjectArray(RubyHash hash) {
            return hash.getStoreSize();
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public int sizeObjectLinkedHashMap(RubyHash hash) {
            notDesignedForCompilation();
            return ((LinkedHashMap<Object, Object>) hash.getStore()).size();
        }

    }

    @CoreMethod(names = "values")
    public abstract static class ValuesNode extends HashCoreMethodNode {

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

        public ValuesNode(ValuesNode prev) {
            super(prev);
        }

        @Specialization(guards = "isNull")
        public RubyArray valuesNull(RubyHash hash) {
            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0);
        }

        @Specialization(guards = "isObjectArray")
        public RubyArray valuesObjectArray(RubyHash hash) {
            final Object[] store = (Object[]) hash.getStore();

            final Object[] values = new Object[hash.getStoreSize()];

            for (int n = 0; n < values.length; n++) {
                values[n] = store[n * 2 + 1];
            }

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), values, values.length);
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public RubyArray valuesObjectLinkedHashMap(RubyHash hash) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();

            final Object[] values = new Object[store.size()];

            int n = 0;

            for (Object value : store.values()) {
                values[n] = value;
                n++;
            }

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), values, values.length);
        }

    }

    @CoreMethod(names = "to_a")
    public abstract static class ToArrayNode extends HashCoreMethodNode {

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

        public ToArrayNode(ToArrayNode prev) {
            super(prev);
        }

        @Specialization(guards = "isNull")
        public RubyArray toArrayNull(RubyHash hash) {
            notDesignedForCompilation();

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), null, 0);
        }

        @Specialization(guards = "isObjectArray")
        public RubyArray toArrayObjectArray(RubyHash hash) {
            notDesignedForCompilation();

            final Object[] store = (Object[]) hash.getStore();
            final int size = hash.getStoreSize();
            final Object[] pairs = new Object[size];

            for (int n = 0; n < size; n++) {
                pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), store[n * 2], store[n * 2 + 1]);
            }

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), pairs, size);
        }

        @Specialization(guards = "isObjectLinkedHashMap")
        public RubyArray toArrayLinkedHashMap(RubyHash hash) {
            notDesignedForCompilation();

            final LinkedHashMap<Object, Object> store = (LinkedHashMap<Object, Object>) hash.getStore();
            final int size = hash.getStoreSize();
            final Object[] pairs = new Object[size];
            int n = 0;

            for (Map.Entry<Object, Object> pair : store.entrySet()) {
                pairs[n] = RubyArray.fromObjects(getContext().getCoreLibrary().getArrayClass(), pair.getKey(), pair.getValue());
                n += 1;
            }

            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), pairs, size);
        }

    }

}
TOP

Related Classes of org.jruby.truffle.nodes.core.HashNodes$KeysNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.