/* * 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 final 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 GETTERS_SETTERS = new ClassValue() { @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 for bound properties. This is used for properties created by * {@link ScriptRuntime#mergeScope} and the Nashorn {@code Object.bindProperties} method. * The former is used to add a script's defined globals to the current global scope while * still storing them in a JO-prefixed ScriptObject class. * *

All properties created by this constructor have the {@link #IS_BOUND} flag set.

* * @param property accessor property to rebind * @param delegate delegate object to rebind receiver to */ 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); // Properties created this way are bound to a delegate this.flags |= IS_BOUND; 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 */ 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)); } }