HashLiteralNode.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.literal;

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.dispatch.DispatchHeadNode;
import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyHash;
import org.jruby.truffle.runtime.core.RubyString;
import org.jruby.truffle.runtime.hash.HashOperations;
import org.jruby.truffle.runtime.hash.KeyValue;

import java.util.ArrayList;
import java.util.List;

public abstract class HashLiteralNode extends RubyNode {

    @Children protected final RubyNode[] keyValues;
    @Child protected DispatchHeadNode dupNode;
    @Child protected DispatchHeadNode freezeNode;

    protected HashLiteralNode(RubyContext context, SourceSection sourceSection, RubyNode[] keyValues) {
        super(context, sourceSection);
        assert keyValues.length % 2 == 0;
        this.keyValues = keyValues;
        dupNode = new DispatchHeadNode(context);
        freezeNode = new DispatchHeadNode(context);
    }

    public static HashLiteralNode create(RubyContext context, SourceSection sourceSection, RubyNode[] keyValues) {
        if (keyValues.length == 0) {
            return new EmptyHashLiteralNode(context, sourceSection);
        } else if (keyValues.length <= HashOperations.SMALL_HASH_SIZE * 2) {
            return new SmallHashLiteralNode(context, sourceSection, keyValues);
        } else {
            return new GenericHashLiteralNode(context, sourceSection, keyValues);
        }
    }

    @Override
    public abstract RubyHash executeRubyHash(VirtualFrame frame);

    @Override
    public Object execute(VirtualFrame frame) {
        return executeRubyHash(frame);
    }

    @Override
    public void executeVoid(VirtualFrame frame) {
        for (RubyNode child : keyValues) {
            child.executeVoid(frame);
        }
    }

    public static class EmptyHashLiteralNode extends HashLiteralNode {

        public EmptyHashLiteralNode(RubyContext context, SourceSection sourceSection) {
            super(context, sourceSection, new RubyNode[]{});
        }

        @ExplodeLoop
        @Override
        public RubyHash executeRubyHash(VirtualFrame frame) {
            return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, null, 0, null);
        }

    }

    public static class SmallHashLiteralNode extends HashLiteralNode {

        @Child protected PredicateDispatchHeadNode equalNode;

        public SmallHashLiteralNode(RubyContext context, SourceSection sourceSection, RubyNode[] keyValues) {
            super(context, sourceSection, keyValues);
            equalNode = new PredicateDispatchHeadNode(context);
        }

        @ExplodeLoop
        @Override
        public RubyHash executeRubyHash(VirtualFrame frame) {
            final Object[] storage = new Object[HashOperations.SMALL_HASH_SIZE * 2];

            int end = 0;

            initializers: for (int n = 0; n < keyValues.length; n += 2) {
                Object key = keyValues[n].execute(frame);

                if (key instanceof RubyString && !((RubyString) key).isFrozen()) {
                    key = freezeNode.call(frame, dupNode.call(frame, key, "dup", null), "freeze", null);
                }

                final Object value = keyValues[n + 1].execute(frame);

                for (int i = 0; i < n; i += 2) {
                    if (i < end && equalNode.call(frame, key, "eql?", null, storage[i])) {
                        storage[i + 1] = value;
                        continue initializers;
                    }
                }

                storage[end] = key;
                storage[end + 1] = value;
                end += 2;
            }

            return new RubyHash(getContext().getCoreLibrary().getHashClass(), null, null, storage, end / 2, null);
        }

    }

    public static class GenericHashLiteralNode extends HashLiteralNode {

        public GenericHashLiteralNode(RubyContext context, SourceSection sourceSection, RubyNode[] keyValues) {
            super(context, sourceSection, keyValues);
        }

        @Override
        public RubyHash executeRubyHash(VirtualFrame frame) {
            notDesignedForCompilation();

            final List<KeyValue> entries = new ArrayList<>();

            for (int n = 0; n < keyValues.length; n += 2) {
                final Object key = keyValues[n].execute(frame);
                final Object value = keyValues[n + 1].execute(frame);
                entries.add(new KeyValue(key, value));
            }

            return HashOperations.verySlowFromEntries(getContext(), entries);
        }

    }

}