| /* |
| * 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; |
| |
| import static jdk.nashorn.internal.codegen.Compiler.SCRIPTS_PACKAGE; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_ARGUMENTS; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_MAP; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.INIT_SCOPE; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.JAVA_THIS; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_DUAL_FIELD_PREFIX; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_SINGLE_FIELD_PREFIX; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.className; |
| import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; |
| import static jdk.nashorn.internal.lookup.Lookup.MH; |
| import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT; |
| import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT_OPTIMISTIC; |
| import static jdk.nashorn.internal.runtime.JSType.GET_UNDEFINED; |
| import static jdk.nashorn.internal.runtime.JSType.TYPE_DOUBLE_INDEX; |
| import static jdk.nashorn.internal.runtime.JSType.TYPE_INT_INDEX; |
| import static jdk.nashorn.internal.runtime.JSType.TYPE_OBJECT_INDEX; |
| import static jdk.nashorn.internal.runtime.JSType.TYPE_UNDEFINED_INDEX; |
| import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex; |
| import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; |
| |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.util.EnumSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import jdk.nashorn.internal.codegen.ClassEmitter.Flag; |
| import jdk.nashorn.internal.codegen.types.Type; |
| import jdk.nashorn.internal.runtime.AccessorProperty; |
| import jdk.nashorn.internal.runtime.AllocationStrategy; |
| import jdk.nashorn.internal.runtime.Context; |
| import jdk.nashorn.internal.runtime.FunctionScope; |
| import jdk.nashorn.internal.runtime.JSType; |
| import jdk.nashorn.internal.runtime.PropertyMap; |
| import jdk.nashorn.internal.runtime.ScriptEnvironment; |
| import jdk.nashorn.internal.runtime.ScriptObject; |
| import jdk.nashorn.internal.runtime.Undefined; |
| import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; |
| import jdk.nashorn.internal.runtime.logging.DebugLogger; |
| import jdk.nashorn.internal.runtime.logging.Loggable; |
| import jdk.nashorn.internal.runtime.logging.Logger; |
| |
| /** |
| * Generates the ScriptObject subclass structure with fields for a user objects. |
| */ |
| @Logger(name="fields") |
| public final class ObjectClassGenerator implements Loggable { |
| |
| /** |
| * Type guard to make sure we don't unnecessarily explode field storages. Rather unbox e.g. |
| * a java.lang.Number than blow up the field. Gradually, optimistic types should create almost |
| * no boxed types |
| */ |
| private static final MethodHandle IS_TYPE_GUARD = findOwnMH("isType", boolean.class, Class.class, Object.class); |
| |
| /** |
| * Marker for scope parameters |
| */ |
| private static final String SCOPE_MARKER = "P"; |
| |
| /** |
| * Minimum number of extra fields in an object. |
| */ |
| static final int FIELD_PADDING = 4; |
| |
| /** |
| * Debug field logger |
| * Should we print debugging information for fields when they are generated and getters/setters are called? |
| */ |
| private final DebugLogger log; |
| |
| /** Field types for object-only fields */ |
| private static final Type[] FIELD_TYPES_OBJECT = new Type[] { Type.OBJECT }; |
| /** Field types for dual primitive/object fields */ |
| private static final Type[] FIELD_TYPES_DUAL = new Type[] { Type.LONG, Type.OBJECT }; |
| |
| /** What type is the primitive type in dual representation */ |
| public static final Type PRIMITIVE_FIELD_TYPE = Type.LONG; |
| |
| private static final MethodHandle GET_DIFFERENT = findOwnMH("getDifferent", Object.class, Object.class, Class.class, MethodHandle.class, MethodHandle.class, int.class); |
| private static final MethodHandle GET_DIFFERENT_UNDEFINED = findOwnMH("getDifferentUndefined", Object.class, int.class); |
| |
| private static boolean initialized = false; |
| |
| /** The context */ |
| private final Context context; |
| |
| private final boolean dualFields; |
| |
| /** |
| * Constructor |
| * |
| * @param context a context |
| * @param dualFields whether to use dual fields representation |
| */ |
| public ObjectClassGenerator(final Context context, final boolean dualFields) { |
| this.context = context; |
| this.dualFields = dualFields; |
| assert context != null; |
| this.log = initLogger(context); |
| if (!initialized) { |
| initialized = true; |
| if (!dualFields) { |
| log.warning("Running with object fields only - this is a deprecated configuration."); |
| } |
| } |
| } |
| |
| @Override |
| public DebugLogger getLogger() { |
| return log; |
| } |
| |
| @Override |
| public DebugLogger initLogger(final Context ctxt) { |
| return ctxt.getLogger(this.getClass()); |
| } |
| |
| /** |
| * Pack a number into a primitive long field |
| * @param n number object |
| * @return primitive long value with all the bits in the number |
| */ |
| public static long pack(final Number n) { |
| if (n instanceof Integer) { |
| return n.intValue(); |
| } else if (n instanceof Long) { |
| return n.longValue(); |
| } else if (n instanceof Double) { |
| return Double.doubleToRawLongBits(n.doubleValue()); |
| } |
| throw new AssertionError("cannot pack" + n); |
| } |
| |
| private static String getPrefixName(final boolean dualFields) { |
| return dualFields ? JS_OBJECT_DUAL_FIELD_PREFIX.symbolName() : JS_OBJECT_SINGLE_FIELD_PREFIX.symbolName(); |
| } |
| |
| private static String getPrefixName(final String className) { |
| if (className.startsWith(JS_OBJECT_DUAL_FIELD_PREFIX.symbolName())) { |
| return getPrefixName(true); |
| } else if (className.startsWith(JS_OBJECT_SINGLE_FIELD_PREFIX.symbolName())) { |
| return getPrefixName(false); |
| } |
| throw new AssertionError("Not a structure class: " + className); |
| } |
| |
| /** |
| * Returns the class name for JavaScript objects with fieldCount fields. |
| * |
| * @param fieldCount Number of fields to allocate. |
| * @param dualFields whether to use dual fields representation |
| * @return The class name. |
| */ |
| public static String getClassName(final int fieldCount, final boolean dualFields) { |
| final String prefix = getPrefixName(dualFields); |
| return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + prefix + fieldCount : |
| SCRIPTS_PACKAGE + '/' + prefix; |
| } |
| |
| /** |
| * Returns the class name for JavaScript scope with fieldCount fields and |
| * paramCount parameters. |
| * |
| * @param fieldCount Number of fields to allocate. |
| * @param paramCount Number of parameters to allocate |
| * @param dualFields whether to use dual fields representation |
| * @return The class name. |
| */ |
| public static String getClassName(final int fieldCount, final int paramCount, final boolean dualFields) { |
| return SCRIPTS_PACKAGE + '/' + getPrefixName(dualFields) + fieldCount + SCOPE_MARKER + paramCount; |
| } |
| |
| /** |
| * Returns the number of fields in the JavaScript scope class. Its name had to be generated using either |
| * {@link #getClassName(int, boolean)} or {@link #getClassName(int, int, boolean)}. |
| * @param clazz the JavaScript scope class. |
| * @return the number of fields in the scope class. |
| */ |
| public static int getFieldCount(final Class<?> clazz) { |
| final String name = clazz.getSimpleName(); |
| final String prefix = getPrefixName(name); |
| |
| if (prefix.equals(name)) { |
| return 0; |
| } |
| final int scopeMarker = name.indexOf(SCOPE_MARKER); |
| return Integer.parseInt(scopeMarker == -1 ? name.substring(prefix.length()) : name.substring(prefix.length(), scopeMarker)); |
| } |
| |
| /** |
| * Returns the name of a field based on number and type. |
| * |
| * @param fieldIndex Ordinal of field. |
| * @param type Type of field. |
| * |
| * @return The field name. |
| */ |
| public static String getFieldName(final int fieldIndex, final Type type) { |
| return type.getDescriptor().substring(0, 1) + fieldIndex; |
| } |
| |
| /** |
| * In the world of Object fields, we also have no undefined SwitchPoint, to reduce as much potential |
| * MethodHandle overhead as possible. In that case, we explicitly need to assign undefined to fields |
| * when we initialize them. |
| * |
| * @param init constructor to generate code in |
| * @param className name of class |
| * @param fieldNames fields to initialize to undefined, where applicable |
| */ |
| private void initializeToUndefined(final MethodEmitter init, final String className, final List<String> fieldNames) { |
| if (dualFields) { |
| // no need to initialize anything to undefined in the dual field world |
| // - then we have a constant getter for undefined for any unknown type |
| return; |
| } |
| |
| if (fieldNames.isEmpty()) { |
| return; |
| } |
| |
| init.load(Type.OBJECT, JAVA_THIS.slot()); |
| init.loadUndefined(Type.OBJECT); |
| |
| final Iterator<String> iter = fieldNames.iterator(); |
| while (iter.hasNext()) { |
| final String fieldName = iter.next(); |
| if (iter.hasNext()) { |
| init.dup2(); |
| } |
| init.putField(className, fieldName, Type.OBJECT.getDescriptor()); |
| } |
| } |
| |
| /** |
| * Generate the byte codes for a JavaScript object class or scope. |
| * Class name is a function of number of fields and number of param |
| * fields |
| * |
| * @param descriptor Descriptor pulled from class name. |
| * |
| * @return Byte codes for generated class. |
| */ |
| public byte[] generate(final String descriptor) { |
| final String[] counts = descriptor.split(SCOPE_MARKER); |
| final int fieldCount = Integer.valueOf(counts[0]); |
| |
| if (counts.length == 1) { |
| return generate(fieldCount); |
| } |
| |
| final int paramCount = Integer.valueOf(counts[1]); |
| |
| return generate(fieldCount, paramCount); |
| } |
| |
| /** |
| * Generate the byte codes for a JavaScript object class with fieldCount fields. |
| * |
| * @param fieldCount Number of fields in the JavaScript object. |
| * |
| * @return Byte codes for generated class. |
| */ |
| public byte[] generate(final int fieldCount) { |
| final String className = getClassName(fieldCount, dualFields); |
| final String superName = className(ScriptObject.class); |
| final ClassEmitter classEmitter = newClassEmitter(className, superName); |
| |
| addFields(classEmitter, fieldCount); |
| |
| final MethodEmitter init = newInitMethod(classEmitter); |
| init.returnVoid(); |
| init.end(); |
| |
| final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, ScriptObject.class); |
| initWithSpillArrays.returnVoid(); |
| initWithSpillArrays.end(); |
| |
| newEmptyInit(className, classEmitter); |
| newAllocate(className, classEmitter); |
| |
| return toByteArray(className, classEmitter); |
| } |
| |
| /** |
| * Generate the byte codes for a JavaScript scope class with fieldCount fields |
| * and paramCount parameters. |
| * |
| * @param fieldCount Number of fields in the JavaScript scope. |
| * @param paramCount Number of parameters in the JavaScript scope |
| * . |
| * @return Byte codes for generated class. |
| */ |
| public byte[] generate(final int fieldCount, final int paramCount) { |
| final String className = getClassName(fieldCount, paramCount, dualFields); |
| final String superName = className(FunctionScope.class); |
| final ClassEmitter classEmitter = newClassEmitter(className, superName); |
| final List<String> initFields = addFields(classEmitter, fieldCount); |
| |
| final MethodEmitter init = newInitScopeMethod(classEmitter); |
| initializeToUndefined(init, className, initFields); |
| init.returnVoid(); |
| init.end(); |
| |
| final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, FunctionScope.class); |
| initializeToUndefined(initWithSpillArrays, className, initFields); |
| initWithSpillArrays.returnVoid(); |
| initWithSpillArrays.end(); |
| |
| final MethodEmitter initWithArguments = newInitScopeWithArgumentsMethod(classEmitter); |
| initializeToUndefined(initWithArguments, className, initFields); |
| initWithArguments.returnVoid(); |
| initWithArguments.end(); |
| |
| return toByteArray(className, classEmitter); |
| } |
| |
| /** |
| * Generates the needed fields. |
| * |
| * @param classEmitter Open class emitter. |
| * @param fieldCount Number of fields. |
| * |
| * @return List fields that need to be initialized. |
| */ |
| private List<String> addFields(final ClassEmitter classEmitter, final int fieldCount) { |
| final List<String> initFields = new LinkedList<>(); |
| final Type[] fieldTypes = dualFields ? FIELD_TYPES_DUAL : FIELD_TYPES_OBJECT; |
| for (int i = 0; i < fieldCount; i++) { |
| for (final Type type : fieldTypes) { |
| final String fieldName = getFieldName(i, type); |
| classEmitter.field(fieldName, type.getTypeClass()); |
| |
| if (type == Type.OBJECT) { |
| initFields.add(fieldName); |
| } |
| } |
| } |
| |
| return initFields; |
| } |
| |
| /** |
| * Allocate and initialize a new class emitter. |
| * |
| * @param className Name of JavaScript class. |
| * |
| * @return Open class emitter. |
| */ |
| private ClassEmitter newClassEmitter(final String className, final String superName) { |
| final ClassEmitter classEmitter = new ClassEmitter(context, className, superName); |
| classEmitter.begin(); |
| |
| return classEmitter; |
| } |
| |
| /** |
| * Allocate and initialize a new <init> method. |
| * |
| * @param classEmitter Open class emitter. |
| * |
| * @return Open method emitter. |
| */ |
| private static MethodEmitter newInitMethod(final ClassEmitter classEmitter) { |
| final MethodEmitter init = classEmitter.init(PropertyMap.class); |
| init.begin(); |
| init.load(Type.OBJECT, JAVA_THIS.slot()); |
| init.load(Type.OBJECT, INIT_MAP.slot()); |
| init.invoke(constructorNoLookup(ScriptObject.class, PropertyMap.class)); |
| |
| return init; |
| } |
| |
| private static MethodEmitter newInitWithSpillArraysMethod(final ClassEmitter classEmitter, final Class<?> superClass) { |
| final MethodEmitter init = classEmitter.init(PropertyMap.class, long[].class, Object[].class); |
| init.begin(); |
| init.load(Type.OBJECT, JAVA_THIS.slot()); |
| init.load(Type.OBJECT, INIT_MAP.slot()); |
| init.load(Type.LONG_ARRAY, 2); |
| init.load(Type.OBJECT_ARRAY, 3); |
| init.invoke(constructorNoLookup(superClass, PropertyMap.class, long[].class, Object[].class)); |
| |
| return init; |
| } |
| |
| /** |
| * Allocate and initialize a new <init> method for scopes. |
| * @param classEmitter Open class emitter. |
| * @return Open method emitter. |
| */ |
| private static MethodEmitter newInitScopeMethod(final ClassEmitter classEmitter) { |
| final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class); |
| init.begin(); |
| init.load(Type.OBJECT, JAVA_THIS.slot()); |
| init.load(Type.OBJECT, INIT_MAP.slot()); |
| init.load(Type.OBJECT, INIT_SCOPE.slot()); |
| init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class)); |
| |
| return init; |
| } |
| |
| /** |
| * Allocate and initialize a new <init> method for scopes with arguments. |
| * @param classEmitter Open class emitter. |
| * @return Open method emitter. |
| */ |
| private static MethodEmitter newInitScopeWithArgumentsMethod(final ClassEmitter classEmitter) { |
| final MethodEmitter init = classEmitter.init(PropertyMap.class, ScriptObject.class, ScriptObject.class); |
| init.begin(); |
| init.load(Type.OBJECT, JAVA_THIS.slot()); |
| init.load(Type.OBJECT, INIT_MAP.slot()); |
| init.load(Type.OBJECT, INIT_SCOPE.slot()); |
| init.load(Type.OBJECT, INIT_ARGUMENTS.slot()); |
| init.invoke(constructorNoLookup(FunctionScope.class, PropertyMap.class, ScriptObject.class, ScriptObject.class)); |
| |
| return init; |
| } |
| |
| /** |
| * Add an empty <init> method to the JavaScript class. |
| * |
| * @param classEmitter Open class emitter. |
| * @param className Name of JavaScript class. |
| */ |
| private static void newEmptyInit(final String className, final ClassEmitter classEmitter) { |
| final MethodEmitter emptyInit = classEmitter.init(); |
| emptyInit.begin(); |
| emptyInit.load(Type.OBJECT, JAVA_THIS.slot()); |
| emptyInit.loadNull(); |
| emptyInit.invoke(constructorNoLookup(className, PropertyMap.class)); |
| emptyInit.returnVoid(); |
| emptyInit.end(); |
| } |
| |
| /** |
| * Add an empty <init> method to the JavaScript class. |
| * |
| * @param classEmitter Open class emitter. |
| * @param className Name of JavaScript class. |
| */ |
| private static void newAllocate(final String className, final ClassEmitter classEmitter) { |
| final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.symbolName(), ScriptObject.class, PropertyMap.class); |
| allocate.begin(); |
| allocate._new(className, Type.typeFor(ScriptObject.class)); |
| allocate.dup(); |
| allocate.load(Type.typeFor(PropertyMap.class), 0); |
| allocate.invoke(constructorNoLookup(className, PropertyMap.class)); |
| allocate._return(); |
| allocate.end(); |
| } |
| |
| /** |
| * Collects the byte codes for a generated JavaScript class. |
| * |
| * @param classEmitter Open class emitter. |
| * @return Byte codes for the class. |
| */ |
| private byte[] toByteArray(final String className, final ClassEmitter classEmitter) { |
| classEmitter.end(); |
| |
| final byte[] code = classEmitter.toByteArray(); |
| final ScriptEnvironment env = context.getEnv(); |
| |
| DumpBytecode.dumpBytecode(env, log, code, className); |
| |
| if (env._verify_code) { |
| context.verify(code); |
| } |
| |
| return code; |
| } |
| |
| /** Double to long bits, used with --dual-fields for primitive double values */ |
| public static final MethodHandle PACK_DOUBLE = |
| MH.explicitCastArguments(MH.findStatic(MethodHandles.publicLookup(), Double.class, "doubleToRawLongBits", MH.type(long.class, double.class)), MH.type(long.class, double.class)); |
| |
| /** double bits to long, used with --dual-fields for primitive double values */ |
| public static final MethodHandle UNPACK_DOUBLE = |
| MH.findStatic(MethodHandles.publicLookup(), Double.class, "longBitsToDouble", MH.type(double.class, long.class)); |
| |
| //type != forType, so use the correct getter for forType, box it and throw |
| @SuppressWarnings("unused") |
| private static Object getDifferent(final Object receiver, final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) { |
| //create the sametype getter, and upcast to value. no matter what the store format is, |
| // |
| final MethodHandle sameTypeGetter = getterForType(forType, primitiveGetter, objectGetter); |
| final MethodHandle mh = MH.asType(sameTypeGetter, sameTypeGetter.type().changeReturnType(Object.class)); |
| try { |
| final Object value = mh.invokeExact(receiver); |
| throw new UnwarrantedOptimismException(value, programPoint); |
| } catch (final Error | RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| private static Object getDifferentUndefined(final int programPoint) { |
| throw new UnwarrantedOptimismException(Undefined.getUndefined(), programPoint); |
| } |
| |
| private static MethodHandle getterForType(final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter) { |
| switch (getAccessorTypeIndex(forType)) { |
| case TYPE_INT_INDEX: |
| return MH.explicitCastArguments(primitiveGetter, primitiveGetter.type().changeReturnType(int.class)); |
| case TYPE_DOUBLE_INDEX: |
| return MH.filterReturnValue(primitiveGetter, UNPACK_DOUBLE); |
| case TYPE_OBJECT_INDEX: |
| return objectGetter; |
| default: |
| throw new AssertionError(forType); |
| } |
| } |
| |
| //no optimism here. we do unconditional conversion to types |
| private static MethodHandle createGetterInner(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final List<MethodHandle> converters, final int programPoint) { |
| final int fti = forType == null ? TYPE_UNDEFINED_INDEX : getAccessorTypeIndex(forType); |
| final int ti = getAccessorTypeIndex(type); |
| //this means fail if forType != type |
| final boolean isOptimistic = converters == CONVERT_OBJECT_OPTIMISTIC; |
| final boolean isPrimitiveStorage = forType != null && forType.isPrimitive(); |
| |
| //which is the primordial getter |
| final MethodHandle getter = primitiveGetter == null ? objectGetter : isPrimitiveStorage ? primitiveGetter : objectGetter; |
| |
| if (forType == null) { |
| if (isOptimistic) { |
| //return undefined if asking for object. otherwise throw UnwarrantedOptimismException |
| if (ti == TYPE_OBJECT_INDEX) { |
| return MH.dropArguments(GET_UNDEFINED.get(TYPE_OBJECT_INDEX), 0, Object.class); |
| } |
| //throw exception |
| return MH.asType( |
| MH.dropArguments( |
| MH.insertArguments( |
| GET_DIFFERENT_UNDEFINED, |
| 0, |
| programPoint), |
| 0, |
| Object.class), |
| getter.type().changeReturnType(type)); |
| } |
| //return an undefined and coerce it to the appropriate type |
| return MH.dropArguments(GET_UNDEFINED.get(ti), 0, Object.class); |
| } |
| |
| assert primitiveGetter != null || forType == Object.class : forType; |
| |
| if (isOptimistic) { |
| if (fti < ti) { |
| //asking for a wider type than currently stored. then it's OK to coerce. |
| //e.g. stored as int, ask for long or double |
| //e.g. stored as long, ask for double |
| assert fti != TYPE_UNDEFINED_INDEX; |
| final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter); |
| return MH.asType(tgetter, tgetter.type().changeReturnType(type)); |
| } else if (fti == ti) { |
| //Fast path, never throw exception - exact getter, just unpack if needed |
| return getterForType(forType, primitiveGetter, objectGetter); |
| } else { |
| assert fti > ti; |
| //if asking for a narrower type than the storage - throw exception |
| //unless FTI is object, in that case we have to go through the converters |
| //there is no |
| if (fti == TYPE_OBJECT_INDEX) { |
| return MH.filterReturnValue( |
| objectGetter, |
| MH.insertArguments( |
| converters.get(ti), |
| 1, |
| programPoint)); |
| } |
| |
| //asking for narrower primitive than we have stored, that is an |
| //UnwarrantedOptimismException |
| return MH.asType( |
| MH.filterArguments( |
| objectGetter, |
| 0, |
| MH.insertArguments( |
| GET_DIFFERENT, |
| 1, |
| forType, |
| primitiveGetter, |
| objectGetter, |
| programPoint)), |
| objectGetter.type().changeReturnType(type)); |
| } |
| } |
| |
| assert !isOptimistic; |
| // freely coerce the result to whatever you asked for, this is e.g. Object->int for a & b |
| final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter); |
| if (fti == TYPE_OBJECT_INDEX) { |
| if (fti != ti) { |
| return MH.filterReturnValue(tgetter, CONVERT_OBJECT.get(ti)); |
| } |
| return tgetter; |
| } |
| |
| assert primitiveGetter != null; |
| final MethodType tgetterType = tgetter.type(); |
| switch (fti) { |
| case TYPE_INT_INDEX: { |
| return MH.asType(tgetter, tgetterType.changeReturnType(type)); |
| } |
| case TYPE_DOUBLE_INDEX: |
| switch (ti) { |
| case TYPE_INT_INDEX: |
| return MH.filterReturnValue(tgetter, JSType.TO_INT32_D.methodHandle); |
| case TYPE_DOUBLE_INDEX: |
| assert tgetterType.returnType() == double.class; |
| return tgetter; |
| default: |
| return MH.asType(tgetter, tgetterType.changeReturnType(Object.class)); |
| } |
| default: |
| throw new UnsupportedOperationException(forType + "=>" + type); |
| } |
| } |
| |
| /** |
| * Given a primitiveGetter (optional for non dual fields) and an objectSetter that retrieve |
| * the primitive and object version of a field respectively, return one with the correct |
| * method type and the correct filters. For example, if the value is stored as a double |
| * and we want an Object getter, in the dual fields world we'd pick the primitiveGetter, |
| * which reads a long, use longBitsToDouble on the result to unpack it, and then change the |
| * return type to Object, boxing it. In the objects only world there are only object fields, |
| * primitives are boxed when asked for them and we don't need to bother with primitive encoding |
| * (or even undefined, which if forType==null) representation, so we just return whatever is |
| * in the object field. The object field is always initiated to Undefined, so here, where we have |
| * the representation for Undefined in all our bits, this is not a problem. |
| * <p> |
| * Representing undefined in a primitive is hard, for an int there aren't enough bits, for a long |
| * we could limit the width of a representation, and for a double (as long as it is stored as long, |
| * as all NaNs will turn into QNaN on ia32, which is one bit pattern, we should use a special NaN). |
| * Naturally we could have special undefined values for all types which mean "go look in a wider field", |
| * but the guards needed on every getter took too much time. |
| * <p> |
| * To see how this is used, look for example in {@link AccessorProperty#getGetter} |
| * <p> |
| * @param forType representation of the underlying type in the field, null if undefined |
| * @param type type to retrieve it as |
| * @param primitiveGetter getter to read the primitive version of this field (null if Objects Only) |
| * @param objectGetter getter to read the object version of this field |
| * @param programPoint program point for getter, if program point is INVALID_PROGRAM_POINT, then this is not an optimistic getter |
| * |
| * @return getter for the given representation that returns the given type |
| */ |
| public static MethodHandle createGetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) { |
| return createGetterInner( |
| forType, |
| type, |
| primitiveGetter, |
| objectGetter, |
| isValid(programPoint) ? CONVERT_OBJECT_OPTIMISTIC : CONVERT_OBJECT, |
| programPoint); |
| } |
| |
| /** |
| * This is similar to the {@link ObjectClassGenerator#createGetter} function. Performs |
| * the necessary operations to massage a setter operand of type {@code type} to |
| * fit into the primitive field (if primitive and dual fields is enabled) or into |
| * the object field (box if primitive and dual fields is disabled) |
| * |
| * @param forType representation of the underlying object |
| * @param type representation of field to write, and setter signature |
| * @param primitiveSetter setter that writes to the primitive field (null if Objects Only) |
| * @param objectSetter setter that writes to the object field |
| * |
| * @return the setter for the given representation that takes a {@code type} |
| */ |
| public static MethodHandle createSetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveSetter, final MethodHandle objectSetter) { |
| assert forType != null; |
| |
| final int fti = getAccessorTypeIndex(forType); |
| final int ti = getAccessorTypeIndex(type); |
| |
| if (fti == TYPE_OBJECT_INDEX || primitiveSetter == null) { |
| if (ti == TYPE_OBJECT_INDEX) { |
| return objectSetter; |
| } |
| |
| return MH.asType(objectSetter, objectSetter.type().changeParameterType(1, type)); |
| } |
| |
| final MethodType pmt = primitiveSetter.type(); |
| |
| switch (fti) { |
| case TYPE_INT_INDEX: |
| switch (ti) { |
| case TYPE_INT_INDEX: |
| return MH.asType(primitiveSetter, pmt.changeParameterType(1, int.class)); |
| case TYPE_DOUBLE_INDEX: |
| return MH.filterArguments(primitiveSetter, 1, PACK_DOUBLE); |
| default: |
| return objectSetter; |
| } |
| case TYPE_DOUBLE_INDEX: |
| if (ti == TYPE_OBJECT_INDEX) { |
| return objectSetter; |
| } |
| return MH.asType(MH.filterArguments(primitiveSetter, 1, PACK_DOUBLE), pmt.changeParameterType(1, type)); |
| default: |
| throw new UnsupportedOperationException(forType + "=>" + type); |
| } |
| } |
| |
| @SuppressWarnings("unused") |
| private static boolean isType(final Class<?> boxedForType, final Object x) { |
| return x != null && x.getClass() == boxedForType; |
| } |
| |
| private static Class<? extends Number> getBoxedType(final Class<?> forType) { |
| if (forType == int.class) { |
| return Integer.class; |
| } |
| |
| if (forType == long.class) { |
| return Long.class; |
| } |
| |
| if (forType == double.class) { |
| return Double.class; |
| } |
| |
| assert false; |
| return null; |
| } |
| |
| /** |
| * If we are setting boxed types (because the compiler couldn't determine which they were) to |
| * a primitive field, we can reuse the primitive field getter, as long as we are setting an element |
| * of the same boxed type as the primitive type representation |
| * |
| * @param forType the current type |
| * @param primitiveSetter primitive setter for the current type with an element of the current type |
| * @param objectSetter the object setter |
| * |
| * @return method handle that checks if the element to be set is of the current type, even though it's boxed |
| * and instead of using the generic object setter, that would blow up the type and invalidate the map, |
| * unbox it and call the primitive setter instead |
| */ |
| public static MethodHandle createGuardBoxedPrimitiveSetter(final Class<?> forType, final MethodHandle primitiveSetter, final MethodHandle objectSetter) { |
| final Class<? extends Number> boxedForType = getBoxedType(forType); |
| //object setter that checks for primitive if current type is primitive |
| return MH.guardWithTest( |
| MH.insertArguments( |
| MH.dropArguments( |
| IS_TYPE_GUARD, |
| 1, |
| Object.class), |
| 0, |
| boxedForType), |
| MH.asType( |
| primitiveSetter, |
| objectSetter.type()), |
| objectSetter); |
| } |
| /** |
| * Add padding to field count to avoid creating too many classes and have some spare fields |
| * @param count the field count |
| * @return the padded field count |
| */ |
| static int getPaddedFieldCount(final int count) { |
| return count / FIELD_PADDING * FIELD_PADDING + FIELD_PADDING; |
| } |
| |
| private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { |
| return MH.findStatic(MethodHandles.lookup(), ObjectClassGenerator.class, name, MH.type(rtype, types)); |
| } |
| |
| /** |
| * Creates the allocator class name and property map for a constructor function with the specified |
| * number of "this" properties that it initializes. |
| * @param thisProperties number of properties assigned to "this" |
| * @return the allocation strategy |
| */ |
| static AllocationStrategy createAllocationStrategy(final int thisProperties, final boolean dualFields) { |
| final int paddedFieldCount = getPaddedFieldCount(thisProperties); |
| return new AllocationStrategy(paddedFieldCount, dualFields); |
| } |
| } |