| /* |
| * Copyright (c) 2008, 2009, Oracle and/or its affiliates. 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. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package sun.dyn; |
| |
| import java.dyn.*; |
| import sun.dyn.util.Wrapper; |
| import static sun.dyn.MemberName.newIllegalArgumentException; |
| |
| /** |
| * 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 |
| */ |
| public class MethodTypeImpl { |
| 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 |
| /*lazy*/ MethodType primsAsBoxes; // replace prims by wrappers |
| /*lazy*/ MethodType primArgsAsBoxes; // wrap args only; make raw return |
| /*lazy*/ MethodType primsAsInts; // replace prims by int/long |
| /*lazy*/ MethodType primsAsLongs; // replace prims by long |
| /*lazy*/ MethodType primsAtEnd; // reorder primitives to the end |
| |
| // Cached adapter information: |
| /*lazy*/ ToGeneric toGeneric; // convert cs. with prims to w/o |
| /*lazy*/ FromGeneric fromGeneric; // convert cs. w/o prims to with |
| /*lazy*/ SpreadGeneric[] spreadGeneric; // expand one argument to many |
| /*lazy*/ FilterGeneric filterGeneric; // convert argument(s) on the fly |
| |
| public MethodType erasedType() { |
| return erasedType; |
| } |
| |
| public static MethodTypeImpl of(MethodType type) { |
| return METHOD_TYPE_FRIEND.form(type); |
| } |
| |
| /** Access methods for the internals of MethodType, supplied to |
| * MethodTypeImpl as a trusted agent. |
| */ |
| static public interface MethodTypeFriend { |
| Class<?>[] ptypes(MethodType mt); |
| MethodTypeImpl form(MethodType mt); |
| void setForm(MethodType mt, MethodTypeImpl form); |
| MethodType makeImpl(Class<?> rtype, Class<?>[] ptypes, boolean trusted); |
| MethodTypeImpl newMethodTypeForm(MethodType mt); |
| Invokers getInvokers(MethodType mt); |
| void setInvokers(MethodType mt, Invokers inv); |
| } |
| public static void setMethodTypeFriend(Access token, MethodTypeFriend am) { |
| Access.check(token); |
| if (METHOD_TYPE_FRIEND != null) |
| throw new InternalError(); // just once |
| METHOD_TYPE_FRIEND = am; |
| } |
| static private MethodTypeFriend METHOD_TYPE_FRIEND; |
| |
| protected MethodTypeImpl(MethodType erasedType) { |
| this.erasedType = erasedType; |
| |
| Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(erasedType); |
| int ptypeCount = 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. |
| int pac = 0, lac = 0, prc = 0, lrc = 0; |
| Class<?> epts[] = 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 = erasedType.returnType(); |
| 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); |
| 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 newIllegalArgumentException("too many arguments"); |
| |
| // send a few bits down to the JVM: |
| this.vmslots = parameterSlotCount(); |
| |
| // short circuit some no-op canonicalizations: |
| if (!hasPrimitives()) { |
| primsAsBoxes = erasedType; |
| primArgsAsBoxes = erasedType; |
| primsAsInts = erasedType; |
| primsAsLongs = erasedType; |
| primsAtEnd = erasedType; |
| } |
| } |
| |
| /** Turn all primitive types to corresponding wrapper types. |
| */ |
| public MethodType primsAsBoxes() { |
| MethodType ct = primsAsBoxes; |
| if (ct != null) return ct; |
| MethodType t = erasedType; |
| ct = canonicalize(erasedType, WRAP, WRAP); |
| if (ct == null) ct = t; // no prims to box |
| return primsAsBoxes = ct; |
| } |
| |
| /** Turn all primitive argument types to corresponding wrapper types. |
| * Subword and void return types are promoted to int. |
| */ |
| public MethodType primArgsAsBoxes() { |
| MethodType ct = primArgsAsBoxes; |
| if (ct != null) return ct; |
| MethodType t = erasedType; |
| ct = canonicalize(erasedType, RAW_RETURN, WRAP); |
| if (ct == null) ct = t; // no prims to box |
| return primArgsAsBoxes = ct; |
| } |
| |
| /** Turn all primitive types to either int or long. |
| * Floating point return types are not changed, because |
| * they may require special calling sequences. |
| * A void return value is turned to int. |
| */ |
| public MethodType primsAsInts() { |
| MethodType ct = primsAsInts; |
| if (ct != null) return ct; |
| MethodType t = erasedType; |
| ct = canonicalize(t, RAW_RETURN, INTS); |
| if (ct == null) ct = t; // no prims to int-ify |
| return primsAsInts = ct; |
| } |
| |
| /** Turn all primitive types to either int or long. |
| * Floating point return types are not changed, because |
| * they may require special calling sequences. |
| * A void return value is turned to int. |
| */ |
| public MethodType primsAsLongs() { |
| MethodType ct = primsAsLongs; |
| if (ct != null) return ct; |
| MethodType t = erasedType; |
| ct = canonicalize(t, RAW_RETURN, LONGS); |
| if (ct == null) ct = t; // no prims to int-ify |
| return primsAsLongs = ct; |
| } |
| |
| /** Stably sort parameters into 3 buckets: ref, int, long. */ |
| public MethodType primsAtEnd() { |
| MethodType ct = primsAtEnd; |
| if (ct != null) return ct; |
| MethodType t = erasedType; |
| |
| int pac = primitiveParameterCount(); |
| if (pac == 0) |
| return primsAtEnd = t; |
| |
| int argc = parameterCount(); |
| int lac = longPrimitiveParameterCount(); |
| if (pac == argc && (lac == 0 || lac == argc)) |
| return primsAtEnd = t; |
| |
| // known to have a mix of 2 or 3 of ref, int, long |
| return primsAtEnd = reorderParameters(t, primsAtEndOrder(t), null); |
| |
| } |
| |
| /** Compute a new ordering of parameters so that all references |
| * are before all ints or longs, and all ints are before all longs. |
| * For this ordering, doubles count as longs, and all other primitive |
| * values count as ints. |
| * As a special case, if the parameters are already in the specified |
| * order, this method returns a null reference, rather than an array |
| * specifying a null permutation. |
| * <p> |
| * For example, the type {@code (int,boolean,int,Object,String)void} |
| * produces the order {@code {3,4,0,1,2}}, the type |
| * {@code (long,int,String)void} produces {@code {2,1,2}}, and |
| * the type {@code (Object,int)Object} produces {@code null}. |
| */ |
| public static int[] primsAtEndOrder(MethodType mt) { |
| MethodTypeImpl form = METHOD_TYPE_FRIEND.form(mt); |
| if (form.primsAtEnd == form.erasedType) |
| // quick check shows no reordering is necessary |
| return null; |
| |
| int argc = form.parameterCount(); |
| int[] paramOrder = new int[argc]; |
| |
| // 3-way bucket sort: |
| int pac = form.primitiveParameterCount(); |
| int lac = form.longPrimitiveParameterCount(); |
| int rfill = 0, ifill = argc - pac, lfill = argc - lac; |
| |
| Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(mt); |
| boolean changed = false; |
| for (int i = 0; i < ptypes.length; i++) { |
| Class<?> pt = ptypes[i]; |
| int ord; |
| if (!pt.isPrimitive()) ord = rfill++; |
| else if (!hasTwoArgSlots(pt)) ord = ifill++; |
| else ord = lfill++; |
| if (ord != i) changed = true; |
| paramOrder[i] = ord; |
| } |
| assert(rfill == argc - pac && ifill == argc - lac && lfill == argc); |
| if (!changed) { |
| form.primsAtEnd = form.erasedType; |
| return null; |
| } |
| return paramOrder; |
| } |
| |
| /** Put the existing parameters of mt into a new order, given by newParamOrder. |
| * The third argument is logically appended to mt.parameterArray, |
| * so that elements of newParamOrder can index either pre-existing or |
| * new parameter types. |
| */ |
| public static MethodType reorderParameters(MethodType mt, int[] newParamOrder, Class<?>[] moreParams) { |
| if (newParamOrder == null) return mt; // no-op reordering |
| Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(mt); |
| Class<?>[] ntypes = new Class<?>[newParamOrder.length]; |
| int ordMax = ptypes.length + (moreParams == null ? 0 : moreParams.length); |
| boolean changed = (ntypes.length != ptypes.length); |
| for (int i = 0; i < newParamOrder.length; i++) { |
| int ord = newParamOrder[i]; |
| if (ord != i) changed = true; |
| Class<?> nt; |
| if (ord < ptypes.length) nt = ptypes[ord]; |
| else if (ord == ordMax) nt = mt.returnType(); |
| else nt = moreParams[ord - ptypes.length]; |
| ntypes[i] = nt; |
| } |
| if (!changed) return mt; |
| return METHOD_TYPE_FRIEND.makeImpl(mt.returnType(), ntypes, true); |
| } |
| |
| 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 boolean hasPrimitives() { |
| return primCounts != 0; |
| } |
| // public boolean hasNonVoidPrimitives() { |
| // if (primCounts == 0) return false; |
| // if (primitiveParameterCount() != 0) return true; |
| // return (primitiveReturnCount() != 0 && returnCount() != 0); |
| // } |
| public boolean hasLongPrimitives() { |
| return (longPrimitiveParameterCount() | longPrimitiveReturnCount()) != 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; |
| } |
| |
| public static void initForm(Access token, MethodType mt) { |
| Access.check(token); |
| MethodTypeImpl form = findForm(mt); |
| METHOD_TYPE_FRIEND.setForm(mt, form); |
| if (form.erasedType == mt) { |
| // This is a principal (erased) type; show it to the JVM. |
| MethodHandleImpl.init(token, mt); |
| } |
| } |
| |
| static MethodTypeImpl findForm(MethodType mt) { |
| MethodType erased = canonicalize(mt, ERASE, ERASE); |
| if (erased == null) { |
| // It is already erased. Make a new MethodTypeImpl. |
| return METHOD_TYPE_FRIEND.newMethodTypeForm(mt); |
| } else { |
| // Share the MethodTypeImpl with the erased version. |
| return METHOD_TYPE_FRIEND.form(erased); |
| } |
| } |
| |
| /** Codes for {@link #canonicalize(java.lang.Class, int). |
| * ERASE means change every reference to {@code Object}. |
| * WRAP means convert primitives (including {@code void} to their |
| * corresponding wrapper types. UNWRAP means the reverse of WRAP. |
| * INTS means convert all non-void primitive types to int or long, |
| * according to size. LONGS means convert all non-void primitives |
| * to long, regardless of size. RAW_RETURN means convert a type |
| * (assumed to be a return type) to int if it is smaller than an int, |
| * or if it is void. |
| */ |
| public static final int NO_CHANGE = 0, ERASE = 1, WRAP = 2, UNWRAP = 3, INTS = 4, LONGS = 5, RAW_RETURN = 6; |
| |
| /** Canonicalize the types in the given method type. |
| * If any types change, intern the new type, and return it. |
| * Otherwise return null. |
| */ |
| public static MethodType canonicalize(MethodType mt, int howRet, int howArgs) { |
| Class<?>[] ptypes = METHOD_TYPE_FRIEND.ptypes(mt); |
| Class<?>[] ptc = MethodTypeImpl.canonicalizes(ptypes, howArgs); |
| Class<?> rtype = mt.returnType(); |
| Class<?> rtc = MethodTypeImpl.canonicalize(rtype, howRet); |
| if (ptc == null && rtc == null) { |
| // It is already canonical. |
| return null; |
| } |
| // Find the erased version of the method type: |
| if (rtc == null) rtc = rtype; |
| if (ptc == null) ptc = ptypes; |
| return METHOD_TYPE_FRIEND.makeImpl(rtc, ptc, true); |
| } |
| |
| /** Canonicalize the given return or param type. |
| * Return null if the type is already canonicalized. |
| */ |
| static Class<?> canonicalize(Class<?> t, int how) { |
| Class<?> ct; |
| if (t == Object.class) { |
| // no change, ever |
| } else if (!t.isPrimitive()) { |
| switch (how) { |
| case UNWRAP: |
| ct = Wrapper.asPrimitiveType(t); |
| if (ct != t) return ct; |
| break; |
| case RAW_RETURN: |
| case ERASE: |
| return Object.class; |
| } |
| } else if (t == void.class) { |
| // no change, usually |
| switch (how) { |
| case RAW_RETURN: |
| return int.class; |
| case WRAP: |
| return Void.class; |
| } |
| } else { |
| // non-void primitive |
| switch (how) { |
| case WRAP: |
| return Wrapper.asWrapperType(t); |
| case INTS: |
| if (t == int.class || t == long.class) |
| return null; // no change |
| if (t == double.class) |
| return long.class; |
| return int.class; |
| case LONGS: |
| if (t == long.class) |
| return null; // no change |
| return long.class; |
| case RAW_RETURN: |
| if (t == int.class || t == long.class || |
| t == float.class || t == double.class) |
| return null; // no change |
| // everything else returns as an int |
| return int.class; |
| } |
| } |
| // 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<?>[] canonicalizes(Class<?>[] ts, int how) { |
| Class<?>[] cs = null; |
| for (int imax = ts.length, i = 0; i < imax; i++) { |
| Class<?> c = canonicalize(ts[i], how); |
| if (c != null) { |
| if (cs == null) |
| cs = ts.clone(); |
| cs[i] = c; |
| } |
| } |
| return cs; |
| } |
| |
| public static Invokers invokers(Access token, MethodType type) { |
| Access.check(token); |
| Invokers inv = METHOD_TYPE_FRIEND.getInvokers(type); |
| if (inv != null) return inv; |
| inv = new Invokers(token, type); |
| METHOD_TYPE_FRIEND.setInvokers(type, inv); |
| return inv; |
| } |
| |
| @Override |
| public String toString() { |
| return "Form"+erasedType; |
| } |
| |
| } |