提交 b642e31e 编写于 作者: H hannesw

8134609: Allow constructors with same prototoype map to share the allocator map

Reviewed-by: attila, sundar
上级 92853b17
......@@ -627,7 +627,7 @@ public final class NativeJSAdapter extends ScriptObject {
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 @@ public final class NativeJSAdapter extends ScriptObject {
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 @@ public final class NativeJSAdapter extends ScriptObject {
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);
}
}
......
......@@ -29,6 +29,7 @@ import static jdk.nashorn.internal.lookup.Lookup.MH;
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 @@ final public class AllocationStrategy implements Serializable {
/** 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 @@ final public class AllocationStrategy implements Serializable {
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 @@ final public class AllocationStrategy implements Serializable {
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();
}
}
}
......@@ -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;
......@@ -62,6 +64,15 @@ public final class Debug {
return firstJSFrame(new Throwable());
}
/**
* 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
......
......@@ -75,16 +75,20 @@ public class PropertyListeners {
}
/**
* 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 @@ public class PropertyListeners {
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 @@ public class PropertyListeners {
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 @@ public class PropertyListeners {
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 @@ public class PropertyListeners {
if (listeners != null) {
for (final WeakPropertyMapSet set : listeners.values()) {
for (final PropertyMap propertyMap : set.elements()) {
propertyMap.protoChanged();
propertyMap.protoChanged(false);
}
}
listeners.clear();
......
......@@ -54,32 +54,36 @@ import jdk.nashorn.internal.scripts.JO;
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
}
/**
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
}
/**
* 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 final class PropertyMap implements Iterable<Object>, Serializable {
*/
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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
}
/**
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
/**
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
/**
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
/**
* 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
// 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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @return Properties as an array.
*/
public Property[] getProperties() {
public final Property[] getProperties() {
return properties.getProperties();
}
......@@ -602,7 +608,7 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
*
* @return class name of owner objects.
*/
public String getClassName() {
public final String getClassName() {
return className;
}
......@@ -612,9 +618,7 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
* @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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
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 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
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);
}
/**
......@@ -829,13 +827,6 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
return (flags & CONTAINS_ARRAY_KEYS) != 0;
}
/**
* 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.
*
......@@ -914,12 +905,72 @@ public final class PropertyMap implements Iterable<Object>, Serializable {
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.
......
......@@ -368,8 +368,8 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
}
@Override
PropertyMap getAllocatorMap() {
return allocationStrategy.getAllocatorMap();
PropertyMap getAllocatorMap(final ScriptObject prototype) {
return allocationStrategy.getAllocatorMap(prototype);
}
@Override
......
......@@ -521,35 +521,39 @@ public class ScriptFunction extends ScriptObject {
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 @@ public class ScriptFunction extends ScriptObject {
*
* @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;
}
......
......@@ -389,9 +389,10 @@ public abstract class ScriptFunctionData implements Serializable {
/**
* 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;
}
......
......@@ -809,9 +809,11 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
if (deep) {
final ScriptObject myProto = getProto();
if (myProto != null) {
return myProto.hasProperty(key, deep);
return myProto.hasProperty(key, true);
}
}
......@@ -1258,11 +1260,8 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
}
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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
// 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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
* @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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
return new GuardedInvocation(
Lookup.EMPTY_SETTER,
NashornGuards.getMapGuard(getMap(), explicitInstanceOfCheck),
getProtoSwitchPoint(name, null),
getProtoSwitchPoints(name, null),
explicitInstanceOfCheck ? null : ClassCastException.class);
}
......@@ -2366,7 +2380,7 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
}
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);
}
......
......@@ -186,10 +186,7 @@ final class SetMethodCreator {
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 @@ final class SetMethodCreator {
//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);
......
/*
* 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;
}
}
}
......@@ -46,7 +46,7 @@ import jdk.nashorn.internal.runtime.linker.NashornGuards;
*
*/
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 @@ public final class WithObject extends Scope {
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;
}
/**
......
......@@ -62,67 +62,146 @@ function createDeep() {
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);
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
/*
* 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));
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册