| /* |
| * Copyright (c) 2010, 2013, 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 jdk.nashorn.internal.codegen.types; |
| |
| import static jdk.internal.org.objectweb.asm.Opcodes.DALOAD; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DASTORE; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DUP; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DUP2; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DUP2_X1; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DUP2_X2; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DUP_X1; |
| import static jdk.internal.org.objectweb.asm.Opcodes.DUP_X2; |
| import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.IALOAD; |
| import static jdk.internal.org.objectweb.asm.Opcodes.IASTORE; |
| import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC; |
| import static jdk.internal.org.objectweb.asm.Opcodes.LALOAD; |
| import static jdk.internal.org.objectweb.asm.Opcodes.LASTORE; |
| import static jdk.internal.org.objectweb.asm.Opcodes.NEWARRAY; |
| import static jdk.internal.org.objectweb.asm.Opcodes.POP; |
| import static jdk.internal.org.objectweb.asm.Opcodes.POP2; |
| import static jdk.internal.org.objectweb.asm.Opcodes.SWAP; |
| import static jdk.internal.org.objectweb.asm.Opcodes.T_DOUBLE; |
| import static jdk.internal.org.objectweb.asm.Opcodes.T_INT; |
| import static jdk.internal.org.objectweb.asm.Opcodes.T_LONG; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; |
| |
| import java.io.DataInput; |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.lang.invoke.CallSite; |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.util.Collections; |
| import java.util.Map; |
| import java.util.TreeMap; |
| import java.util.WeakHashMap; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import jdk.internal.org.objectweb.asm.Handle; |
| import jdk.internal.org.objectweb.asm.MethodVisitor; |
| import jdk.nashorn.internal.codegen.CompilerConstants.Call; |
| import jdk.nashorn.internal.runtime.Context; |
| import jdk.nashorn.internal.runtime.ScriptObject; |
| import jdk.nashorn.internal.runtime.Undefined; |
| import jdk.nashorn.internal.runtime.linker.Bootstrap; |
| |
| /** |
| * This is the representation of a JavaScript type, disassociated from java |
| * Classes, with the basis for conversion weight, mapping to ASM types |
| * and implementing the ByteCodeOps interface which tells this type |
| * how to generate code for various operations. |
| * |
| * Except for ClassEmitter, this is the only class that has to know |
| * about the underlying byte code generation system. |
| * |
| * The different types know how to generate bytecode for the different |
| * operations, inherited from BytecodeOps, that they support. This avoids |
| * if/else chains depending on type in several cases and allows for |
| * more readable and shorter code |
| * |
| * The Type class also contains logic used by the type inference and |
| * for comparing types against each other, as well as the concepts |
| * of narrower to wider types. The widest type is an object. Ideally we |
| * would like as narrow types as possible for code to be efficient, e.g |
| * INTs rather than OBJECTs |
| */ |
| |
| public abstract class Type implements Comparable<Type>, BytecodeOps, Serializable { |
| private static final long serialVersionUID = 1L; |
| |
| /** Human readable name for type */ |
| private transient final String name; |
| |
| /** Descriptor for type */ |
| private transient final String descriptor; |
| |
| /** The "weight" of the type. Used for picking widest/least specific common type */ |
| private transient final int weight; |
| |
| /** How many bytecode slots does this type occupy */ |
| private transient final int slots; |
| |
| /** The class for this type */ |
| private final Class<?> clazz; |
| |
| /** |
| * Cache for internal types - this is a query that requires complex stringbuilding inside |
| * ASM and it saves startup time to cache the type mappings |
| */ |
| private static final Map<Class<?>, jdk.internal.org.objectweb.asm.Type> INTERNAL_TYPE_CACHE = |
| Collections.synchronizedMap(new WeakHashMap<Class<?>, jdk.internal.org.objectweb.asm.Type>()); |
| |
| /** Internal ASM type for this Type - computed once at construction */ |
| private transient final jdk.internal.org.objectweb.asm.Type internalType; |
| |
| /** Weights are used to decide which types are "wider" than other types */ |
| protected static final int MIN_WEIGHT = -1; |
| |
| /** Set way below Integer.MAX_VALUE to prevent overflow when adding weights. Objects are still heaviest. */ |
| protected static final int MAX_WEIGHT = 20; |
| |
| static final Call BOOTSTRAP = staticCallNoLookup(Bootstrap.class, "mathBootstrap", CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class); |
| |
| static final Handle MATHBOOTSTRAP = new Handle(H_INVOKESTATIC, BOOTSTRAP.className(), "mathBootstrap", BOOTSTRAP.descriptor()); |
| |
| /** |
| * Constructor |
| * |
| * @param clazz class for type |
| * @param weight weight - higher is more generic |
| * @param slots how many bytecode slots the type takes up |
| */ |
| Type(final String name, final Class<?> clazz, final int weight, final int slots) { |
| this.name = name; |
| this.clazz = clazz; |
| this.descriptor = jdk.internal.org.objectweb.asm.Type.getDescriptor(clazz); |
| this.weight = weight; |
| assert weight >= MIN_WEIGHT && weight <= MAX_WEIGHT : "illegal type weight: " + weight; |
| this.slots = slots; |
| this.internalType = getInternalType(clazz); |
| } |
| |
| /** |
| * Get the weight of this type - use this e.g. for sorting method descriptors |
| * @return the weight |
| */ |
| public int getWeight() { |
| return weight; |
| } |
| |
| /** |
| * Get the Class representing this type |
| * @return the class for this type |
| */ |
| public Class<?> getTypeClass() { |
| return clazz; |
| } |
| |
| /** |
| * For specialization, return the next, slightly more difficulty, type |
| * to test. |
| * |
| * @return the next Type |
| */ |
| public Type nextWider() { |
| return null; |
| } |
| |
| /** |
| * Get the boxed type for this class |
| * @return the boxed version of this type or null if N/A |
| */ |
| public Class<?> getBoxedType() { |
| assert !getTypeClass().isPrimitive(); |
| return null; |
| } |
| |
| /** |
| * Returns the character describing the bytecode type for this value on the stack or local variable, identical to |
| * what would be used as the prefix for a bytecode {@code LOAD} or {@code STORE} instruction, therefore it must be |
| * one of {@code A, F, D, I, L}. Also, the special value {@code U} is used for local variable slots that haven't |
| * been initialized yet (it can't appear for a value pushed to the operand stack, those always have known values). |
| * Note that while we allow all JVM internal types, Nashorn doesn't necessarily use them all - currently we don't |
| * have floats, only doubles, but that might change in the future. |
| * @return the character describing the bytecode type for this value on the stack. |
| */ |
| public abstract char getBytecodeStackType(); |
| |
| /** |
| * Generate a method descriptor given a return type and a param array |
| * |
| * @param returnType return type |
| * @param types parameters |
| * |
| * @return a descriptor string |
| */ |
| public static String getMethodDescriptor(final Type returnType, final Type... types) { |
| final jdk.internal.org.objectweb.asm.Type[] itypes = new jdk.internal.org.objectweb.asm.Type[types.length]; |
| for (int i = 0; i < types.length; i++) { |
| itypes[i] = types[i].getInternalType(); |
| } |
| return jdk.internal.org.objectweb.asm.Type.getMethodDescriptor(returnType.getInternalType(), itypes); |
| } |
| |
| /** |
| * Generate a method descriptor given a return type and a param array |
| * |
| * @param returnType return type |
| * @param types parameters |
| * |
| * @return a descriptor string |
| */ |
| public static String getMethodDescriptor(final Class<?> returnType, final Class<?>... types) { |
| final jdk.internal.org.objectweb.asm.Type[] itypes = new jdk.internal.org.objectweb.asm.Type[types.length]; |
| for (int i = 0; i < types.length; i++) { |
| itypes[i] = getInternalType(types[i]); |
| } |
| return jdk.internal.org.objectweb.asm.Type.getMethodDescriptor(getInternalType(returnType), itypes); |
| } |
| |
| /** |
| * Return a character representing {@code type} in a method signature. |
| * |
| * @param type parameter type |
| * @return descriptor character |
| */ |
| public static char getShortSignatureDescriptor(final Type type) { |
| // Use 'Z' for boolean parameters as we need to distinguish from int |
| if (type instanceof BooleanType) { |
| return 'Z'; |
| } |
| return type.getBytecodeStackType(); |
| } |
| |
| /** |
| * Return the type for an internal type, package private - do not use |
| * outside code gen |
| * |
| * @param itype internal type |
| * @return Nashorn type |
| */ |
| @SuppressWarnings("fallthrough") |
| static Type typeFor(final jdk.internal.org.objectweb.asm.Type itype) { |
| switch (itype.getSort()) { |
| case jdk.internal.org.objectweb.asm.Type.BOOLEAN: |
| return BOOLEAN; |
| case jdk.internal.org.objectweb.asm.Type.INT: |
| return INT; |
| case jdk.internal.org.objectweb.asm.Type.LONG: |
| return LONG; |
| case jdk.internal.org.objectweb.asm.Type.DOUBLE: |
| return NUMBER; |
| case jdk.internal.org.objectweb.asm.Type.OBJECT: |
| if (Context.isStructureClass(itype.getClassName())) { |
| return SCRIPT_OBJECT; |
| } |
| try { |
| return Type.typeFor(Class.forName(itype.getClassName())); |
| } catch(final ClassNotFoundException e) { |
| throw new AssertionError(e); |
| } |
| case jdk.internal.org.objectweb.asm.Type.VOID: |
| return null; |
| case jdk.internal.org.objectweb.asm.Type.ARRAY: |
| switch (itype.getElementType().getSort()) { |
| case jdk.internal.org.objectweb.asm.Type.DOUBLE: |
| return NUMBER_ARRAY; |
| case jdk.internal.org.objectweb.asm.Type.INT: |
| return INT_ARRAY; |
| case jdk.internal.org.objectweb.asm.Type.LONG: |
| return LONG_ARRAY; |
| default: |
| assert false; |
| case jdk.internal.org.objectweb.asm.Type.OBJECT: |
| return OBJECT_ARRAY; |
| } |
| |
| default: |
| assert false : "Unknown itype : " + itype + " sort " + itype.getSort(); |
| break; |
| } |
| return null; |
| } |
| |
| /** |
| * Get the return type for a method |
| * |
| * @param methodDescriptor method descriptor |
| * @return return type |
| */ |
| public static Type getMethodReturnType(final String methodDescriptor) { |
| return Type.typeFor(jdk.internal.org.objectweb.asm.Type.getReturnType(methodDescriptor)); |
| } |
| |
| /** |
| * Get type array representing arguments of a method in order |
| * |
| * @param methodDescriptor method descriptor |
| * @return parameter type array |
| */ |
| public static Type[] getMethodArguments(final String methodDescriptor) { |
| final jdk.internal.org.objectweb.asm.Type itypes[] = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(methodDescriptor); |
| final Type types[] = new Type[itypes.length]; |
| for (int i = 0; i < itypes.length; i++) { |
| types[i] = Type.typeFor(itypes[i]); |
| } |
| return types; |
| } |
| |
| /** |
| * Write a map of {@code int} to {@code Type} to an output stream. This is used to store deoptimization state. |
| * |
| * @param typeMap the type map |
| * @param output data output |
| * @throws IOException if write cannot be completed |
| */ |
| public static void writeTypeMap(final Map<Integer, Type> typeMap, final DataOutput output) throws IOException { |
| if (typeMap == null) { |
| output.writeInt(0); |
| } else { |
| output.writeInt(typeMap.size()); |
| for(final Map.Entry<Integer, Type> e: typeMap.entrySet()) { |
| output.writeInt(e.getKey()); |
| final byte typeChar; |
| final Type type = e.getValue(); |
| if(type == Type.OBJECT) { |
| typeChar = 'L'; |
| } else if (type == Type.NUMBER) { |
| typeChar = 'D'; |
| } else if (type == Type.LONG) { |
| typeChar = 'J'; |
| } else { |
| throw new AssertionError(); |
| } |
| output.writeByte(typeChar); |
| } |
| } |
| } |
| |
| /** |
| * Read a map of {@code int} to {@code Type} from an input stream. This is used to store deoptimization state. |
| * |
| * @param input data input |
| * @return type map |
| * @throws IOException if read cannot be completed |
| */ |
| public static Map<Integer, Type> readTypeMap(final DataInput input) throws IOException { |
| final int size = input.readInt(); |
| if (size <= 0) { |
| return null; |
| } |
| final Map<Integer, Type> map = new TreeMap<>(); |
| for(int i = 0; i < size; ++i) { |
| final int pp = input.readInt(); |
| final int typeChar = input.readByte(); |
| final Type type; |
| switch (typeChar) { |
| case 'L': type = Type.OBJECT; break; |
| case 'D': type = Type.NUMBER; break; |
| case 'J': type = Type.LONG; break; |
| default: continue; |
| } |
| map.put(pp, type); |
| } |
| return map; |
| } |
| |
| static jdk.internal.org.objectweb.asm.Type getInternalType(final String className) { |
| return jdk.internal.org.objectweb.asm.Type.getType(className); |
| } |
| |
| private jdk.internal.org.objectweb.asm.Type getInternalType() { |
| return internalType; |
| } |
| |
| private static jdk.internal.org.objectweb.asm.Type lookupInternalType(final Class<?> type) { |
| final Map<Class<?>, jdk.internal.org.objectweb.asm.Type> c = INTERNAL_TYPE_CACHE; |
| jdk.internal.org.objectweb.asm.Type itype = c.get(type); |
| if (itype != null) { |
| return itype; |
| } |
| itype = jdk.internal.org.objectweb.asm.Type.getType(type); |
| c.put(type, itype); |
| return itype; |
| } |
| |
| private static jdk.internal.org.objectweb.asm.Type getInternalType(final Class<?> type) { |
| return lookupInternalType(type); |
| } |
| |
| static void invokestatic(final MethodVisitor method, final Call call) { |
| method.visitMethodInsn(INVOKESTATIC, call.className(), call.name(), call.descriptor(), false); |
| } |
| |
| /** |
| * Get the internal JVM name of a type |
| * @return the internal name |
| */ |
| public String getInternalName() { |
| return jdk.internal.org.objectweb.asm.Type.getInternalName(getTypeClass()); |
| } |
| |
| /** |
| * Get the internal JVM name of type type represented by a given Java class |
| * @param clazz the class |
| * @return the internal name |
| */ |
| public static String getInternalName(final Class<?> clazz) { |
| return jdk.internal.org.objectweb.asm.Type.getInternalName(clazz); |
| } |
| |
| /** |
| * Determines whether a type is the UNKNOWN type, i.e. not set yet |
| * Used for type inference. |
| * |
| * @return true if UNKNOWN, false otherwise |
| */ |
| public boolean isUnknown() { |
| return this.equals(Type.UNKNOWN); |
| } |
| |
| /** |
| * Determines whether this type represents an primitive type according to the ECMAScript specification, |
| * which includes Boolean, Number, and String. |
| * |
| * @return true if a JavaScript primitive type, false otherwise. |
| */ |
| public boolean isJSPrimitive() { |
| return !isObject() || isString(); |
| } |
| |
| /** |
| * Determines whether a type is the BOOLEAN type |
| * @return true if BOOLEAN, false otherwise |
| */ |
| public boolean isBoolean() { |
| return this.equals(Type.BOOLEAN); |
| } |
| |
| /** |
| * Determines whether a type is the INT type |
| * @return true if INTEGER, false otherwise |
| */ |
| public boolean isInteger() { |
| return this.equals(Type.INT); |
| } |
| |
| /** |
| * Determines whether a type is the LONG type |
| * @return true if LONG, false otherwise |
| */ |
| public boolean isLong() { |
| return this.equals(Type.LONG); |
| } |
| |
| /** |
| * Determines whether a type is the NUMBER type |
| * @return true if NUMBER, false otherwise |
| */ |
| public boolean isNumber() { |
| return this.equals(Type.NUMBER); |
| } |
| |
| /** |
| * Determines whether a type is numeric, i.e. NUMBER, |
| * INT, LONG. |
| * |
| * @return true if numeric, false otherwise |
| */ |
| public boolean isNumeric() { |
| return this instanceof NumericType; |
| } |
| |
| /** |
| * Determines whether a type is an array type, i.e. |
| * OBJECT_ARRAY or NUMBER_ARRAY (for now) |
| * |
| * @return true if an array type, false otherwise |
| */ |
| public boolean isArray() { |
| return this instanceof ArrayType; |
| } |
| |
| /** |
| * Determines if a type takes up two bytecode slots or not |
| * |
| * @return true if type takes up two bytecode slots rather than one |
| */ |
| public boolean isCategory2() { |
| return getSlots() == 2; |
| } |
| |
| /** |
| * Determines whether a type is an OBJECT type, e.g. OBJECT, STRING, |
| * NUMBER_ARRAY etc. |
| * |
| * @return true if object type, false otherwise |
| */ |
| public boolean isObject() { |
| return this instanceof ObjectType; |
| } |
| |
| /** |
| * Is this a primitive type (e.g int, long, double, boolean) |
| * @return true if primitive |
| */ |
| public boolean isPrimitive() { |
| return !isObject(); |
| } |
| |
| /** |
| * Determines whether a type is a STRING type |
| * |
| * @return true if object type, false otherwise |
| */ |
| public boolean isString() { |
| return this.equals(Type.STRING); |
| } |
| |
| /** |
| * Determines whether a type is a CHARSEQUENCE type used internally strings |
| * |
| * @return true if CharSequence (internal string) type, false otherwise |
| */ |
| public boolean isCharSequence() { |
| return this.equals(Type.CHARSEQUENCE); |
| } |
| |
| /** |
| * Determine if two types are equivalent, i.e. need no conversion |
| * |
| * @param type the second type to check |
| * |
| * @return true if types are equivalent, false otherwise |
| */ |
| public boolean isEquivalentTo(final Type type) { |
| return this.weight() == type.weight() || isObject() && type.isObject(); |
| } |
| |
| /** |
| * Determine if a type can be assigned to from another |
| * |
| * @param type0 the first type to check |
| * @param type1 the second type to check |
| * |
| * @return true if type1 can be written to type2, false otherwise |
| */ |
| public static boolean isAssignableFrom(final Type type0, final Type type1) { |
| if (type0.isObject() && type1.isObject()) { |
| return type0.weight() >= type1.weight(); |
| } |
| |
| return type0.weight() == type1.weight(); |
| } |
| |
| /** |
| * Determine if this type is assignable from another type |
| * @param type the type to check against |
| * |
| * @return true if "type" can be written to this type, false otherwise |
| */ |
| public boolean isAssignableFrom(final Type type) { |
| return Type.isAssignableFrom(this, type); |
| } |
| |
| /** |
| * Determines is this type is equivalent to another, i.e. needs no conversion |
| * to be assigned to it. |
| * |
| * @param type0 the first type to check |
| * @param type1 the second type to check |
| * |
| * @return true if this type is equivalent to type, false otherwise |
| */ |
| public static boolean areEquivalent(final Type type0, final Type type1) { |
| return type0.isEquivalentTo(type1); |
| } |
| |
| /** |
| * Determine the number of bytecode slots a type takes up |
| * |
| * @return the number of slots for this type, 1 or 2. |
| */ |
| public int getSlots() { |
| return slots; |
| } |
| |
| /** |
| * Returns the widest or most common of two types |
| * |
| * @param type0 type one |
| * @param type1 type two |
| * |
| * @return the widest type |
| */ |
| public static Type widest(final Type type0, final Type type1) { |
| if (type0.isArray() && type1.isArray()) { |
| return ((ArrayType)type0).getElementType() == ((ArrayType)type1).getElementType() ? type0 : Type.OBJECT; |
| } else if (type0.isArray() != type1.isArray()) { |
| //array and non array is always object, widest(Object[], int) NEVER returns Object[], which has most weight. that does not make sense |
| return Type.OBJECT; |
| } else if (type0.isObject() && type1.isObject() && type0.getTypeClass() != type1.getTypeClass()) { |
| // Object<type=String> and Object<type=ScriptFunction> will produce Object |
| // TODO: maybe find most specific common superclass? |
| return Type.OBJECT; |
| } |
| return type0.weight() > type1.weight() ? type0 : type1; |
| } |
| |
| /** |
| * Returns the widest or most common of two types, given as classes |
| * |
| * @param type0 type one |
| * @param type1 type two |
| * |
| * @return the widest type |
| */ |
| public static Class<?> widest(final Class<?> type0, final Class<?> type1) { |
| return widest(Type.typeFor(type0), Type.typeFor(type1)).getTypeClass(); |
| } |
| |
| /** |
| * When doing widening for return types of a function or a ternary operator, it is not valid to widen a boolean to |
| * anything other than object. Note that this wouldn't be necessary if {@code Type.widest} did not allow |
| * boolean-to-number widening. Eventually, we should address it there, but it affects too many other parts of the |
| * system and is sometimes legitimate (e.g. whenever a boolean value would undergo ToNumber conversion anyway). |
| * @param t1 type 1 |
| * @param t2 type 2 |
| * @return wider of t1 and t2, except if one is boolean and the other is neither boolean nor unknown, in which case |
| * {@code Type.OBJECT} is returned. |
| */ |
| public static Type widestReturnType(final Type t1, final Type t2) { |
| if (t1.isUnknown()) { |
| return t2; |
| } else if (t2.isUnknown()) { |
| return t1; |
| } else if(t1.isBoolean() != t2.isBoolean() || t1.isNumeric() != t2.isNumeric()) { |
| return Type.OBJECT; |
| } |
| return Type.widest(t1, t2); |
| } |
| |
| /** |
| * Returns a generic version of the type. Basically, if the type {@link #isObject()}, returns {@link #OBJECT}, |
| * otherwise returns the type unchanged. |
| * @param type the type to generify |
| * @return the generified type |
| */ |
| public static Type generic(final Type type) { |
| return type.isObject() ? Type.OBJECT : type; |
| } |
| |
| /** |
| * Returns the narrowest or least common of two types |
| * |
| * @param type0 type one |
| * @param type1 type two |
| * |
| * @return the widest type |
| */ |
| public static Type narrowest(final Type type0, final Type type1) { |
| return type0.narrowerThan(type1) ? type0 : type1; |
| } |
| |
| /** |
| * Check whether this type is strictly narrower than another one |
| * @param type type to check against |
| * @return true if this type is strictly narrower |
| */ |
| public boolean narrowerThan(final Type type) { |
| return weight() < type.weight(); |
| } |
| |
| /** |
| * Check whether this type is strictly wider than another one |
| * @param type type to check against |
| * @return true if this type is strictly wider |
| */ |
| public boolean widerThan(final Type type) { |
| return weight() > type.weight(); |
| } |
| |
| /** |
| * Returns the widest or most common of two types, but no wider than "limit" |
| * |
| * @param type0 type one |
| * @param type1 type two |
| * @param limit limiting type |
| * |
| * @return the widest type, but no wider than limit |
| */ |
| public static Type widest(final Type type0, final Type type1, final Type limit) { |
| final Type type = Type.widest(type0, type1); |
| if (type.weight() > limit.weight()) { |
| return limit; |
| } |
| return type; |
| } |
| |
| /** |
| * Returns the widest or most common of two types, but no narrower than "limit" |
| * |
| * @param type0 type one |
| * @param type1 type two |
| * @param limit limiting type |
| * |
| * @return the widest type, but no wider than limit |
| */ |
| public static Type narrowest(final Type type0, final Type type1, final Type limit) { |
| final Type type = type0.weight() < type1.weight() ? type0 : type1; |
| if (type.weight() < limit.weight()) { |
| return limit; |
| } |
| return type; |
| } |
| |
| /** |
| * Returns the narrowest of this type and another |
| * |
| * @param other type to compare against |
| * |
| * @return the widest type |
| */ |
| public Type narrowest(final Type other) { |
| return Type.narrowest(this, other); |
| } |
| |
| /** |
| * Returns the widest of this type and another |
| * |
| * @param other type to compare against |
| * |
| * @return the widest type |
| */ |
| public Type widest(final Type other) { |
| return Type.widest(this, other); |
| } |
| |
| /** |
| * Returns the weight of a type, used for type comparison |
| * between wider and narrower types |
| * |
| * @return the weight |
| */ |
| int weight() { |
| return weight; |
| } |
| |
| /** |
| * Return the descriptor of a type, used for e.g. signature |
| * generation |
| * |
| * @return the descriptor |
| */ |
| public String getDescriptor() { |
| return descriptor; |
| } |
| |
| /** |
| * Return the descriptor of a type, short version |
| * Used mainly for debugging purposes |
| * |
| * @return the short descriptor |
| */ |
| public String getShortDescriptor() { |
| return descriptor; |
| } |
| |
| @Override |
| public String toString() { |
| return name; |
| } |
| |
| /** |
| * Return the (possibly cached) Type object for this class |
| * |
| * @param clazz the class to check |
| * |
| * @return the Type representing this class |
| */ |
| public static Type typeFor(final Class<?> clazz) { |
| final Type type = cache.get(clazz); |
| if(type != null) { |
| return type; |
| } |
| assert !clazz.isPrimitive() || clazz == void.class; |
| final Type newType; |
| if (clazz.isArray()) { |
| newType = new ArrayType(clazz); |
| } else { |
| newType = new ObjectType(clazz); |
| } |
| final Type existingType = cache.putIfAbsent(clazz, newType); |
| return existingType == null ? newType : existingType; |
| } |
| |
| @Override |
| public int compareTo(final Type o) { |
| return o.weight() - weight(); |
| } |
| |
| /** |
| * Common logic for implementing dup for all types |
| * |
| * @param method method visitor |
| * @param depth dup depth |
| * |
| * @return the type at the top of the stack afterwards |
| */ |
| @Override |
| public Type dup(final MethodVisitor method, final int depth) { |
| return Type.dup(method, this, depth); |
| } |
| |
| /** |
| * Common logic for implementing swap for all types |
| * |
| * @param method method visitor |
| * @param other the type to swap with |
| * |
| * @return the type at the top of the stack afterwards, i.e. other |
| */ |
| @Override |
| public Type swap(final MethodVisitor method, final Type other) { |
| Type.swap(method, this, other); |
| return other; |
| } |
| |
| /** |
| * Common logic for implementing pop for all types |
| * |
| * @param method method visitor |
| * |
| * @return the type that was popped |
| */ |
| @Override |
| public Type pop(final MethodVisitor method) { |
| Type.pop(method, this); |
| return this; |
| } |
| |
| @Override |
| public Type loadEmpty(final MethodVisitor method) { |
| assert false : "unsupported operation"; |
| return null; |
| } |
| |
| /** |
| * Superclass logic for pop for all types |
| * |
| * @param method method emitter |
| * @param type type to pop |
| */ |
| protected static void pop(final MethodVisitor method, final Type type) { |
| method.visitInsn(type.isCategory2() ? POP2 : POP); |
| } |
| |
| private static Type dup(final MethodVisitor method, final Type type, final int depth) { |
| final boolean cat2 = type.isCategory2(); |
| |
| switch (depth) { |
| case 0: |
| method.visitInsn(cat2 ? DUP2 : DUP); |
| break; |
| case 1: |
| method.visitInsn(cat2 ? DUP2_X1 : DUP_X1); |
| break; |
| case 2: |
| method.visitInsn(cat2 ? DUP2_X2 : DUP_X2); |
| break; |
| default: |
| return null; //invalid depth |
| } |
| |
| return type; |
| } |
| |
| private static void swap(final MethodVisitor method, final Type above, final Type below) { |
| if (below.isCategory2()) { |
| if (above.isCategory2()) { |
| method.visitInsn(DUP2_X2); |
| method.visitInsn(POP2); |
| } else { |
| method.visitInsn(DUP_X2); |
| method.visitInsn(POP); |
| } |
| } else { |
| if (above.isCategory2()) { |
| method.visitInsn(DUP2_X1); |
| method.visitInsn(POP2); |
| } else { |
| method.visitInsn(SWAP); |
| } |
| } |
| } |
| |
| /** Mappings between java classes and their Type singletons */ |
| private static final ConcurrentMap<Class<?>, Type> cache = new ConcurrentHashMap<>(); |
| |
| /** |
| * This is the boolean singleton, used for all boolean types |
| */ |
| public static final Type BOOLEAN = putInCache(new BooleanType()); |
| |
| /** |
| * This is an integer type, i.e INT, INT32. |
| */ |
| public static final BitwiseType INT = putInCache(new IntType()); |
| |
| /** |
| * This is the number singleton, used for all number types |
| */ |
| public static final NumericType NUMBER = putInCache(new NumberType()); |
| |
| /** |
| * This is the long singleton, used for all long types |
| */ |
| public static final Type LONG = putInCache(new LongType()); |
| |
| /** |
| * A string singleton |
| */ |
| public static final Type STRING = putInCache(new ObjectType(String.class)); |
| |
| /** |
| * This is the CharSequence singleton used to represent JS strings internally |
| * (either a {@code java.lang.String} or {@code jdk.nashorn.internal.runtime.ConsString}. |
| */ |
| public static final Type CHARSEQUENCE = putInCache(new ObjectType(CharSequence.class)); |
| |
| |
| /** |
| * This is the object singleton, used for all object types |
| */ |
| public static final Type OBJECT = putInCache(new ObjectType()); |
| |
| /** |
| * A undefined singleton |
| */ |
| public static final Type UNDEFINED = putInCache(new ObjectType(Undefined.class)); |
| |
| /** |
| * This is the singleton for ScriptObjects |
| */ |
| public static final Type SCRIPT_OBJECT = putInCache(new ObjectType(ScriptObject.class)); |
| |
| /** |
| * This is the singleton for integer arrays |
| */ |
| public static final ArrayType INT_ARRAY = putInCache(new ArrayType(int[].class) { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public void astore(final MethodVisitor method) { |
| method.visitInsn(IASTORE); |
| } |
| |
| @Override |
| public Type aload(final MethodVisitor method) { |
| method.visitInsn(IALOAD); |
| return INT; |
| } |
| |
| @Override |
| public Type newarray(final MethodVisitor method) { |
| method.visitIntInsn(NEWARRAY, T_INT); |
| return this; |
| } |
| |
| @Override |
| public Type getElementType() { |
| return INT; |
| } |
| }); |
| |
| /** |
| * This is the singleton for long arrays |
| */ |
| public static final ArrayType LONG_ARRAY = putInCache(new ArrayType(long[].class) { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public void astore(final MethodVisitor method) { |
| method.visitInsn(LASTORE); |
| } |
| |
| @Override |
| public Type aload(final MethodVisitor method) { |
| method.visitInsn(LALOAD); |
| return LONG; |
| } |
| |
| @Override |
| public Type newarray(final MethodVisitor method) { |
| method.visitIntInsn(NEWARRAY, T_LONG); |
| return this; |
| } |
| |
| @Override |
| public Type getElementType() { |
| return LONG; |
| } |
| }); |
| |
| /** |
| * This is the singleton for numeric arrays |
| */ |
| public static final ArrayType NUMBER_ARRAY = putInCache(new ArrayType(double[].class) { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public void astore(final MethodVisitor method) { |
| method.visitInsn(DASTORE); |
| } |
| |
| @Override |
| public Type aload(final MethodVisitor method) { |
| method.visitInsn(DALOAD); |
| return NUMBER; |
| } |
| |
| @Override |
| public Type newarray(final MethodVisitor method) { |
| method.visitIntInsn(NEWARRAY, T_DOUBLE); |
| return this; |
| } |
| |
| @Override |
| public Type getElementType() { |
| return NUMBER; |
| } |
| }); |
| |
| /** This is the singleton for object arrays */ |
| public static final ArrayType OBJECT_ARRAY = putInCache(new ArrayType(Object[].class)); |
| |
| /** This type, always an object type, just a toString override */ |
| public static final Type THIS = new ObjectType() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public String toString() { |
| return "this"; |
| } |
| }; |
| |
| /** Scope type, always an object type, just a toString override */ |
| public static final Type SCOPE = new ObjectType() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public String toString() { |
| return "scope"; |
| } |
| }; |
| |
| private static interface Unknown { |
| // EMPTY - used as a class that is absolutely not compatible with a type to represent "unknown" |
| } |
| |
| private abstract static class ValueLessType extends Type { |
| private static final long serialVersionUID = 1L; |
| |
| ValueLessType(final String name) { |
| super(name, Unknown.class, MIN_WEIGHT, 1); |
| } |
| |
| @Override |
| public Type load(final MethodVisitor method, final int slot) { |
| throw new UnsupportedOperationException("load " + slot); |
| } |
| |
| @Override |
| public void store(final MethodVisitor method, final int slot) { |
| throw new UnsupportedOperationException("store " + slot); |
| } |
| |
| @Override |
| public Type ldc(final MethodVisitor method, final Object c) { |
| throw new UnsupportedOperationException("ldc " + c); |
| } |
| |
| @Override |
| public Type loadUndefined(final MethodVisitor method) { |
| throw new UnsupportedOperationException("load undefined"); |
| } |
| |
| @Override |
| public Type loadForcedInitializer(final MethodVisitor method) { |
| throw new UnsupportedOperationException("load forced initializer"); |
| } |
| |
| @Override |
| public Type convert(final MethodVisitor method, final Type to) { |
| throw new UnsupportedOperationException("convert => " + to); |
| } |
| |
| @Override |
| public void _return(final MethodVisitor method) { |
| throw new UnsupportedOperationException("return"); |
| } |
| |
| @Override |
| public Type add(final MethodVisitor method, final int programPoint) { |
| throw new UnsupportedOperationException("add"); |
| } |
| } |
| |
| /** |
| * This is the unknown type which is used as initial type for type |
| * inference. It has the minimum type width |
| */ |
| public static final Type UNKNOWN = new ValueLessType("<unknown>") { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public String getDescriptor() { |
| return "<unknown>"; |
| } |
| |
| @Override |
| public char getBytecodeStackType() { |
| return 'U'; |
| } |
| }; |
| |
| /** |
| * This is the unknown type which is used as initial type for type |
| * inference. It has the minimum type width |
| */ |
| public static final Type SLOT_2 = new ValueLessType("<slot_2>") { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public String getDescriptor() { |
| return "<slot_2>"; |
| } |
| |
| @Override |
| public char getBytecodeStackType() { |
| throw new UnsupportedOperationException("getBytecodeStackType"); |
| } |
| }; |
| |
| private static <T extends Type> T putInCache(final T type) { |
| cache.put(type.getTypeClass(), type); |
| return type; |
| } |
| |
| /** |
| * Read resolve |
| * @return resolved type |
| */ |
| protected final Object readResolve() { |
| return Type.typeFor(clazz); |
| } |
| } |