| /* |
| * 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.runtime; |
| |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.ACCESSOR_TYPES; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.LOG; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_TYPE; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGuardBoxedPrimitiveSetter; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getAccessorType; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getAccessorTypeIndex; |
| import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getNumberOfAccessorTypes; |
| import static jdk.nashorn.internal.lookup.Lookup.MH; |
| import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName; |
| |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import jdk.nashorn.internal.codegen.ObjectClassGenerator; |
| import jdk.nashorn.internal.codegen.types.Type; |
| import jdk.nashorn.internal.lookup.Lookup; |
| import jdk.nashorn.internal.lookup.MethodHandleFactory; |
| |
| /** |
| * An AccessorProperty is the most generic property type. An AccessorProperty is |
| * represented as fields in a ScriptObject class. |
| */ |
| public class AccessorProperty extends Property { |
| private static final MethodHandles.Lookup lookup = MethodHandles.lookup(); |
| private static final MethodHandle REPLACE_MAP = findOwnMH("replaceMap", Object.class, Object.class, PropertyMap.class, String.class, Class.class, Class.class); |
| |
| private static final int NOOF_TYPES = getNumberOfAccessorTypes(); |
| |
| /** |
| * Properties in different maps for the same structure class will share their field getters and setters. This could |
| * be further extended to other method handles that are looked up in the AccessorProperty constructor, but right now |
| * these are the most frequently retrieved ones, and lookup of method handle natives only registers in the profiler |
| * for them. |
| */ |
| private static ClassValue<GettersSetters> GETTERS_SETTERS = new ClassValue<GettersSetters>() { |
| @Override |
| protected GettersSetters computeValue(Class<?> structure) { |
| return new GettersSetters(structure); |
| } |
| }; |
| |
| /** Property getter cache */ |
| private MethodHandle[] getters = new MethodHandle[NOOF_TYPES]; |
| |
| private static final MethodType[] ACCESSOR_GETTER_TYPES = new MethodType[NOOF_TYPES]; |
| private static final MethodType[] ACCESSOR_SETTER_TYPES = new MethodType[NOOF_TYPES]; |
| private static final MethodType ACCESSOR_GETTER_PRIMITIVE_TYPE; |
| private static final MethodType ACCESSOR_SETTER_PRIMITIVE_TYPE; |
| private static final MethodHandle SPILL_ELEMENT_GETTER; |
| private static final MethodHandle SPILL_ELEMENT_SETTER; |
| |
| private static final int SPILL_CACHE_SIZE = 8; |
| private static final MethodHandle[] SPILL_ACCESSORS = new MethodHandle[SPILL_CACHE_SIZE * 2]; |
| |
| static { |
| MethodType getterPrimitiveType = null; |
| MethodType setterPrimitiveType = null; |
| |
| for (int i = 0; i < NOOF_TYPES; i++) { |
| final Type type = ACCESSOR_TYPES.get(i); |
| ACCESSOR_GETTER_TYPES[i] = MH.type(type.getTypeClass(), Object.class); |
| ACCESSOR_SETTER_TYPES[i] = MH.type(void.class, Object.class, type.getTypeClass()); |
| |
| if (type == PRIMITIVE_TYPE) { |
| getterPrimitiveType = ACCESSOR_GETTER_TYPES[i]; |
| setterPrimitiveType = ACCESSOR_SETTER_TYPES[i]; |
| } |
| } |
| |
| ACCESSOR_GETTER_PRIMITIVE_TYPE = getterPrimitiveType; |
| ACCESSOR_SETTER_PRIMITIVE_TYPE = setterPrimitiveType; |
| |
| final MethodType spillGetterType = MethodType.methodType(Object[].class, Object.class); |
| final MethodHandle spillGetter = MH.asType(MH.getter(MethodHandles.lookup(), ScriptObject.class, "spill", Object[].class), spillGetterType); |
| SPILL_ELEMENT_GETTER = MH.filterArguments(MH.arrayElementGetter(Object[].class), 0, spillGetter); |
| SPILL_ELEMENT_SETTER = MH.filterArguments(MH.arrayElementSetter(Object[].class), 0, spillGetter); |
| } |
| |
| /** |
| * Create a new accessor property. Factory method used by nasgen generated code. |
| * |
| * @param key {@link Property} key. |
| * @param propertyFlags {@link Property} flags. |
| * @param getter {@link Property} get accessor method. |
| * @param setter {@link Property} set accessor method. |
| * |
| * @return New {@link AccessorProperty} created. |
| */ |
| public static AccessorProperty create(final String key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter) { |
| return new AccessorProperty(key, propertyFlags, -1, getter, setter); |
| } |
| |
| /** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ |
| private MethodHandle primitiveGetter; |
| |
| /** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ |
| private MethodHandle primitiveSetter; |
| |
| /** Seed getter for the Object version of this field */ |
| private MethodHandle objectGetter; |
| |
| /** Seed setter for the Object version of this field */ |
| private MethodHandle objectSetter; |
| |
| /** |
| * Current type of this object, in object only mode, this is an Object.class. In dual-fields mode |
| * null means undefined, and primitive types are allowed. The reason a special type is used for |
| * undefined, is that are no bits left to represent it in primitive types |
| */ |
| private Class<?> currentType; |
| |
| /** |
| * Delegate constructor. This is used when adding properties to the Global scope, which |
| * is necessary for outermost levels in a script (the ScriptObject is represented by |
| * a JO-prefixed ScriptObject class, but the properties need to be in the Global scope |
| * and are thus rebound with that as receiver |
| * |
| * @param property accessor property to rebind |
| * @param delegate delegate object to rebind receiver to |
| */ |
| public AccessorProperty(final AccessorProperty property, final Object delegate) { |
| super(property); |
| |
| this.primitiveGetter = bindTo(property.primitiveGetter, delegate); |
| this.primitiveSetter = bindTo(property.primitiveSetter, delegate); |
| this.objectGetter = bindTo(property.ensureObjectGetter(), delegate); |
| this.objectSetter = bindTo(property.ensureObjectSetter(), delegate); |
| |
| setCurrentType(property.getCurrentType()); |
| } |
| |
| /** |
| * Constructor for spill properties. Array getters and setters will be created on demand. |
| * |
| * @param key the property key |
| * @param flags the property flags |
| * @param slot spill slot |
| */ |
| public AccessorProperty(final String key, final int flags, final int slot) { |
| super(key, flags, slot); |
| assert (flags & IS_SPILL) == IS_SPILL; |
| |
| setCurrentType(Object.class); |
| } |
| |
| /** |
| * Constructor. Similar to the constructor with both primitive getters and setters, the difference |
| * here being that only one getter and setter (setter is optional for non writable fields) is given |
| * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes |
| * |
| * @param key the property key |
| * @param flags the property flags |
| * @param slot the property field number or spill slot |
| * @param getter the property getter |
| * @param setter the property setter or null if non writable, non configurable |
| */ |
| public AccessorProperty(final String key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) { |
| super(key, flags, slot); |
| |
| // we don't need to prep the setters these will never be invalidated as this is a nasgen |
| // or known type getter/setter. No invalidations will take place |
| |
| final Class<?> getterType = getter.type().returnType(); |
| final Class<?> setterType = setter == null ? null : setter.type().parameterType(1); |
| |
| assert setterType == null || setterType == getterType; |
| |
| if (getterType.isPrimitive()) { |
| for (int i = 0; i < NOOF_TYPES; i++) { |
| getters[i] = MH.asType( |
| Lookup.filterReturnType( |
| getter, |
| getAccessorType(i).getTypeClass()), |
| ACCESSOR_GETTER_TYPES[i]); |
| } |
| } else { |
| objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter; |
| objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter; |
| } |
| |
| setCurrentType(getterType); |
| } |
| |
| private static class GettersSetters { |
| final MethodHandle[] getters; |
| final MethodHandle[] setters; |
| |
| public GettersSetters(Class<?> structure) { |
| final int fieldCount = ObjectClassGenerator.getFieldCount(structure); |
| getters = new MethodHandle[fieldCount]; |
| setters = new MethodHandle[fieldCount]; |
| for(int i = 0; i < fieldCount; ++i) { |
| final String fieldName = ObjectClassGenerator.getFieldName(i, Type.OBJECT); |
| getters[i] = MH.asType(MH.getter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.GET_OBJECT_TYPE); |
| setters[i] = MH.asType(MH.setter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.SET_OBJECT_TYPE); |
| } |
| } |
| } |
| |
| /** |
| * Constructor for dual field AccessorPropertys. |
| * |
| * @param key property key |
| * @param flags property flags |
| * @param structure structure for objects associated with this property |
| * @param slot property field number or spill slot |
| */ |
| public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot) { |
| super(key, flags, slot); |
| |
| /* |
| * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also |
| * works in dual field mode, it only means that the property never has a primitive |
| * representation. |
| */ |
| primitiveGetter = null; |
| primitiveSetter = null; |
| |
| if (isParameter() && hasArguments()) { |
| final MethodHandle arguments = MH.getter(lookup, structure, "arguments", ScriptObject.class); |
| |
| objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE); |
| objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE); |
| } else { |
| final GettersSetters gs = GETTERS_SETTERS.get(structure); |
| objectGetter = gs.getters[slot]; |
| objectSetter = gs.setters[slot]; |
| |
| if (!OBJECT_FIELDS_ONLY) { |
| final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(slot, PRIMITIVE_TYPE); |
| final Class<?> typeClass = PRIMITIVE_TYPE.getTypeClass(); |
| primitiveGetter = MH.asType(MH.getter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_GETTER_PRIMITIVE_TYPE); |
| primitiveSetter = MH.asType(MH.setter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_SETTER_PRIMITIVE_TYPE); |
| } |
| } |
| |
| Class<?> initialType = null; |
| |
| if (OBJECT_FIELDS_ONLY || isAlwaysObject()) { |
| initialType = Object.class; |
| } else if (!canBePrimitive()) { |
| info(key + " cannot be primitive"); |
| initialType = Object.class; |
| } else { |
| info(key + " CAN be primitive"); |
| if (!canBeUndefined()) { |
| info(key + " is always defined"); |
| initialType = int.class; //double works too for less type invalidation, but this requires experimentation, e.g. var x = 17; x += 2 will turn it into double now because of lack of range analysis |
| } |
| } |
| |
| // is always object means "is never initialized to undefined, and always of object type |
| setCurrentType(initialType); |
| } |
| |
| /** |
| * Copy constructor |
| * |
| * @param property source property |
| */ |
| protected AccessorProperty(final AccessorProperty property) { |
| super(property); |
| |
| this.getters = property.getters; |
| this.primitiveGetter = property.primitiveGetter; |
| this.primitiveSetter = property.primitiveSetter; |
| this.objectGetter = property.objectGetter; |
| this.objectSetter = property.objectSetter; |
| |
| setCurrentType(property.getCurrentType()); |
| } |
| |
| private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) { |
| if (mh == null) { |
| return null; |
| } |
| |
| return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class); |
| } |
| |
| @Override |
| protected Property copy() { |
| return new AccessorProperty(this); |
| } |
| |
| @Override |
| public void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { |
| if (isSpill()) { |
| self.spill[getSlot()] = value; |
| } else { |
| try { |
| getSetter(Object.class, self.getMap()).invokeExact((Object)self, value); |
| } catch (final Error|RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| } |
| |
| @Override |
| public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { |
| if (isSpill()) { |
| return self.spill[getSlot()]; |
| } |
| |
| try { |
| return getGetter(Object.class).invokeExact((Object)self); |
| } catch (final Error|RuntimeException e) { |
| throw e; |
| } catch (final Throwable e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| // Spill getters and setters are lazily initialized, see JDK-8011630 |
| private MethodHandle ensureObjectGetter() { |
| if (isSpill() && objectGetter == null) { |
| objectGetter = getSpillGetter(); |
| } |
| return objectGetter; |
| } |
| |
| private MethodHandle ensureObjectSetter() { |
| if (isSpill() && objectSetter == null) { |
| objectSetter = getSpillSetter(); |
| } |
| return objectSetter; |
| } |
| |
| @Override |
| public MethodHandle getGetter(final Class<?> type) { |
| final int i = getAccessorTypeIndex(type); |
| ensureObjectGetter(); |
| |
| if (getters[i] == null) { |
| getters[i] = debug( |
| createGetter(currentType, type, primitiveGetter, objectGetter), |
| currentType, type, "get"); |
| } |
| |
| return getters[i]; |
| } |
| |
| private Property getWiderProperty(final Class<?> type) { |
| final AccessorProperty newProperty = new AccessorProperty(this); |
| newProperty.invalidate(type); |
| return newProperty; |
| } |
| |
| private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) { |
| final PropertyMap newMap = oldMap.replaceProperty(this, newProperty); |
| assert oldMap.size() > 0; |
| assert newMap.size() == oldMap.size(); |
| return newMap; |
| } |
| |
| // the final three arguments are for debug printout purposes only |
| @SuppressWarnings("unused") |
| private static Object replaceMap(final Object sobj, final PropertyMap newMap, final String key, final Class<?> oldType, final Class<?> newType) { |
| if (DEBUG_FIELDS) { |
| final PropertyMap oldMap = ((ScriptObject)sobj).getMap(); |
| info("Type change for '" + key + "' " + oldType + "=>" + newType); |
| finest("setting map " + sobj + " from " + Debug.id(oldMap) + " to " + Debug.id(newMap) + " " + oldMap + " => " + newMap); |
| } |
| ((ScriptObject)sobj).setMap(newMap); |
| return sobj; |
| } |
| |
| private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) { |
| ensureObjectSetter(); |
| MethodHandle mh = createSetter(forType, type, primitiveSetter, objectSetter); |
| mh = debug(mh, currentType, type, "set"); |
| return mh; |
| } |
| |
| @Override |
| public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) { |
| final int i = getAccessorTypeIndex(type); |
| final int ci = currentType == null ? -1 : getAccessorTypeIndex(currentType); |
| final Class<?> forType = currentType == null ? type : currentType; |
| |
| //if we are asking for an object setter, but are still a primitive type, we might try to box it |
| MethodHandle mh; |
| |
| if (needsInvalidator(i, ci)) { |
| final Property newProperty = getWiderProperty(type); |
| final PropertyMap newMap = getWiderMap(currentMap, newProperty); |
| final MethodHandle widerSetter = newProperty.getSetter(type, newMap); |
| final MethodHandle explodeTypeSetter = MH.filterArguments(widerSetter, 0, MH.insertArguments(REPLACE_MAP, 1, newMap, getKey(), currentType, type)); |
| if (currentType != null && currentType.isPrimitive() && type == Object.class) { |
| //might try a box check on this to avoid widening field to object storage |
| mh = createGuardBoxedPrimitiveSetter(currentType, generateSetter(currentType, currentType), explodeTypeSetter); |
| } else { |
| mh = explodeTypeSetter; |
| } |
| } else { |
| mh = generateSetter(forType, type); |
| } |
| |
| return mh; |
| } |
| |
| @Override |
| public boolean canChangeType() { |
| if (OBJECT_FIELDS_ONLY) { |
| return false; |
| } |
| return currentType != Object.class && (isConfigurable() || isWritable()); |
| } |
| |
| private boolean needsInvalidator(final int ti, final int fti) { |
| return canChangeType() && ti > fti; |
| } |
| |
| private void invalidate(final Class<?> newType) { |
| getters = new MethodHandle[NOOF_TYPES]; |
| setCurrentType(newType); |
| } |
| |
| private MethodHandle getSpillGetter() { |
| final int slot = getSlot(); |
| MethodHandle getter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2] : null; |
| if (getter == null) { |
| getter = MH.insertArguments(SPILL_ELEMENT_GETTER, 1, slot); |
| if (slot < SPILL_CACHE_SIZE) { |
| SPILL_ACCESSORS[slot * 2 + 0] = getter; |
| } |
| } |
| return getter; |
| } |
| |
| private MethodHandle getSpillSetter() { |
| final int slot = getSlot(); |
| MethodHandle setter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2 + 1] : null; |
| if (setter == null) { |
| setter = MH.insertArguments(SPILL_ELEMENT_SETTER, 1, slot); |
| if (slot < SPILL_CACHE_SIZE) { |
| SPILL_ACCESSORS[slot * 2 + 1] = setter; |
| } |
| } |
| return setter; |
| } |
| |
| private static void finest(final String str) { |
| if (DEBUG_FIELDS) { |
| LOG.finest(str); |
| } |
| } |
| |
| private static void info(final String str) { |
| if (DEBUG_FIELDS) { |
| LOG.info(str); |
| } |
| } |
| |
| private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) { |
| if (DEBUG_FIELDS) { |
| return MethodHandleFactory.addDebugPrintout( |
| LOG, |
| mh, |
| tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", forType=" + stripName(forType) + ", type=" + stripName(type) + ')'); |
| } |
| return mh; |
| } |
| |
| private void setCurrentType(final Class<?> currentType) { |
| this.currentType = currentType; |
| } |
| |
| @Override |
| public Class<?> getCurrentType() { |
| return currentType; |
| } |
| |
| private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { |
| return MH.findStatic(lookup, AccessorProperty.class, name, MH.type(rtype, types)); |
| } |
| |
| } |