ObjectIDOperations.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.runtime;

import com.oracle.truffle.api.ExactMath;
import org.jruby.truffle.runtime.core.RubyBignum;

import java.math.BigInteger;

/**
 * <pre>
 * Object IDs distribution
 *
 * We try to respect MRI scheme when it makes sense (Fixnum for the moment).
 * Have a look at include/ruby/ruby.h below ruby_special_consts.
 *
 * Encoding for Fixnum (long):
 * ... 0000 = false
 * ... 0010 = true
 * ... 0100 = nil
 *
 * ... xxx1 = Fixnum of value (id-1)/2 if -2^62 <= value < 2^62
 * ... xxx0 = BasicObject generated id (for id > 4)
 *
 * Encoding for Bignum:
 * ... 0001 | 64-bit long = Fixnum if value < -2^62 or value >= 2^62
 * ... 0010 | 64-bit raw double bits = Float
 * </pre>
 */
public abstract class ObjectIDOperations {

    public static final int FALSE = 0;
    public static final int TRUE = 2;
    public static final int NIL = 4;
    public static final int FIRST_OBJECT_ID = 6;

    private static final BigInteger LARGE_FIXNUM_FLAG = BigInteger.ONE.shiftLeft(64);
    private static final BigInteger FLOAT_FLAG = BigInteger.ONE.shiftLeft(65);

    private static final long SMALL_FIXNUM_MIN = -(1L << 62);
    private static final long SMALL_FIXNUM_MAX = (1L << 62) - 1;

    // primitive => ID

    public static boolean isSmallFixnum(long fixnum) {
        // TODO: optimize
        return SMALL_FIXNUM_MIN <= fixnum && fixnum <= SMALL_FIXNUM_MAX;
    }

    public static long smallFixnumToIDOverflow(long fixnum) throws ArithmeticException{
        return ExactMath.addExact(ExactMath.multiplyExact(fixnum, 2), 1);
    }

    public static long smallFixnumToID(long fixnum) {
        assert isSmallFixnum(fixnum);
        return fixnum * 2 + 1;
    }

    public static RubyBignum largeFixnumToID(RubyContext context, long fixnum) {
        assert !isSmallFixnum(fixnum);
        return new RubyBignum(context.getCoreLibrary().getBignumClass(), BigInteger.valueOf(fixnum).or(LARGE_FIXNUM_FLAG));
    }

    public static RubyBignum floatToID(RubyContext context, double value) {
        long bits = Double.doubleToRawLongBits(value);
        return new RubyBignum(context.getCoreLibrary().getBignumClass(), BigInteger.valueOf(bits).or(FLOAT_FLAG));
    }

    // ID => primitive

    public static boolean isSmallFixnumID(long id) {
        return id % 2 != 0;
    }

    public static long toFixnum(long id) {
        return (id - 1) / 2;
    }

    public static boolean isLargeFixnumID(RubyBignum id) {
        return !id.bigIntegerValue().and(LARGE_FIXNUM_FLAG).equals(BigInteger.ZERO);
    }

    public static long toFixnum(RubyBignum id) {
        return id.longValue();
    }

    public static boolean isFloatID(RubyBignum id) {
        return !id.bigIntegerValue().and(FLOAT_FLAG).equals(BigInteger.ZERO);
    }

    public static double toFloat(RubyBignum id) {
        return Double.longBitsToDouble(id.longValue());
    }

}