8134609: Allow constructors with same prototoype map to share the allocator map
Reviewed-by: attila, sundar
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJSAdapter.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/objects/NativeJSAdapter.java Wed Sep 16 14:42:32 2015 +0200
@@ -627,7 +627,7 @@
return new GuardedInvocation(MH.dropArguments(MH.constant(Object.class,
func.createBound(this, new Object[] { name })), 0, Object.class),
testJSAdaptor(adaptee, null, null, null),
- adaptee.getProtoSwitchPoint(__call__, find.getOwner()));
+ adaptee.getProtoSwitchPoints(__call__, find.getOwner()), null);
}
}
throw typeError("no.such.function", desc.getNameToken(2), ScriptRuntime.safeToString(this));
@@ -698,7 +698,7 @@
return new GuardedInvocation(
methodHandle,
testJSAdaptor(adaptee, findData.getGetter(Object.class, INVALID_PROGRAM_POINT, null), findData.getOwner(), func),
- adaptee.getProtoSwitchPoint(hook, findData.getOwner()));
+ adaptee.getProtoSwitchPoints(hook, findData.getOwner()), null);
}
}
}
@@ -710,7 +710,7 @@
final MethodHandle methodHandle = hook.equals(__put__) ?
MH.asType(Lookup.EMPTY_SETTER, type) :
Lookup.emptyGetter(type.returnType());
- return new GuardedInvocation(methodHandle, testJSAdaptor(adaptee, null, null, null), adaptee.getProtoSwitchPoint(hook, null));
+ return new GuardedInvocation(methodHandle, testJSAdaptor(adaptee, null, null, null), adaptee.getProtoSwitchPoints(hook, null), null);
}
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AllocationStrategy.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AllocationStrategy.java Wed Sep 16 14:42:32 2015 +0200
@@ -29,6 +29,7 @@
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
+import java.lang.ref.WeakReference;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.CompilerConstants;
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
@@ -53,6 +54,9 @@
/** lazily generated allocator */
private transient MethodHandle allocator;
+ /** Last used allocator map */
+ private transient AllocatorMap lastMap;
+
/**
* Construct an allocation strategy with the given map and class name.
* @param fieldCount number of fields in the allocated object
@@ -71,11 +75,49 @@
return allocatorClassName;
}
- PropertyMap getAllocatorMap() {
- // Create a new map for each function instance
- return PropertyMap.newMap(null, getAllocatorClassName(), 0, fieldCount, 0);
+ /**
+ * Get the property map for the allocated object.
+ * @param prototype the prototype object
+ * @return the property map
+ */
+ synchronized PropertyMap getAllocatorMap(final ScriptObject prototype) {
+ assert prototype != null;
+ final PropertyMap protoMap = prototype.getMap();
+
+ if (lastMap != null) {
+ if (!lastMap.hasSharedProtoMap()) {
+ if (lastMap.hasSamePrototype(prototype)) {
+ return lastMap.allocatorMap;
+ }
+ if (lastMap.hasSameProtoMap(protoMap) && lastMap.hasUnchangedProtoMap()) {
+ // Convert to shared prototype map. Allocated objects will use the same property map
+ // that can be used as long as none of the prototypes modify the shared proto map.
+ final PropertyMap allocatorMap = PropertyMap.newMap(null, getAllocatorClassName(), 0, fieldCount, 0);
+ final SharedPropertyMap sharedProtoMap = new SharedPropertyMap(protoMap);
+ allocatorMap.setSharedProtoMap(sharedProtoMap);
+ prototype.setMap(sharedProtoMap);
+ lastMap = new AllocatorMap(prototype, protoMap, allocatorMap);
+ return allocatorMap;
+ }
+ }
+
+ if (lastMap.hasValidSharedProtoMap() && lastMap.hasSameProtoMap(protoMap)) {
+ prototype.setMap(lastMap.getSharedProtoMap());
+ return lastMap.allocatorMap;
+ }
+ }
+
+ final PropertyMap allocatorMap = PropertyMap.newMap(null, getAllocatorClassName(), 0, fieldCount, 0);
+ lastMap = new AllocatorMap(prototype, protoMap, allocatorMap);
+
+ return allocatorMap;
}
+ /**
+ * Allocate an object with the given property map
+ * @param map the property map
+ * @return the allocated object
+ */
ScriptObject allocate(final PropertyMap map) {
try {
if (allocator == null) {
@@ -94,4 +136,43 @@
public String toString() {
return "AllocationStrategy[fieldCount=" + fieldCount + "]";
}
+
+ static class AllocatorMap {
+ final private WeakReference<ScriptObject> prototype;
+ final private WeakReference<PropertyMap> prototypeMap;
+
+ private PropertyMap allocatorMap;
+
+ AllocatorMap(final ScriptObject prototype, final PropertyMap protoMap, final PropertyMap allocMap) {
+ this.prototype = new WeakReference<>(prototype);
+ this.prototypeMap = new WeakReference<>(protoMap);
+ this.allocatorMap = allocMap;
+ }
+
+ boolean hasSamePrototype(final ScriptObject proto) {
+ return prototype.get() == proto;
+ }
+
+ boolean hasSameProtoMap(final PropertyMap protoMap) {
+ return prototypeMap.get() == protoMap || allocatorMap.getSharedProtoMap() == protoMap;
+ }
+
+ boolean hasUnchangedProtoMap() {
+ final ScriptObject proto = prototype.get();
+ return proto != null && proto.getMap() == prototypeMap.get();
+ }
+
+ boolean hasSharedProtoMap() {
+ return getSharedProtoMap() != null;
+ }
+
+ boolean hasValidSharedProtoMap() {
+ return hasSharedProtoMap() && getSharedProtoMap().isValidSharedProtoMap();
+ }
+
+ PropertyMap getSharedProtoMap() {
+ return allocatorMap.getSharedProtoMap();
+ }
+
+ }
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Debug.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Debug.java Wed Sep 16 14:42:32 2015 +0200
@@ -26,6 +26,8 @@
package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.parser.TokenType.EOF;
+
+import jdk.nashorn.api.scripting.NashornException;
import jdk.nashorn.internal.parser.Lexer;
import jdk.nashorn.internal.parser.Token;
import jdk.nashorn.internal.parser.TokenStream;
@@ -63,6 +65,15 @@
}
/**
+ * Return a formatted script stack trace string with frames information separated by '\n'.
+ * This is a shortcut for {@code NashornException.getScriptStackString(new Throwable())}.
+ * @return formatted stack trace string
+ */
+ public static String scriptStack() {
+ return NashornException.getScriptStackString(new Throwable());
+ }
+
+ /**
* Return the system identity hashcode for an object as a human readable
* string
*
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyListeners.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyListeners.java Wed Sep 16 14:42:32 2015 +0200
@@ -75,16 +75,20 @@
}
/**
- * Return listeners added to this ScriptObject.
+ * Return number of listeners added to a ScriptObject.
* @param obj the object
* @return the listener count
*/
public static int getListenerCount(final ScriptObject obj) {
- final PropertyListeners propertyListeners = obj.getMap().getListeners();
- if (propertyListeners != null) {
- return propertyListeners.listeners == null ? 0 : propertyListeners.listeners.size();
- }
- return 0;
+ return obj.getMap().getListenerCount();
+ }
+
+ /**
+ * Return the number of listeners added to this PropertyListeners instance.
+ * @return the listener count;
+ */
+ public int getListenerCount() {
+ return listeners == null ? 0 : listeners.size();
}
// Property listener management methods
@@ -156,7 +160,7 @@
final WeakPropertyMapSet set = listeners.get(prop.getKey());
if (set != null) {
for (final PropertyMap propertyMap : set.elements()) {
- propertyMap.propertyAdded(prop);
+ propertyMap.propertyAdded(prop, false);
}
listeners.remove(prop.getKey());
if (Context.DEBUG) {
@@ -176,7 +180,7 @@
final WeakPropertyMapSet set = listeners.get(prop.getKey());
if (set != null) {
for (final PropertyMap propertyMap : set.elements()) {
- propertyMap.propertyDeleted(prop);
+ propertyMap.propertyDeleted(prop, false);
}
listeners.remove(prop.getKey());
if (Context.DEBUG) {
@@ -198,7 +202,7 @@
final WeakPropertyMapSet set = listeners.get(oldProp.getKey());
if (set != null) {
for (final PropertyMap propertyMap : set.elements()) {
- propertyMap.propertyModified(oldProp, newProp);
+ propertyMap.propertyModified(oldProp, newProp, false);
}
listeners.remove(oldProp.getKey());
if (Context.DEBUG) {
@@ -215,7 +219,7 @@
if (listeners != null) {
for (final WeakPropertyMapSet set : listeners.values()) {
for (final PropertyMap propertyMap : set.elements()) {
- propertyMap.protoChanged();
+ propertyMap.protoChanged(false);
}
}
listeners.clear();
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java Wed Sep 16 14:42:32 2015 +0200
@@ -54,32 +54,36 @@
* All property maps are immutable. If a property is added, modified or removed, the mutator
* will return a new map.
*/
-public final class PropertyMap implements Iterable<Object>, Serializable {
+public class PropertyMap implements Iterable<Object>, Serializable {
/** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
- public static final int NOT_EXTENSIBLE = 0b0000_0001;
+ private static final int NOT_EXTENSIBLE = 0b0000_0001;
/** Does this map contain valid array keys? */
- public static final int CONTAINS_ARRAY_KEYS = 0b0000_0010;
+ private static final int CONTAINS_ARRAY_KEYS = 0b0000_0010;
/** Map status flags. */
- private int flags;
+ private final int flags;
/** Map of properties. */
private transient PropertyHashMap properties;
/** Number of fields in use. */
- private int fieldCount;
+ private final int fieldCount;
/** Number of fields available. */
private final int fieldMaximum;
/** Length of spill in use. */
- private int spillLength;
+ private final int spillLength;
/** Structure class name */
- private String className;
+ private final String className;
+
+ /** A reference to the expected shared prototype property map. If this is set this
+ * property map should only be used if it the same as the actual prototype map. */
+ private transient SharedPropertyMap sharedProtoMap;
/** {@link SwitchPoint}s for gets on inherited properties. */
- private transient HashMap<String, SwitchPoint> protoGetSwitches;
+ private transient HashMap<String, SwitchPoint> protoSwitches;
/** History of maps, used to limit map duplication. */
private transient WeakHashMap<Property, SoftReference<PropertyMap>> history;
@@ -95,24 +99,21 @@
private static final long serialVersionUID = -7041836752008732533L;
/**
- * Constructor.
+ * Constructs a new property map.
*
* @param properties A {@link PropertyHashMap} with initial contents.
* @param fieldCount Number of fields in use.
* @param fieldMaximum Number of fields available.
* @param spillLength Number of spill slots used.
- * @param containsArrayKeys True if properties contain numeric keys
*/
- private PropertyMap(final PropertyHashMap properties, final String className, final int fieldCount,
- final int fieldMaximum, final int spillLength, final boolean containsArrayKeys) {
+ private PropertyMap(final PropertyHashMap properties, final int flags, final String className,
+ final int fieldCount, final int fieldMaximum, final int spillLength) {
this.properties = properties;
this.className = className;
this.fieldCount = fieldCount;
this.fieldMaximum = fieldMaximum;
this.spillLength = spillLength;
- if (containsArrayKeys) {
- setContainsArrayKeys();
- }
+ this.flags = flags;
if (Context.DEBUG) {
count.increment();
@@ -120,20 +121,22 @@
}
/**
- * Cloning constructor.
+ * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries.
*
* @param propertyMap Existing property map.
* @param properties A {@link PropertyHashMap} with a new set of properties.
*/
- private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties) {
+ private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength) {
this.properties = properties;
- this.flags = propertyMap.flags;
- this.spillLength = propertyMap.spillLength;
- this.fieldCount = propertyMap.fieldCount;
+ this.flags = flags;
+ this.spillLength = spillLength;
+ this.fieldCount = fieldCount;
this.fieldMaximum = propertyMap.fieldMaximum;
+ this.className = propertyMap.className;
// We inherit the parent property listeners instance. It will be cloned when a new listener is added.
this.listeners = propertyMap.listeners;
this.freeSlots = propertyMap.freeSlots;
+ this.sharedProtoMap = propertyMap.sharedProtoMap;
if (Context.DEBUG) {
count.increment();
@@ -142,12 +145,12 @@
}
/**
- * Cloning constructor.
+ * Constructs an exact clone of {@code propertyMap}.
*
* @param propertyMap Existing property map.
*/
- private PropertyMap(final PropertyMap propertyMap) {
- this(propertyMap, propertyMap.properties);
+ protected PropertyMap(final PropertyMap propertyMap) {
+ this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength);
}
private void writeObject(final ObjectOutputStream out) throws IOException {
@@ -183,7 +186,7 @@
*/
public static PropertyMap newMap(final Collection<Property> properties, final String className, final int fieldCount, final int fieldMaximum, final int spillLength) {
final PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties);
- return new PropertyMap(newProperties, className, fieldCount, fieldMaximum, spillLength, false);
+ return new PropertyMap(newProperties, 0, className, fieldCount, fieldMaximum, spillLength);
}
/**
@@ -205,7 +208,7 @@
* @return New empty {@link PropertyMap}.
*/
public static PropertyMap newMap(final Class<? extends ScriptObject> clazz) {
- return new PropertyMap(EMPTY_HASHMAP, clazz.getName(), 0, 0, 0, false);
+ return new PropertyMap(EMPTY_HASHMAP, 0, clazz.getName(), 0, 0, 0);
}
/**
@@ -227,12 +230,12 @@
}
/**
- * Get the listeners of this map, or null if none exists
+ * Get the number of listeners of this map
*
- * @return the listeners
+ * @return the number of listeners
*/
- public PropertyListeners getListeners() {
- return listeners;
+ public int getListenerCount() {
+ return listeners == null ? 0 : listeners.getListenerCount();
}
/**
@@ -253,9 +256,12 @@
* A new property is being added.
*
* @param property The new Property added.
+ * @param isSelf was the property added to this map?
*/
- public void propertyAdded(final Property property) {
- invalidateProtoGetSwitchPoint(property);
+ public void propertyAdded(final Property property, final boolean isSelf) {
+ if (!isSelf) {
+ invalidateProtoSwitchPoint(property.getKey());
+ }
if (listeners != null) {
listeners.propertyAdded(property);
}
@@ -265,9 +271,12 @@
* An existing property is being deleted.
*
* @param property The property being deleted.
+ * @param isSelf was the property deleted from this map?
*/
- public void propertyDeleted(final Property property) {
- invalidateProtoGetSwitchPoint(property);
+ public void propertyDeleted(final Property property, final boolean isSelf) {
+ if (!isSelf) {
+ invalidateProtoSwitchPoint(property.getKey());
+ }
if (listeners != null) {
listeners.propertyDeleted(property);
}
@@ -278,9 +287,12 @@
*
* @param oldProperty The old property
* @param newProperty The new property
+ * @param isSelf was the property modified on this map?
*/
- public void propertyModified(final Property oldProperty, final Property newProperty) {
- invalidateProtoGetSwitchPoint(oldProperty);
+ public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) {
+ if (!isSelf) {
+ invalidateProtoSwitchPoint(oldProperty.getKey());
+ }
if (listeners != null) {
listeners.propertyModified(oldProperty, newProperty);
}
@@ -288,9 +300,15 @@
/**
* The prototype of an object associated with this {@link PropertyMap} is changed.
+ *
+ * @param isSelf was the prototype changed on the object using this map?
*/
- public void protoChanged() {
- invalidateAllProtoGetSwitchPoints();
+ public void protoChanged(final boolean isSelf) {
+ if (!isSelf) {
+ invalidateAllProtoSwitchPoints();
+ } else if (sharedProtoMap != null) {
+ sharedProtoMap.invalidateSwitchPoint();
+ }
if (listeners != null) {
listeners.protoChanged();
}
@@ -303,14 +321,14 @@
* @return A shared {@link SwitchPoint} for the property.
*/
public synchronized SwitchPoint getSwitchPoint(final String key) {
- if (protoGetSwitches == null) {
- protoGetSwitches = new HashMap<>();
+ if (protoSwitches == null) {
+ protoSwitches = new HashMap<>();
}
- SwitchPoint switchPoint = protoGetSwitches.get(key);
+ SwitchPoint switchPoint = protoSwitches.get(key);
if (switchPoint == null) {
switchPoint = new SwitchPoint();
- protoGetSwitches.put(key, switchPoint);
+ protoSwitches.put(key, switchPoint);
}
return switchPoint;
@@ -319,19 +337,17 @@
/**
* Indicate that a prototype property has changed.
*
- * @param property {@link Property} to invalidate.
+ * @param key {@link Property} key to invalidate.
*/
- synchronized void invalidateProtoGetSwitchPoint(final Property property) {
- if (protoGetSwitches != null) {
-
- final String key = property.getKey();
- final SwitchPoint sp = protoGetSwitches.get(key);
+ synchronized void invalidateProtoSwitchPoint(final String key) {
+ if (protoSwitches != null) {
+ final SwitchPoint sp = protoSwitches.get(key);
if (sp != null) {
- protoGetSwitches.remove(key);
+ protoSwitches.remove(key);
if (Context.DEBUG) {
protoInvalidations.increment();
}
- SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
+ SwitchPoint.invalidateAll(new SwitchPoint[]{sp});
}
}
}
@@ -339,15 +355,15 @@
/**
* Indicate that proto itself has changed in hierarchy somewhere.
*/
- synchronized void invalidateAllProtoGetSwitchPoints() {
- if (protoGetSwitches != null) {
- final int size = protoGetSwitches.size();
+ synchronized void invalidateAllProtoSwitchPoints() {
+ if (protoSwitches != null) {
+ final int size = protoSwitches.size();
if (size > 0) {
if (Context.DEBUG) {
protoInvalidations.add(size);
}
- SwitchPoint.invalidateAll(protoGetSwitches.values().toArray(new SwitchPoint[size]));
- protoGetSwitches.clear();
+ SwitchPoint.invalidateAll(protoSwitches.values().toArray(new SwitchPoint[size]));
+ protoSwitches.clear();
}
}
}
@@ -363,7 +379,7 @@
* @return New {@link PropertyMap} with {@link Property} added.
*/
PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) {
- // No need to store bound property in the history as bound properties can't be reused.
+ // We must not store bound property in the history as bound properties can't be reused.
return addPropertyNoHistory(new AccessorProperty(property, bindTo));
}
@@ -376,16 +392,16 @@
return property.isSpill() ? slot + fieldMaximum : slot;
}
- // Update boundaries and flags after a property has been added
- private void updateFlagsAndBoundaries(final Property newProperty) {
- if(newProperty.isSpill()) {
- spillLength = Math.max(spillLength, newProperty.getSlot() + 1);
- } else {
- fieldCount = Math.max(fieldCount, newProperty.getSlot() + 1);
- }
- if (isValidArrayIndex(getArrayIndex(newProperty.getKey()))) {
- setContainsArrayKeys();
- }
+ private int newSpillLength(final Property newProperty) {
+ return newProperty.isSpill() ? Math.max(spillLength, newProperty.getSlot() + 1) : spillLength;
+ }
+
+ private int newFieldCount(final Property newProperty) {
+ return !newProperty.isSpill() ? Math.max(fieldCount, newProperty.getSlot() + 1) : fieldCount;
+ }
+
+ private int newFlags(final Property newProperty) {
+ return isValidArrayIndex(getArrayIndex(newProperty.getKey())) ? flags | CONTAINS_ARRAY_KEYS : flags;
}
// Update the free slots bitmap for a property that has been deleted and/or added. This method is not synchronized
@@ -420,13 +436,10 @@
* @param property {@link Property} being added.
* @return New {@link PropertyMap} with {@link Property} added.
*/
- public PropertyMap addPropertyNoHistory(final Property property) {
- if (listeners != null) {
- listeners.propertyAdded(property);
- }
+ public final PropertyMap addPropertyNoHistory(final Property property) {
+ propertyAdded(property, true);
final PropertyHashMap newProperties = properties.immutableAdd(property);
- final PropertyMap newMap = new PropertyMap(this, newProperties);
- newMap.updateFlagsAndBoundaries(property);
+ final PropertyMap newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
newMap.updateFreeSlots(null, property);
return newMap;
@@ -439,16 +452,13 @@
*
* @return New {@link PropertyMap} with {@link Property} added.
*/
- public synchronized PropertyMap addProperty(final Property property) {
- if (listeners != null) {
- listeners.propertyAdded(property);
- }
+ public final synchronized PropertyMap addProperty(final Property property) {
+ propertyAdded(property, true);
PropertyMap newMap = checkHistory(property);
if (newMap == null) {
final PropertyHashMap newProperties = properties.immutableAdd(property);
- newMap = new PropertyMap(this, newProperties);
- newMap.updateFlagsAndBoundaries(property);
+ newMap = new PropertyMap(this, newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
newMap.updateFreeSlots(null, property);
addToHistory(property, newMap);
}
@@ -463,10 +473,8 @@
*
* @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found.
*/
- public synchronized PropertyMap deleteProperty(final Property property) {
- if (listeners != null) {
- listeners.propertyDeleted(property);
- }
+ public final synchronized PropertyMap deleteProperty(final Property property) {
+ propertyDeleted(property, true);
PropertyMap newMap = checkHistory(property);
final String key = property.getKey();
@@ -477,13 +485,13 @@
// If deleted property was last field or spill slot we can make it reusable by reducing field/slot count.
// Otherwise mark it as free in free slots bitset.
if (isSpill && slot >= 0 && slot == spillLength - 1) {
- newMap = new PropertyMap(newProperties, className, fieldCount, fieldMaximum, spillLength - 1, containsArrayKeys());
+ newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength - 1);
newMap.freeSlots = freeSlots;
} else if (!isSpill && slot >= 0 && slot == fieldCount - 1) {
- newMap = new PropertyMap(newProperties, className, fieldCount - 1, fieldMaximum, spillLength, containsArrayKeys());
+ newMap = new PropertyMap(this, newProperties, flags, fieldCount - 1, spillLength);
newMap.freeSlots = freeSlots;
} else {
- newMap = new PropertyMap(this, newProperties);
+ newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength);
newMap.updateFreeSlots(property, null);
}
addToHistory(property, newMap);
@@ -500,13 +508,8 @@
*
* @return New {@link PropertyMap} with {@link Property} replaced.
*/
- public PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
- if (listeners != null) {
- listeners.propertyModified(oldProperty, newProperty);
- }
- // Add replaces existing property.
- final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty);
- final PropertyMap newMap = new PropertyMap(this, newProperties);
+ public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
+ propertyModified(oldProperty, newProperty, true);
/*
* See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods.
*
@@ -528,14 +531,17 @@
newProperty instanceof UserAccessorProperty :
"arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]";
- newMap.flags = flags;
-
/*
* spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need
* to add spill count of the newly added UserAccessorProperty property.
*/
+ final int newSpillLength = sameType ? spillLength : Math.max(spillLength, newProperty.getSlot() + 1);
+
+ // Add replaces existing property.
+ final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty);
+ final PropertyMap newMap = new PropertyMap(this, newProperties, flags, fieldCount, newSpillLength);
+
if (!sameType) {
- newMap.spillLength = Math.max(spillLength, newProperty.getSlot() + 1);
newMap.updateFreeSlots(oldProperty, newProperty);
}
return newMap;
@@ -551,7 +557,7 @@
* @param propertyFlags attribute flags of the property
* @return the newly created UserAccessorProperty
*/
- public UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) {
+ public final UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) {
return new UserAccessorProperty(key, propertyFlags, getFreeSpillSlot());
}
@@ -562,7 +568,7 @@
*
* @return {@link Property} matching key.
*/
- public Property findProperty(final String key) {
+ public final Property findProperty(final String key) {
return properties.find(key);
}
@@ -573,12 +579,12 @@
*
* @return New {@link PropertyMap} with added properties.
*/
- public PropertyMap addAll(final PropertyMap other) {
+ public final PropertyMap addAll(final PropertyMap other) {
assert this != other : "adding property map to itself";
final Property[] otherProperties = other.properties.getProperties();
final PropertyHashMap newProperties = properties.immutableAdd(otherProperties);
- final PropertyMap newMap = new PropertyMap(this, newProperties);
+ final PropertyMap newMap = new PropertyMap(this, newProperties, flags, fieldCount, spillLength);
for (final Property property : otherProperties) {
// This method is only safe to use with non-slotted, native getter/setter properties
assert property.getSlot() == -1;
@@ -593,7 +599,7 @@
*
* @return Properties as an array.
*/
- public Property[] getProperties() {
+ public final Property[] getProperties() {
return properties.getProperties();
}
@@ -602,7 +608,7 @@
*
* @return class name of owner objects.
*/
- public String getClassName() {
+ public final String getClassName() {
return className;
}
@@ -612,9 +618,7 @@
* @return New map with {@link #NOT_EXTENSIBLE} flag set.
*/
PropertyMap preventExtensions() {
- final PropertyMap newMap = new PropertyMap(this);
- newMap.flags |= NOT_EXTENSIBLE;
- return newMap;
+ return new PropertyMap(this, properties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
}
/**
@@ -630,10 +634,7 @@
newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE));
}
- final PropertyMap newMap = new PropertyMap(this, newProperties);
- newMap.flags |= NOT_EXTENSIBLE;
-
- return newMap;
+ return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
}
/**
@@ -655,10 +656,7 @@
newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags));
}
- final PropertyMap newMap = new PropertyMap(this, newProperties);
- newMap.flags |= NOT_EXTENSIBLE;
-
- return newMap;
+ return new PropertyMap(this, newProperties, flags | NOT_EXTENSIBLE, fieldCount, spillLength);
}
/**
@@ -830,13 +828,6 @@
}
/**
- * Flag this object as having array keys in defined properties
- */
- private void setContainsArrayKeys() {
- flags |= CONTAINS_ARRAY_KEYS;
- }
-
- /**
* Test to see if {@link PropertyMap} is extensible.
*
* @return {@code true} if {@link PropertyMap} can be added to.
@@ -914,12 +905,72 @@
setProtoNewMapCount.increment();
}
- final PropertyMap newMap = new PropertyMap(this);
+ final PropertyMap newMap = makeUnsharedCopy();
addToProtoHistory(newProto, newMap);
return newMap;
}
+ /**
+ * Make a copy of this property map with the shared prototype field set to null. Note that this is
+ * only necessary for shared maps of top-level objects. Shared prototype maps represented by
+ * {@link SharedPropertyMap} are automatically converted to plain property maps when they evolve.
+ *
+ * @return a copy with the shared proto map unset
+ */
+ PropertyMap makeUnsharedCopy() {
+ final PropertyMap newMap = new PropertyMap(this);
+ newMap.sharedProtoMap = null;
+ return newMap;
+ }
+
+ /**
+ * Set a reference to the expected parent prototype map. This is used for class-like
+ * structures where we only want to use a top-level property map if all of the
+ * prototype property maps have not been modified.
+ *
+ * @param protoMap weak reference to the prototype property map
+ */
+ void setSharedProtoMap(final SharedPropertyMap protoMap) {
+ sharedProtoMap = protoMap;
+ }
+
+ /**
+ * Get the expected prototype property map if it is known, or null.
+ *
+ * @return parent map or null
+ */
+ public PropertyMap getSharedProtoMap() {
+ return sharedProtoMap;
+ }
+
+ /**
+ * Returns {@code true} if this map has been used as a shared prototype map (i.e. as a prototype
+ * for a JavaScript constructor function) and has not had properties added, deleted or replaced since then.
+ * @return true if this is a valid shared prototype map
+ */
+ boolean isValidSharedProtoMap() {
+ return false;
+ }
+
+ /**
+ * Returns the shared prototype switch point, or null if this is not a shared prototype map.
+ * @return the shared prototype switch point, or null
+ */
+ SwitchPoint getSharedProtoSwitchPoint() {
+ return null;
+ }
+
+ /**
+ * Return true if this map has a shared prototype map which has either been invalidated or does
+ * not match the map of {@code proto}.
+ * @param prototype the prototype object
+ * @return true if this is an invalid shared map for {@code prototype}
+ */
+ boolean isInvalidSharedMapFor(final ScriptObject prototype) {
+ return sharedProtoMap != null
+ && (!sharedProtoMap.isValidSharedProtoMap() || prototype == null || sharedProtoMap != prototype.getMap());
+ }
/**
* {@link PropertyMap} iterator.
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 16 14:42:32 2015 +0200
@@ -368,8 +368,8 @@
}
@Override
- PropertyMap getAllocatorMap() {
- return allocationStrategy.getAllocatorMap();
+ PropertyMap getAllocatorMap(final ScriptObject prototype) {
+ return allocationStrategy.getAllocatorMap(prototype);
}
@Override
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunction.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunction.java Wed Sep 16 14:42:32 2015 +0200
@@ -521,35 +521,39 @@
assert !isBoundFunction(); // allocate never invoked on bound functions
- final ScriptObject object = data.allocate(getAllocatorMap());
+ final ScriptObject prototype = getAllocatorPrototype();
+ final ScriptObject object = data.allocate(getAllocatorMap(prototype));
if (object != null) {
- final Object prototype = getPrototype();
- if (prototype instanceof ScriptObject) {
- object.setInitialProto((ScriptObject) prototype);
- }
-
- if (object.getProto() == null) {
- object.setInitialProto(getObjectPrototype());
- }
+ object.setInitialProto(prototype);
}
return object;
}
- private PropertyMap getAllocatorMap() {
- if (allocatorMap == null) {
- allocatorMap = data.getAllocatorMap();
+ /**
+ * Get the property map used by "allocate"
+ * @param prototype actual prototype object
+ * @return property map
+ */
+ private synchronized PropertyMap getAllocatorMap(final ScriptObject prototype) {
+ if (allocatorMap == null || allocatorMap.isInvalidSharedMapFor(prototype)) {
+ // The prototype map has changed since this function was last used as constructor.
+ // Get a new allocator map.
+ allocatorMap = data.getAllocatorMap(prototype);
}
return allocatorMap;
}
/**
- * Return Object.prototype - used by "allocate"
- *
- * @return Object.prototype
+ * Return the actual prototype used by "allocate"
+ * @return allocator prototype
*/
- protected final ScriptObject getObjectPrototype() {
+ private ScriptObject getAllocatorPrototype() {
+ final Object prototype = getPrototype();
+ if (prototype instanceof ScriptObject) {
+ return (ScriptObject) prototype;
+ }
return Global.objectPrototype();
}
@@ -591,10 +595,10 @@
*
* @param newPrototype new prototype object
*/
- public final void setPrototype(final Object newPrototype) {
+ public synchronized final void setPrototype(final Object newPrototype) {
if (newPrototype instanceof ScriptObject && newPrototype != this.prototype && allocatorMap != null) {
- // Replace our current allocator map with one that is associated with the new prototype.
- allocatorMap = allocatorMap.changeProto((ScriptObject) newPrototype);
+ // Unset allocator map to be replaced with one matching the new prototype.
+ allocatorMap = null;
}
this.prototype = newPrototype;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunctionData.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptFunctionData.java Wed Sep 16 14:42:32 2015 +0200
@@ -389,9 +389,10 @@
/**
* Get the property map to use for objects allocated by this function.
*
+ * @param prototype the prototype of the allocated object
* @return the property map for allocated objects.
*/
- PropertyMap getAllocatorMap() {
+ PropertyMap getAllocatorMap(final ScriptObject prototype) {
return null;
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java Wed Sep 16 14:42:32 2015 +0200
@@ -809,9 +809,11 @@
if (deep) {
final ScriptObject myProto = getProto();
- if (myProto != null) {
- return myProto.findProperty(key, deep, start);
- }
+ final FindProperty find = myProto == null ? null : myProto.findProperty(key, true, start);
+ // checkSharedProtoMap must be invoked after myProto.checkSharedProtoMap to propagate
+ // shared proto invalidation up the prototype chain. It also must be invoked when prototype is null.
+ checkSharedProtoMap();
+ return find;
}
return null;
@@ -832,7 +834,7 @@
if (deep) {
final ScriptObject myProto = getProto();
if (myProto != null) {
- return myProto.hasProperty(key, deep);
+ return myProto.hasProperty(key, true);
}
}
@@ -1258,11 +1260,8 @@
if (oldProto != newProto) {
proto = newProto;
- // Let current listeners know that the prototype has changed and set our map
- final PropertyListeners listeners = getMap().getListeners();
- if (listeners != null) {
- listeners.protoChanged();
- }
+ // Let current listeners know that the prototype has changed
+ getMap().protoChanged(true);
// Replace our current allocator map with one that is associated with the new prototype.
setMap(getMap().changeProto(newProto));
}
@@ -1314,7 +1313,7 @@
}
p = p.getProto();
}
- setProto((ScriptObject)newProto);
+ setProto((ScriptObject) newProto);
} else {
throw typeError("cant.set.proto.to.non.object", ScriptRuntime.safeToString(this), ScriptRuntime.safeToString(newProto));
}
@@ -2012,11 +2011,11 @@
final ScriptObject owner = find.getOwner();
final Class<ClassCastException> exception = explicitInstanceOfCheck ? null : ClassCastException.class;
- final SwitchPoint protoSwitchPoint;
+ final SwitchPoint[] protoSwitchPoints;
if (mh == null) {
mh = Lookup.emptyGetter(returnType);
- protoSwitchPoint = getProtoSwitchPoint(name, owner);
+ protoSwitchPoints = getProtoSwitchPoints(name, owner);
} else if (!find.isSelf()) {
assert mh.type().returnType().equals(returnType) :
"return type mismatch for getter " + mh.type().returnType() + " != " + returnType;
@@ -2024,12 +2023,12 @@
// Add a filter that replaces the self object with the prototype owning the property.
mh = addProtoFilter(mh, find.getProtoChainLength());
}
- protoSwitchPoint = getProtoSwitchPoint(name, owner);
+ protoSwitchPoints = getProtoSwitchPoints(name, owner);
} else {
- protoSwitchPoint = null;
+ protoSwitchPoints = null;
}
- final GuardedInvocation inv = new GuardedInvocation(mh, guard, protoSwitchPoint, exception);
+ final GuardedInvocation inv = new GuardedInvocation(mh, guard, protoSwitchPoints, exception);
return inv.addSwitchPoint(findBuiltinSwitchPoint(name));
}
@@ -2128,17 +2127,32 @@
* @param owner the property owner, null if property is not defined
* @return a SwitchPoint or null
*/
- public final SwitchPoint getProtoSwitchPoint(final String name, final ScriptObject owner) {
+ public final SwitchPoint[] getProtoSwitchPoints(final String name, final ScriptObject owner) {
if (owner == this || getProto() == null) {
return null;
}
+ final List<SwitchPoint> switchPoints = new ArrayList<>();
for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) {
final ScriptObject parent = obj.getProto();
parent.getMap().addListener(name, obj.getMap());
+ final SwitchPoint sp = parent.getMap().getSharedProtoSwitchPoint();
+ if (sp != null && !sp.hasBeenInvalidated()) {
+ switchPoints.add(sp);
+ }
}
- return getMap().getSwitchPoint(name);
+ switchPoints.add(getMap().getSwitchPoint(name));
+ return switchPoints.toArray(new SwitchPoint[switchPoints.size()]);
+ }
+
+ private void checkSharedProtoMap() {
+ // Check if our map has an expected shared prototype property map. If it has, make sure that
+ // the prototype map has not been invalidated, and that it does match the actual map of the prototype.
+ if (getMap().isInvalidSharedMapFor(getProto())) {
+ // Change our own map to one that does not assume a shared prototype map.
+ setMap(getMap().makeUnsharedCopy());
+ }
}
/**
@@ -2220,7 +2234,7 @@
return new GuardedInvocation(
Lookup.EMPTY_SETTER,
NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck),
- getProtoSwitchPoint(name, null),
+ getProtoSwitchPoints(name, null),
explicitInstanceOfCheck ? null : ClassCastException.class);
}
@@ -2366,7 +2380,7 @@
find.getGetter(Object.class, INVALID_PROGRAM_POINT, request),
find.getProtoChainLength(),
func),
- getProtoSwitchPoint(NO_SUCH_PROPERTY_NAME, find.getOwner()),
+ getProtoSwitchPoints(NO_SUCH_PROPERTY_NAME, find.getOwner()),
//TODO this doesn't need a ClassCastException as guard always checks script object
null);
}
@@ -2438,7 +2452,7 @@
}
return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()),
- NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), getProtoSwitchPoint(name, null),
+ NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck), getProtoSwitchPoints(name, null),
explicitInstanceOfCheck ? null : ClassCastException.class);
}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SetMethodCreator.java Wed Sep 16 14:42:32 2015 +0200
@@ -186,10 +186,7 @@
private SetMethod createNewPropertySetter(final SwitchPoint builtinSwitchPoint) {
final SetMethod sm = map.getFreeFieldSlot() > -1 ? createNewFieldSetter(builtinSwitchPoint) : createNewSpillPropertySetter(builtinSwitchPoint);
- final PropertyListeners listeners = map.getListeners();
- if (listeners != null) {
- listeners.propertyAdded(sm.property);
- }
+ map.propertyAdded(sm.property, true);
return sm;
}
@@ -204,7 +201,7 @@
//fast type specific setter
final MethodHandle fastSetter = property.getSetter(type, newMap); //0 sobj, 1 value, slot folded for spill property already
- //slow setter, that calls ScriptObject.set with appropraite type and key name
+ //slow setter, that calls ScriptObject.set with appropriate type and key name
MethodHandle slowSetter = ScriptObject.SET_SLOW[getAccessorTypeIndex(type)];
slowSetter = MH.insertArguments(slowSetter, 3, NashornCallSiteDescriptor.getFlags(desc));
slowSetter = MH.insertArguments(slowSetter, 1, name);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/SharedPropertyMap.java Wed Sep 16 14:42:32 2015 +0200
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2015, 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 java.lang.invoke.SwitchPoint;
+
+/**
+ * This class represents a property map that can be shared among multiple prototype objects, allowing all inheriting
+ * top-level objects to also share one property map. This is class is only used for prototype objects, the
+ * top-level objects use ordinary {@link PropertyMap}s with the {@link PropertyMap#sharedProtoMap} field
+ * set to the expected shared prototype map.
+ *
+ * <p>When an instance of this class is evolved because a property is added, removed, or modified in an object
+ * using it, the {@link #invalidateSwitchPoint()} method is invoked to signal to all callsites and inheriting
+ * objects that the assumption of a single shared prototype map is no longer valid. The property map resulting
+ * from the modification will no longer be an instance of this class.</p>
+ */
+public final class SharedPropertyMap extends PropertyMap {
+
+ private SwitchPoint switchPoint;
+
+ private static final long serialVersionUID = 2166297719721778876L;
+
+ /**
+ * Create a new shared property map from the given {@code map}.
+ * @param map property map to copy
+ */
+ public SharedPropertyMap(final PropertyMap map) {
+ super(map);
+ this.switchPoint = new SwitchPoint();
+ }
+
+ @Override
+ public void propertyAdded(final Property property, final boolean isSelf) {
+ if (isSelf) {
+ invalidateSwitchPoint();
+ }
+ super.propertyAdded(property, isSelf);
+ }
+
+ @Override
+ public void propertyDeleted(final Property property, final boolean isSelf) {
+ if (isSelf) {
+ invalidateSwitchPoint();
+ }
+ super.propertyDeleted(property, isSelf);
+ }
+
+ @Override
+ public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) {
+ if (isSelf) {
+ invalidateSwitchPoint();
+ }
+ super.propertyModified(oldProperty, newProperty, isSelf);
+ }
+
+ @Override
+ synchronized boolean isValidSharedProtoMap() {
+ return switchPoint != null;
+ }
+
+ @Override
+ synchronized SwitchPoint getSharedProtoSwitchPoint() {
+ return switchPoint;
+ }
+
+ /**
+ * Invalidate the shared prototype switch point if this is a shared prototype map.
+ */
+ synchronized void invalidateSwitchPoint() {
+ if (switchPoint != null) {
+ assert !switchPoint.hasBeenInvalidated();
+ SwitchPoint.invalidateAll(new SwitchPoint[]{ switchPoint });
+ switchPoint = null;
+ }
+ }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/WithObject.java Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/WithObject.java Wed Sep 16 14:42:32 2015 +0200
@@ -46,7 +46,7 @@
*
*/
public final class WithObject extends Scope {
- private static final MethodHandle WITHEXPRESSIONGUARD = findOwnMH("withExpressionGuard", boolean.class, Object.class, PropertyMap.class, SwitchPoint.class);
+ private static final MethodHandle WITHEXPRESSIONGUARD = findOwnMH("withExpressionGuard", boolean.class, Object.class, PropertyMap.class, SwitchPoint[].class);
private static final MethodHandle WITHEXPRESSIONFILTER = findOwnMH("withFilterExpression", Object.class, Object.class);
private static final MethodHandle WITHSCOPEFILTER = findOwnMH("withFilterScope", Object.class, Object.class);
private static final MethodHandle BIND_TO_EXPRESSION_OBJ = findOwnMH("bindToExpression", Object.class, Object.class, Object.class);
@@ -360,13 +360,24 @@
private MethodHandle expressionGuard(final String name, final ScriptObject owner) {
final PropertyMap map = expression.getMap();
- final SwitchPoint sp = expression.getProtoSwitchPoint(name, owner);
+ final SwitchPoint[] sp = expression.getProtoSwitchPoints(name, owner);
return MH.insertArguments(WITHEXPRESSIONGUARD, 1, map, sp);
}
@SuppressWarnings("unused")
- private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint sp) {
- return ((WithObject)receiver).expression.getMap() == map && (sp == null || !sp.hasBeenInvalidated());
+ private static boolean withExpressionGuard(final Object receiver, final PropertyMap map, final SwitchPoint[] sp) {
+ return ((WithObject)receiver).expression.getMap() == map && !hasBeenInvalidated(sp);
+ }
+
+ private static boolean hasBeenInvalidated(final SwitchPoint[] switchPoints) {
+ if (switchPoints != null) {
+ for (final SwitchPoint switchPoint : switchPoints) {
+ if (switchPoint.hasBeenInvalidated()) {
+ return true;
+ }
+ }
+ }
+ return false;
}
/**
--- a/nashorn/test/script/basic/JDK-8134569.js Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/test/script/basic/JDK-8134569.js Wed Sep 16 14:42:32 2015 +0200
@@ -62,67 +62,146 @@
return new C();
}
+function createDeeper() {
+ function C() {
+ this.i1 = 1;
+ this.i2 = 2;
+ this.i3 = 3;
+ return this;
+ }
+ function D() {
+ this.p1 = 1;
+ this.p2 = 2;
+ this.p3 = 3;
+ return this;
+ }
+ function E() {
+ this.e1 = 1;
+ this.e2 = 2;
+ this.e3 = 3;
+ return this;
+ }
+ D.prototype = new E();
+ C.prototype = new D();
+ return new C();
+}
+
function createEval() {
return eval("Object.create({})");
}
function p(o) { print(o.x) }
-var a, b;
+function e(o) { print(o.e1) }
+
+var a, b, c;
create();
a = create();
b = create();
+c = create();
a.__proto__.x = 123;
p(a);
p(b);
+p(c);
a = create();
b = create();
+c = create();
b.__proto__.x = 123;
p(a);
p(b);
+p(c);
a = createEmpty();
b = createEmpty();
+c = createEmpty();
a.__proto__.x = 123;
p(a);
p(b);
+p(c);
a = createEmpty();
b = createEmpty();
+c = createEmpty();
b.__proto__.x = 123;
p(a);
p(b);
+p(c);
a = createDeep();
b = createDeep();
+c = createDeep();
a.__proto__.__proto__.x = 123;
p(a);
p(b);
+p(c);
a = createDeep();
b = createDeep();
+c = createDeep();
b.__proto__.__proto__.x = 123;
p(a);
p(b);
+p(c);
+
+a = createDeeper();
+b = createDeeper();
+c = createDeeper();
+a.__proto__.__proto__.__proto__.x = 123;
+
+p(a);
+p(b);
+p(c);
+
+a = createDeeper();
+b = createDeeper();
+c = createDeeper();
+b.__proto__.__proto__.__proto__.x = 123;
+
+p(a);
+p(b);
+p(c);
+
+a = createDeeper();
+b = createDeeper();
+c = createDeeper();
+a.__proto__.__proto__ = null;
+
+e(a);
+e(b);
+e(c);
+
+a = createDeeper();
+b = createDeeper();
+c = createDeeper();
+b.__proto__.__proto__ = null;
+
+e(a);
+e(b);
+e(c);
+
a = createEval();
b = createEval();
+c = createEval();
a.__proto__.x = 123;
p(a);
p(b);
+p(c);
a = createEval();
b = createEval();
+c = createEval();
b.__proto__.x = 123;
p(a);
p(b);
+p(c);
--- a/nashorn/test/script/basic/JDK-8134569.js.EXPECTED Wed Sep 16 16:26:30 2015 +0530
+++ b/nashorn/test/script/basic/JDK-8134569.js.EXPECTED Wed Sep 16 14:42:32 2015 +0200
@@ -1,16 +1,36 @@
123
undefined
undefined
+undefined
123
+undefined
123
undefined
undefined
+undefined
+123
+undefined
+123
+undefined
+undefined
+undefined
123
+undefined
123
undefined
undefined
+undefined
123
+undefined
+undefined
+1
+1
+1
+undefined
+1
123
undefined
undefined
+undefined
123
+undefined
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8134609.js Wed Sep 16 14:42:32 2015 +0200
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+/**
+ * JDK-8134609: Allow constructors with same prototoype map to share the allocator map
+ *
+ * @test
+ * @run
+ * @fork
+ * @option -Dnashorn.debug
+ */
+
+function createProto(members) {
+ function P() {
+ for (var id in members) {
+ if (members.hasOwnProperty(id)) {
+ this[id] = members[id];
+ }
+ }
+ return this;
+ }
+ return new P();
+}
+
+function createSubclass(prototype, members) {
+ function C() {
+ for (var id in members) {
+ if (members.hasOwnProperty(id)) {
+ this[id] = members[id];
+ }
+ }
+ return this;
+ }
+
+ C.prototype = prototype;
+
+ return new C();
+}
+
+function assertP1(object, value) {
+ Assert.assertTrue(object.p1 === value);
+}
+
+// First prototype will have non-shared proto-map. Second and third will be shared.
+var proto0 = createProto({p1: 0, p2: 1});
+var proto1 = createProto({p1: 1, p2: 2});
+var proto2 = createProto({p1: 2, p2: 3});
+
+Assert.assertTrue(Debug.map(proto1) === Debug.map(proto2));
+
+assertP1(proto1, 1);
+assertP1(proto2, 2);
+
+// First instantiation will have a non-shared prototype map, from the second one
+// maps will be shared until a different proto map comes along.
+var child0 = createSubclass(proto1, {c1: 1, c2: 2});
+var child1 = createSubclass(proto2, {c1: 2, c2: 3});
+var child2 = createSubclass(proto1, {c1: 3, c2: 4});
+var child3 = createSubclass(proto2, {c1: 1, c2: 2});
+var child4 = createSubclass(proto0, {c1: 3, c2: 2});
+
+Assert.assertTrue(Debug.map(child1) === Debug.map(child2));
+Assert.assertTrue(Debug.map(child1) === Debug.map(child3));
+Assert.assertTrue(Debug.map(child3) !== Debug.map(child4));
+
+assertP1(child1, 2);
+assertP1(child2, 1);
+assertP1(child3, 2);
+assertP1(child4, 0);
+
+Assert.assertTrue(delete proto2.p1);
+
+assertP1(child3, undefined);
+assertP1(child2, 1);
+Assert.assertTrue(Debug.map(child1) !== Debug.map(child3));