ArrayBuilderNode.java

/*
 * Copyright (c) 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 com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.nodes.Node;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.util.ArrayUtils;
import org.jruby.util.cli.Options;

import java.util.Arrays;

/*
 * TODO(CS): how does this work when when multithreaded? Could a node get replaced by someone else and
 * then suddenly you're passing it a store type it doesn't expect?
 */

public abstract class ArrayBuilderNode extends Node {

    public static final int ARRAYS_UNINITIALIZED_SIZE = Options.TRUFFLE_ARRAYS_UNINITIALIZED_SIZE.load();

    private final RubyContext context;

    public ArrayBuilderNode(RubyContext context) {
        this.context = context;
    }

    public abstract Object start();
    public abstract Object start(int length);
    public abstract Object ensure(Object store, int length);
    public abstract Object append(Object store, int index, RubyArray array);
    public abstract Object append(Object store, int index, Object value);
    public abstract Object finish(Object store, int length);

    protected RubyContext getContext() {
        return context;
    }

    public static class UninitializedArrayBuilderNode extends ArrayBuilderNode {

        private boolean couldUseInteger = true;
        private boolean couldUseLong = true;
        private boolean couldUseDouble = true;

        public UninitializedArrayBuilderNode(RubyContext context) {
            super(context);
        }

        public void resume(Object[] store) {
            for (Object value : store) {
                screen(value);
            }
        }

        @Override
        public Object start() {
            CompilerDirectives.transferToInterpreter();
            return new Object[ARRAYS_UNINITIALIZED_SIZE];
        }

        @Override
        public Object start(int length) {
            CompilerDirectives.transferToInterpreter();
            return new Object[length];
        }

        @Override
        public Object ensure(Object store, int length) {
            // All appends go through append(Object, int, Object), which is always happy to make space
            return store;
        }

        @Override
        public Object append(Object store, int index, RubyArray array) {
            CompilerDirectives.transferToInterpreter();

            for (Object value : array.slowToArray()) {
                store = append(store, index, value);
                index++;
            }

            return store;
        }

        @Override
        public Object append(Object store, int index, Object value) {
            CompilerDirectives.transferToInterpreter();

            screen(value);

            Object[] storeArray = (Object[]) store;

            if (index >= storeArray.length) {
                storeArray = Arrays.copyOf(storeArray, ArrayUtils.capacity(storeArray.length, index + 1));
            }

            storeArray[index] = value;
            return storeArray;
        }

        @Override
        public Object finish(Object store, int length) {
            if (couldUseInteger) {
                replace(new IntegerArrayBuilderNode(getContext(), length));
                return ArrayUtils.unboxInteger((Object[]) store, length);
            } else if (couldUseLong) {
                replace(new LongArrayBuilderNode(getContext(), length));
                return ArrayUtils.unboxLong((Object[]) store, length);
            } else if (couldUseDouble) {
                replace(new DoubleArrayBuilderNode(getContext(), length));
                return ArrayUtils.unboxDouble((Object[]) store, length);
            } else {
                replace(new ObjectArrayBuilderNode(getContext(), length));
                return store;
            }
        }

        private void screen(Object value) {
            if (value instanceof Integer) {
                couldUseDouble = false;
            } else if (value instanceof Long) {
                couldUseInteger = false;
                couldUseDouble = false;
            } else if (value instanceof Double) {
                couldUseInteger = false;
                couldUseLong = false;
            } else {
                couldUseInteger = false;
                couldUseLong = false;
                couldUseDouble = false;
            }
        }

    }

    public static class IntegerArrayBuilderNode extends ArrayBuilderNode {

        private final int expectedLength;

        @CompilerDirectives.CompilationFinal private boolean hasAppendedIntegerArray = false;
        @CompilerDirectives.CompilationFinal private boolean hasAppendedObjectArray = false;

        public IntegerArrayBuilderNode(RubyContext context, int expectedLength) {
            super(context);
            this.expectedLength = expectedLength;
        }

        @Override
        public Object start() {
            return new int[expectedLength];
        }

        @Override
        public Object start(int length) {
            if (length > expectedLength) {
                CompilerDirectives.transferToInterpreter();

                final UninitializedArrayBuilderNode newNode = new UninitializedArrayBuilderNode(getContext());
                replace(newNode);
                return newNode.start(length);
            }

            return new int[expectedLength];
        }

        @Override
        public Object ensure(Object store, int length) {
            if (length > ((int[]) store).length) {
                CompilerDirectives.transferToInterpreter();

                final Object[] newStore = ArrayUtils.box((int[]) store);

                final UninitializedArrayBuilderNode newNode = new UninitializedArrayBuilderNode(getContext());
                replace(newNode);
                newNode.resume(newStore);
                return newNode.ensure(newStore, length);
            }

            return store;
        }

        @Override
        public Object append(Object store, int index, RubyArray array) {
            Object otherStore = array.getStore();

            if (otherStore == null) {
                return store;
            }

            if (hasAppendedIntegerArray && otherStore instanceof int[]) {
                System.arraycopy(otherStore, 0, store, index, array.getSize());
                return store;
            }

            if (hasAppendedObjectArray && otherStore instanceof Object[]) {
                System.arraycopy(otherStore, 0, store, index, array.getSize());
                return store;
            }

            CompilerDirectives.transferToInterpreterAndInvalidate();

            if (otherStore instanceof int[]) {
                hasAppendedIntegerArray = true;
                System.arraycopy(otherStore, 0, store, index, array.getSize());
                return store;
            }

            if (otherStore instanceof Object[]) {
                hasAppendedObjectArray = true;
                System.arraycopy(otherStore, 0, store, index, array.getSize());
                return store;
            }

            throw new UnsupportedOperationException(array.getStore().getClass().getName());
        }

        @Override
        public Object append(Object store, int index, Object value) {
            // TODO(CS): inject probability
            if (value instanceof Integer) {
                ((int[]) store)[index] = (int) value;
                return store;
            } else {
                CompilerDirectives.transferToInterpreter();

                replace(new ObjectArrayBuilderNode(getContext(), expectedLength));

                // TODO(CS): not sure why this happens - need to investigate

                final Object[] newStore;

                if (store instanceof int[]) {
                    newStore = ArrayUtils.box((int[]) store);
                } else if (store instanceof Object[]) {
                    newStore = (Object[]) store;
                } else {
                    throw new UnsupportedOperationException();
                }

                newStore[index] = value;
                return newStore;
            }
        }

        public Object finish(Object store, int length) {
            return store;
        }

    }

    public static class LongArrayBuilderNode extends ArrayBuilderNode {

        private final int expectedLength;

        public LongArrayBuilderNode(RubyContext context, int expectedLength) {
            super(context);
            this.expectedLength = expectedLength;
        }

        @Override
        public Object start() {
            return new long[expectedLength];
        }

        @Override
        public Object start(int length) {
            if (length > expectedLength) {
                CompilerDirectives.transferToInterpreter();

                final UninitializedArrayBuilderNode newNode = new UninitializedArrayBuilderNode(getContext());
                replace(newNode);
                return newNode.start(length);
            }

            return new long[expectedLength];
        }

        @Override
        public Object ensure(Object store, int length) {
            CompilerDirectives.transferToInterpreter();
            throw new UnsupportedOperationException();
        }

        @Override
        public Object append(Object store, int index, RubyArray array) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Object append(Object store, int index, Object value) {
            // TODO(CS): inject probability
            if (value instanceof Long) {
                ((long[]) store)[index] = (long) value;
                return store;
            } else if (value instanceof Integer) {
                ((long[]) store)[index] = (int) value;
                return store;
            } else {
                CompilerDirectives.transferToInterpreter();

                replace(new ObjectArrayBuilderNode(getContext(), expectedLength));

                final Object[] newStore = ArrayUtils.box((long[]) store);
                newStore[index] = value;
                return newStore;
            }
        }

        public Object finish(Object store, int length) {
            return store;
        }

    }

    public static class DoubleArrayBuilderNode extends ArrayBuilderNode {

        private final int expectedLength;

        public DoubleArrayBuilderNode(RubyContext context, int expectedLength) {
            super(context);
            this.expectedLength = expectedLength;
        }

        @Override
        public Object start() {
            return new double[expectedLength];
        }

        @Override
        public Object start(int length) {
            if (length > expectedLength) {
                CompilerDirectives.transferToInterpreter();

                final UninitializedArrayBuilderNode newNode = new UninitializedArrayBuilderNode(getContext());
                replace(newNode);
                return newNode.start(length);
            }

            return new double[expectedLength];
        }

        @Override
        public Object ensure(Object store, int length) {
            CompilerDirectives.transferToInterpreter();
            throw new UnsupportedOperationException();
        }

        @Override
        public Object append(Object store, int index, RubyArray array) {
            CompilerDirectives.transferToInterpreter();
            throw new UnsupportedOperationException();
        }

        @Override
        public Object append(Object store, int index, Object value) {
            // TODO(CS): inject probability
            if (value instanceof Double) {
                ((double[]) store)[index] = (double) value;
                return store;
            } else {
                CompilerDirectives.transferToInterpreter();

                replace(new ObjectArrayBuilderNode(getContext(), expectedLength));

                final Object[] newStore = ArrayUtils.box((double[]) store);
                newStore[index] = value;
                return newStore;
            }
        }

        public Object finish(Object store, int length) {
            return store;
        }

    }

    public static class ObjectArrayBuilderNode extends ArrayBuilderNode {

        private final int expectedLength;

        @CompilerDirectives.CompilationFinal private boolean hasAppendedObjectArray = false;

        public ObjectArrayBuilderNode(RubyContext context, int expectedLength) {
            super(context);
            this.expectedLength = expectedLength;
        }

        @Override
        public Object start() {
            return new Object[expectedLength];
        }

        @Override
        public Object start(int length) {
            if (length > expectedLength) {
                CompilerDirectives.transferToInterpreter();

                final UninitializedArrayBuilderNode newNode = new UninitializedArrayBuilderNode(getContext());
                replace(newNode);
                return newNode.start(length);
            }

            return new Object[expectedLength];
        }

        @Override
        public Object ensure(Object store, int length) {
            if (length > ((Object[]) store).length) {
                CompilerDirectives.transferToInterpreter();

                final UninitializedArrayBuilderNode newNode = new UninitializedArrayBuilderNode(getContext());
                replace(newNode);
                newNode.resume((Object[]) store);
                return newNode.ensure(store, length);
            }

            return store;
        }

        @Override
        public Object append(Object store, int index, RubyArray array) {
            Object otherStore = array.getStore();

            if (otherStore == null) {
                return store;
            }

            if (hasAppendedObjectArray && otherStore instanceof Object[]) {
                System.arraycopy(otherStore, 0, store, index, array.getSize());
                return store;
            }

            CompilerDirectives.transferToInterpreterAndInvalidate();

            if (otherStore instanceof Object[]) {
                hasAppendedObjectArray = true;
                System.arraycopy(otherStore, 0, store, index, array.getSize());
                return store;
            }

            throw new UnsupportedOperationException(array.getStore().getClass().getName());
        }

        @Override
        public Object append(Object store, int index, Object value) {
            ((Object[]) store)[index] = value;
            return store;
        }

        public Object finish(Object store, int length) {
            return store;
        }

    }

}