mirror of
https://github.com/openjdk/jdk.git
synced 2026-02-16 05:15:22 +00:00
8134609: Allow constructors with same prototoype map to share the allocator map
Reviewed-by: attila, sundar
This commit is contained in:
parent
1b10b826d8
commit
f4e6cd073f
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
95
nashorn/test/script/basic/JDK-8134609.js
Normal file
95
nashorn/test/script/basic/JDK-8134609.js
Normal file
@ -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));
|
||||
Loading…
x
Reference in New Issue
Block a user