/*
 * Copyright 2008-2009 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package java.dyn;

import impl.java.dyn.util.Wrappers;

/**
 * Shared information for a group of method types, which differ
 * only by reference types, and therefore share a common erasure
 * and wrapping.
 * <p>
 * For an empirical discussion of the structure of method types,
 * see <a href="http://groups.google.com/group/jvm-languages/browse_thread/thread/ac9308ae74da9b7e/">
 * the thread "Avoiding Boxing" on jvm-languages</a>.
 * There are approximately 2000 distinct erased method types in the JDK.
 * There are a little over 10 times that number of unerased types.
 * No more than half of these are likely to be loaded at once.
 * @author John Rose
 */
class MethodTypeForm {
    final int[] argToSlotTable, slotToArgTable;
    final long argCounts;                // packed slot & value counts
    final long primCounts;               // packed prim & double counts
    final int vmslots;                   // total number of parameter slots
    final MethodType erasedType;         // the canonical erasure
    final MethodType wrappedType;        // erasure, with primitives wrapped

    public static MethodTypeForm of(MethodType type) {
        return type.form;
    }

    private MethodTypeForm(MethodType erase) {
        this.erasedType = erase;
        MethodType wt = canonType(erase, WRAP);
        this.wrappedType = (wt == null) ? erase : wt;

        int ptypeCount = erase.ptypes.length;
        int pslotCount = ptypeCount;            // temp. estimate
        int rtypeCount = 1;                     // temp. estimate
        int rslotCount = 1;                     // temp. estimate

        int[] argToSlotTab = null, slotToArgTab = null;

        // Walk the argument types, looking for primitives.
        if (wt != null) {
            int pac = 0, lac = 0, prc = 0, lrc = 0;
            Class<?> epts[] = erase.ptypes;
            for (int i = 0; i < epts.length; i++) {
                Class<?> pt = epts[i];
                if (pt != Object.class) {
                    assert(pt.isPrimitive());
                    ++pac;
                    if (hasTwoArgSlots(pt))  ++lac;
                }
            }
            pslotCount += lac;                  // #slots = #args + #longs
            Class<?> rt = erase.rtype;
            if (rt != Object.class) {
                ++prc;          // even void.class counts as a prim here
                if (hasTwoArgSlots(rt))  ++lrc;
                // adjust #slots, #args
                if (rt == void.class)
                    rtypeCount = rslotCount = 0;
                else
                    rslotCount += lrc;
            }
            if (lac != 0) {
                int slot = ptypeCount + lac;
                slotToArgTab = new int[slot+1];
                argToSlotTab = new int[1+ptypeCount];
                argToSlotTab[0] = slot;  // argument "-1" is past end of slots
                for (int i = 0; i < epts.length; i++) {
                    Class<?> pt = epts[i];
                    if (hasTwoArgSlots(pt))  --slot;
                    --slot;
                    slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note
                    argToSlotTab[1+i]  = slot;
                }
                assert(slot == 0);  // filled the table
            }
            this.primCounts = pack(lrc, prc, lac, pac);
        } else {
            this.primCounts = 0;
        }
        this.argCounts = pack(rslotCount, rtypeCount, pslotCount, ptypeCount);
        if (slotToArgTab == null) {
            int slot = ptypeCount; // first arg is deepest in stack
            slotToArgTab = new int[slot+1];
            argToSlotTab = new int[1+ptypeCount];
            argToSlotTab[0] = slot;  // argument "-1" is past end of slots
            for (int i = 0; i < ptypeCount; i++) {
                --slot;
                slotToArgTab[slot] = i+1; // "+1" see argSlotToParameter note
                argToSlotTab[1+i]  = slot;
            }
        }
        this.argToSlotTable = argToSlotTab;
        this.slotToArgTable = slotToArgTab;

        if (pslotCount >= 256)  throw new IllegalArgumentException("too many arguments");

        // send a few bits down to the JVM:
        this.vmslots = parameterSlotCount();
    }

    private static boolean hasTwoArgSlots(Class<?> type) {
        return type == long.class || type == double.class;
    }

    private static long pack(int a, int b, int c, int d) {
        assert(((a|b|c|d) & ~0xFFFF) == 0);
        long hw = ((a << 16) | b), lw = ((c << 16) | d);
        return (hw << 32) | lw;
    }
    private static char unpack(long packed, int word) { // word==0 => return a, ==3 => return d
        assert(word <= 3);
        return (char)(packed >> ((3-word) * 16));
    }

    public int parameterCount() {                      // # outgoing values
        return unpack(argCounts, 3);
    }
    public int parameterSlotCount() {                  // # outgoing interpreter slots
        return unpack(argCounts, 2);
    }
    public int returnCount() {                         // = 0 (V), or 1
        return unpack(argCounts, 1);
    }
    public int returnSlotCount() {                     // = 0 (V), 2 (J/D), or 1
        return unpack(argCounts, 0);
    }
    public int primitiveParameterCount() {
        return unpack(primCounts, 3);
    }
    public int longPrimitiveParameterCount() {
        return unpack(primCounts, 2);
    }
    public int primitiveReturnCount() {                // = 0 (obj), or 1
        return unpack(primCounts, 1);
    }
    public int longPrimitiveReturnCount() {            // = 1 (J/D), or 0
        return unpack(primCounts, 0);
    }
    public int parameterToArgSlot(int i) {
        return argToSlotTable[1+i];
    }
    public int argSlotToParameter(int argSlot) {
        // Note:  Empty slots are represented by zero in this table.
        // Valid arguments slots contain incremented entries, so as to be non-zero.
        // We return -1 the caller to mean an empty slot.
        return slotToArgTable[argSlot] - 1;
    }

    static final int ERASE = 1, WRAP = 2, UNWRAP = 4, ARGS_ONLY = 8;

    static MethodTypeForm findForm(MethodType mt) {
        MethodType erased = canonType(mt, ERASE);
        if (erased == null) {
            // It is already erased.  Make a new MethodTypeForm.
            return new MethodTypeForm(mt);
        } else {
            // Share the MethodTypeForm with the erased version.
            return erased.form;
        }
    }

    /** Canonicalize the types in the given method type.
     * If any types change, intern the new type, and return it.
     * Otherwise return null.
     */
    static MethodType canonType(MethodType mt, int how) {
        Class<?>[] ptc = MethodTypeForm.canonTypes(mt.ptypes, how);
        Class<?> rtc = null;
        if ((how & ARGS_ONLY) == 0)
            rtc = MethodTypeForm.canonType(mt.rtype, how);
        if (ptc == null && rtc == null) {
            // It is already canonical.
            return null;
        }
        // Find the erased version of the method type:
        if (rtc == null)  rtc = mt.rtype;
        if (ptc == null)  ptc = mt.ptypes;
        return MethodType.makeImpl(rtc, ptc, true);
    }

    /** Canonicalize the given return or param type.
     *  Return null if the type is already canonicalized.
     */
    static Class<?> canonType(Class<?> t, int how) {
        if (t == Object.class) {
            // no change, ever
        } else if (!t.isPrimitive()) {
            if ((how & UNWRAP) != 0) {
                Class<?> pt = Wrappers.asPrimitiveType(t);
                if (pt != t)
                    return pt;
            }
            if ((how & ERASE) != 0)
                return Object.class;
        } else {
            if ((how & WRAP) != 0) {
                if ((how & ERASE) != 0)
                    return Object.class;
                return Wrappers.asWrapperType(t);
            }
        }
        // no change; return null to signify
        return null;
    }

    /** Canonicalize each param type in the given array.
     *  Return null if all types are already canonicalized.
     */
    static Class<?>[] canonTypes(Class<?>[] ts, int how) {
        Class<?>[] cs = null;
        for (int imax = ts.length, i = 0; i < imax; i++) {
            Class<?> c = canonType(ts[i], how);
            if (c != null) {
                if (cs == null)
                    cs = ts.clone();
                cs[i] = c;
            }
        }
        return cs;
    }
}
