GeneralDivModNode.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.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.BranchProfile;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyBignum;

import java.math.BigInteger;

public class GeneralDivModNode extends RubyNode {

    @Child protected FixnumOrBignumNode fixnumOrBignumQuotient;
    @Child protected FixnumOrBignumNode fixnumOrBignumRemainder;

    private final BranchProfile bZeroProfile = BranchProfile.create();
    private final BranchProfile bMinusOneProfile = BranchProfile.create();
    private final BranchProfile bigIntegerFixnumProfile = BranchProfile.create();
    private final BranchProfile useFixnumPairProfile = BranchProfile.create();
    private final BranchProfile useObjectPairProfile = BranchProfile.create();

    public GeneralDivModNode(RubyContext context, SourceSection sourceSection) {
        super(context, sourceSection);
        fixnumOrBignumQuotient = new FixnumOrBignumNode(context, sourceSection);
        fixnumOrBignumRemainder = new FixnumOrBignumNode(context, sourceSection);
    }

    public RubyArray execute(int a, int b) {
        return divMod(a, b);
    }

    public RubyArray execute(int a, long b) {
        return divMod(a, b);
    }

    public RubyArray execute(int a, BigInteger b) {
        return divMod(BigInteger.valueOf(a), b);
    }

    public RubyArray execute(long a, int b) {
        return divMod(a, b);
    }

    public RubyArray execute(long a, long b) {
        return divMod(a, b);
    }

    public RubyArray execute(long a, RubyBignum b) {
        return divMod(BigInteger.valueOf(a), b.bigIntegerValue());
    }

    public RubyArray execute(RubyBignum a, int b) {
        return divMod(a.bigIntegerValue(), BigInteger.valueOf(b));
    }

    public RubyArray execute(RubyBignum a, long b) {
        return divMod(a.bigIntegerValue(), BigInteger.valueOf(b));
    }

    public RubyArray execute(RubyBignum a, RubyBignum b) {
        return divMod(a.bigIntegerValue(), b.bigIntegerValue());
    }

    /*
     * div-mod algorithms copied from org.jruby.RubyFixnum and org.jruby.RubyBignum. See license and contributors there.
     */

    @CompilerDirectives.TruffleBoundary
    private RubyArray divMod(long a, long b) {
        if (b == 0) {
            bZeroProfile.enter();
            throw new ArithmeticException("divide by zero");
        }

        long mod;
        Object integerDiv;

        if (b == -1) {
            bMinusOneProfile.enter();

            if (a == Long.MIN_VALUE) {
                integerDiv = BigInteger.valueOf(a).negate();
            } else {
                integerDiv = -a;
            }
            mod = 0;
        } else {
            long div = a / b;
            mod = a - b * div;
            if (mod < 0 && b > 0 || mod > 0 && b < 0) {
                div -= 1;
                mod += b;
            }
            integerDiv = div;
        }

        if (integerDiv instanceof Long && ((long) integerDiv) >= Integer.MIN_VALUE && ((long) integerDiv) <= Integer.MAX_VALUE && mod >= Integer.MIN_VALUE && mod <= Integer.MAX_VALUE) {
            useFixnumPairProfile.enter();
            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), new int[]{(int) (long) integerDiv, (int) mod}, 2);
        } else if (integerDiv instanceof Long) {
            useObjectPairProfile.enter();
            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), new Object[]{integerDiv, mod}, 2);
        } else {
            useObjectPairProfile.enter();
            return new RubyArray(getContext().getCoreLibrary().getArrayClass(), new Object[]{
                    fixnumOrBignumQuotient.fixnumOrBignum(create((BigInteger) integerDiv)),
                    mod}, 2);
        }
    }

    @CompilerDirectives.TruffleBoundary
    private RubyArray divMod(BigInteger a, BigInteger b) {
        if (b.signum() == 0) {
            bZeroProfile.enter();
            throw new ArithmeticException("divide by zero");
        }

        final BigInteger[] bigIntegerResults = a.divideAndRemainder(b);

        if ((a.signum() * b.signum()) == -1 && bigIntegerResults[1].signum() != 0) {
            bigIntegerFixnumProfile.enter();
            bigIntegerResults[0] = bigIntegerResults[0].subtract(BigInteger.ONE);
            bigIntegerResults[1] = b.add(bigIntegerResults[1]);
        }

        return new RubyArray(getContext().getCoreLibrary().getArrayClass(), new Object[]{
                fixnumOrBignumQuotient.fixnumOrBignum(create(bigIntegerResults[0])),
                fixnumOrBignumRemainder.fixnumOrBignum(create(bigIntegerResults[1]))}, 2);
    }

    public RubyBignum create(BigInteger value) {
        return new RubyBignum(getContext().getCoreLibrary().getBignumClass(), value);
    }

    @Override
    public Object execute(VirtualFrame frame) {
        throw new UnsupportedOperationException();
    }

}