# HG changeset patch # User hannesw # Date 1369396458 -7200 # Node ID fdfb4edd78d67ba9caafb71c754d8204fb129d72 # Parent 6fc7b51e83d66c4978eeba8c2fa3de874d9ac3c5 8011630: JSON parsing performance issue Reviewed-by: lagergren, sundar diff -r 6fc7b51e83d6 -r fdfb4edd78d6 src/jdk/nashorn/internal/objects/NativeArguments.java --- a/src/jdk/nashorn/internal/objects/NativeArguments.java Thu May 23 15:51:08 2013 +0200 +++ b/src/jdk/nashorn/internal/objects/NativeArguments.java Fri May 24 13:54:18 2013 +0200 @@ -603,6 +603,11 @@ } } + @Override + public Object getLength() { + return length; + } + private Object getArgumentsLength() { return length; } diff -r 6fc7b51e83d6 -r fdfb4edd78d6 src/jdk/nashorn/internal/runtime/AccessorProperty.java --- a/src/jdk/nashorn/internal/runtime/AccessorProperty.java Thu May 23 15:51:08 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/AccessorProperty.java Fri May 24 13:54:18 2013 +0200 @@ -75,7 +75,23 @@ private static final MethodType[] ACCESSOR_GETTER_TYPES = new MethodType[NOOF_TYPES]; private static final MethodType[] ACCESSOR_SETTER_TYPES = new MethodType[NOOF_TYPES]; - private static final MethodHandle SPILLGETTER = MH.asType(MH.getter(MethodHandles.lookup(), ScriptObject.class, "spill", Object[].class), Lookup.GET_OBJECT_TYPE); + private static final MethodHandle SPILL_ELEMENT_GETTER; + private static final MethodHandle SPILL_ELEMENT_SETTER; + + private static final int SPILL_CACHE_SIZE = 8; + private static final MethodHandle[] SPILL_ACCESSORS = new MethodHandle[SPILL_CACHE_SIZE * 2]; + + static { + for (int i = 0; i < NOOF_TYPES; i++) { + final Type type = ACCESSOR_TYPES.get(i); + ACCESSOR_GETTER_TYPES[i] = MH.type(type.getTypeClass(), Object.class); + ACCESSOR_SETTER_TYPES[i] = MH.type(void.class, Object.class, type.getTypeClass()); + } + + final MethodHandle spillGetter = MH.getter(MethodHandles.lookup(), ScriptObject.class, "spill", Object[].class); + SPILL_ELEMENT_GETTER = MH.filterArguments(MH.arrayElementGetter(Object[].class), 0, spillGetter); + SPILL_ELEMENT_SETTER = MH.filterArguments(MH.arrayElementSetter(Object[].class), 0, spillGetter); + } /** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ private MethodHandle primitiveGetter; @@ -96,14 +112,6 @@ */ private Class currentType; - static { - for (int i = 0; i < NOOF_TYPES; i++) { - final Type type = ACCESSOR_TYPES.get(i); - ACCESSOR_GETTER_TYPES[i] = MH.type(type.getTypeClass(), Object.class); - ACCESSOR_SETTER_TYPES[i] = MH.type(void.class, Object.class, type.getTypeClass()); - } - } - /** * Delegate constructor. This is used when adding properties to the Global scope, which * is necessary for outermost levels in a script (the ScriptObject is represented by @@ -114,19 +122,31 @@ * @param delegate delegate script object to rebind receiver to */ public AccessorProperty(final AccessorProperty property, final ScriptObject delegate) { - this(property); + super(property); - this.getters = new MethodHandle[NOOF_TYPES]; - - this.primitiveGetter = bindTo(primitiveGetter, delegate); - this.primitiveSetter = bindTo(primitiveSetter, delegate); - this.objectGetter = bindTo(objectGetter, delegate); - this.objectSetter = bindTo(objectSetter, delegate); + this.primitiveGetter = bindTo(property.primitiveGetter, delegate); + this.primitiveSetter = bindTo(property.primitiveSetter, delegate); + this.objectGetter = bindTo(property.objectGetter, delegate); + this.objectSetter = bindTo(property.objectSetter, delegate); setCurrentType(property.getCurrentType()); } /** + * Constructor for spill properties. Array getters and setters will be created on demand. + * + * @param key the property key + * @param flags the property flags + * @param slot spill slot + */ + public AccessorProperty(final String key, final int flags, final int slot) { + super(key, flags, slot); + assert (flags & IS_SPILL) == IS_SPILL; + + setCurrentType(Object.class); + } + + /** * Constructor. Similar to the constructor with both primitive getters and setters, the difference * here being that only one getter and setter (setter is optional for non writable fields) is given * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes @@ -268,7 +288,40 @@ } @Override + protected void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { + if (isSpill()) { + self.spill[getSlot()] = value; + } else { + try { + getSetter(Object.class, self.getMap()).invokeExact((Object)self, value); + } catch (final Error|RuntimeException e) { + throw e; + } catch (final Throwable e) { + throw new RuntimeException(e); + } + } + } + + @Override + protected Object getObjectValue(final ScriptObject self, final ScriptObject owner) { + if (isSpill()) { + return self.spill[getSlot()]; + } else { + try { + return getGetter(Object.class).invokeExact((Object)self); + } catch (final Error|RuntimeException e) { + throw e; + } catch (final Throwable e) { + throw new RuntimeException(e); + } + } + } + + @Override public MethodHandle getGetter(final Class type) { + if (isSpill() && objectGetter == null) { + objectGetter = getSpillGetter(); + } final int i = getAccessorTypeIndex(type); if (getters[i] == null) { getters[i] = debug( @@ -284,7 +337,7 @@ "get"); } - return isSpill() ? MH.filterArguments(getters[i], 0, SPILLGETTER) : getters[i]; + return getters[i]; } private Property getWiderProperty(final Class type) { @@ -313,6 +366,9 @@ } private MethodHandle generateSetter(final Class forType, final Class type) { + if (isSpill() && objectSetter == null) { + objectSetter = getSpillSetter(); + } MethodHandle mh = createSetter(forType, type, primitiveSetter, objectSetter); mh = MH.asType(mh, ACCESSOR_SETTER_TYPES[getAccessorTypeIndex(type)]); //has to be the case for invokeexact to work in ScriptObject mh = debug(mh, currentType, type, "set"); @@ -343,7 +399,7 @@ mh = generateSetter(forType, type); } - return isSpill() ? MH.filterArguments(mh, 0, SPILLGETTER) : mh; + return mh; } @Override @@ -363,6 +419,30 @@ setCurrentType(newType); } + private MethodHandle getSpillGetter() { + final int slot = getSlot(); + MethodHandle getter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2] : null; + if (getter == null) { + getter = MH.asType(MH.insertArguments(SPILL_ELEMENT_GETTER, 1, slot), Lookup.GET_OBJECT_TYPE); + if (slot < SPILL_CACHE_SIZE) { + SPILL_ACCESSORS[slot * 2] = getter; + } + } + return getter; + } + + private MethodHandle getSpillSetter() { + final int slot = getSlot(); + MethodHandle setter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2 + 1] : null; + if (setter == null) { + setter = MH.asType(MH.insertArguments(SPILL_ELEMENT_SETTER, 1, slot), Lookup.SET_OBJECT_TYPE); + if (slot < SPILL_CACHE_SIZE) { + SPILL_ACCESSORS[slot * 2 + 1] = setter; + } + } + return setter; + } + private static void finest(final String str) { if (DEBUG_FIELDS) { LOG.finest(str); diff -r 6fc7b51e83d6 -r fdfb4edd78d6 src/jdk/nashorn/internal/runtime/FindProperty.java --- a/src/jdk/nashorn/internal/runtime/FindProperty.java Thu May 23 15:51:08 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/FindProperty.java Fri May 24 13:54:18 2013 +0200 @@ -153,5 +153,24 @@ return prototype.isScope(); } + /** + * Get the property value from self as object. + * + * @return the property value + */ + public Object getObjectValue() { + return property.getObjectValue(getGetterReceiver(), getOwner()); + } + + /** + * Set the property value in self. + * + * @param value the new value + * @param strict strict flag + */ + public void setObjectValue(final Object value, final boolean strict) { + property.setObjectValue(getSetterReceiver(), getOwner(), value, strict); + } + } diff -r 6fc7b51e83d6 -r fdfb4edd78d6 src/jdk/nashorn/internal/runtime/Property.java --- a/src/jdk/nashorn/internal/runtime/Property.java Thu May 23 15:51:08 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/Property.java Fri May 24 13:54:18 2013 +0200 @@ -352,6 +352,26 @@ } /** + * Set the value of this property in {@code owner}. This allows to bypass creation of the + * setter MethodHandle for spill and user accessor properties. + * + * @param self the this object + * @param owner the owner object + * @param value the new property value + */ + protected abstract void setObjectValue(ScriptObject self, ScriptObject owner, Object value, boolean strict); + + /** + * Set the Object value of this property from {@code owner}. This allows to bypass creation of the + * getter MethodHandle for spill and user accessor properties. + * + * @param self the this object + * @param owner the owner object + * @return the property value + */ + protected abstract Object getObjectValue(ScriptObject self, ScriptObject owner); + + /** * Abstract method for retrieving the setter for the property. We do not know * anything about the internal representation when we request the setter, we only * know that the setter will take the property as a parameter of the given type. diff -r 6fc7b51e83d6 -r fdfb4edd78d6 src/jdk/nashorn/internal/runtime/ScriptObject.java --- a/src/jdk/nashorn/internal/runtime/ScriptObject.java Thu May 23 15:51:08 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java Fri May 24 13:54:18 2013 +0200 @@ -25,7 +25,6 @@ package jdk.nashorn.internal.runtime; -import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall; import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; import static jdk.nashorn.internal.lookup.Lookup.MH; @@ -151,17 +150,6 @@ /** Method handle for setting the user accessors of a ScriptObject */ public static final Call SET_USER_ACCESSORS = virtualCall(ScriptObject.class, "setUserAccessors", void.class, String.class, ScriptFunction.class, ScriptFunction.class); - /** Method handle for getter for {@link UserAccessorProperty}, given a slot */ - static final Call USER_ACCESSOR_GETTER = staticCall(MethodHandles.lookup(), ScriptObject.class, "userAccessorGetter", Object.class, ScriptObject.class, int.class, Object.class); - - /** Method handle for setter for {@link UserAccessorProperty}, given a slot */ - static final Call USER_ACCESSOR_SETTER = staticCall(MethodHandles.lookup(), ScriptObject.class, "userAccessorSetter", void.class, ScriptObject.class, int.class, String.class, Object.class, Object.class); - - private static final MethodHandle INVOKE_UA_GETTER = Bootstrap.createDynamicInvoker("dyn:call", Object.class, - Object.class, Object.class); - private static final MethodHandle INVOKE_UA_SETTER = Bootstrap.createDynamicInvoker("dyn:call", void.class, - Object.class, Object.class, Object.class); - /** * Constructor */ @@ -699,17 +687,9 @@ * @return New property. */ public final Property addOwnProperty(final String key, final int propertyFlags, final Object value) { - final MethodHandle setter = addSpill(key, propertyFlags); - - try { - setter.invokeExact((Object)this, value); - } catch (final Error|RuntimeException e) { - throw e; - } catch (final Throwable e) { - throw new RuntimeException(e); - } - - return getMap().findProperty(key); + final Property property = addSpillProperty(key, propertyFlags); + property.setObjectValue(this, this, value, false); + return property; } /** @@ -744,15 +724,7 @@ // Erase the property field value with undefined. If the property is defined // by user-defined accessors, we don't want to call the setter!! if (!(property instanceof UserAccessorProperty)) { - try { - // make the property value to be undefined - //TODO specproperties - property.getSetter(Object.class, getMap()).invokeExact((Object)this, (Object)UNDEFINED); - } catch (final RuntimeException | Error e) { - throw e; - } catch (final Throwable t) { - throw new RuntimeException(t); - } + property.setObjectValue(this, this, UNDEFINED, false); } } @@ -948,18 +920,7 @@ * @return the value of the property */ protected static Object getObjectValue(final FindProperty find) { - final MethodHandle getter = find.getGetter(Object.class); - if (getter != null) { - try { - return getter.invokeExact((Object)find.getGetterReceiver()); - } catch (final Error|RuntimeException e) { - throw e; - } catch (final Throwable e) { - throw new RuntimeException(e); - } - } - - return UNDEFINED; + return find.getObjectValue(); } /** @@ -2087,11 +2048,7 @@ property = addOwnProperty(property); } else { int i = getMap().getSpillLength(); - MethodHandle getter = MH.arrayElementGetter(Object[].class); - MethodHandle setter = MH.arrayElementSetter(Object[].class); - getter = MH.asType(MH.insertArguments(getter, 1, i), Lookup.GET_OBJECT_TYPE); - setter = MH.asType(MH.insertArguments(setter, 1, i), Lookup.SET_OBJECT_TYPE); - property = new AccessorProperty(key, propertyFlags | Property.IS_SPILL, i, getter, setter); + property = new AccessorProperty(key, propertyFlags | Property.IS_SPILL, i); notifyPropertyAdded(this, property); property = addOwnProperty(property); i = property.getSlot(); @@ -2115,20 +2072,15 @@ /** * Add a spill entry for the given key. - * @param key Property key. - * @param propertyFlags Property flags. + * @param key Property key. * @return Setter method handle. */ - private MethodHandle addSpill(final String key, final int propertyFlags) { - final Property spillProperty = addSpillProperty(key, propertyFlags); + MethodHandle addSpill(final String key) { + final Property spillProperty = addSpillProperty(key, 0); final Class type = Object.class; return spillProperty.getSetter(type, getMap()); //TODO specfields } - MethodHandle addSpill(final String key) { - return addSpill(key, 0); - } - /** * Make sure arguments are paired correctly, with respect to more parameters than declared, * fewer parameters than declared and other things that JavaScript allows. This might involve @@ -2659,14 +2611,8 @@ return; } - try { - final MethodHandle setter = f.getSetter(Object.class, strict); //TODO specfields - setter.invokeExact((Object)f.getSetterReceiver(), value); - } catch (final Error|RuntimeException e) { - throw e; - } catch (final Throwable e) { - throw new RuntimeException(e); - } + f.setObjectValue(value, strict); + } else if (!isExtensible()) { if (strict) { throw typeError("object.non.extensible", key, ScriptRuntime.safeToString(this)); @@ -2677,13 +2623,7 @@ } private void spill(final String key, final Object value) { - try { - addSpill(key).invokeExact((Object)this, value); - } catch (final Error|RuntimeException e) { - throw e; - } catch (final Throwable e) { - throw new RuntimeException(e); - } + addSpillProperty(key, 0).setObjectValue(this, this, value, false); } @@ -3217,46 +3157,6 @@ return (index < 0 || (index >= spill.length)) ? null : spill[index]; } - // User defined getter and setter are always called by "dyn:call". Note that the user - // getter/setter may be inherited. If so, proto is bound during lookup. In either - // inherited or self case, slot is also bound during lookup. Actual ScriptFunction - // to be called is retrieved everytime and applied. - @SuppressWarnings("unused") - private static Object userAccessorGetter(final ScriptObject proto, final int slot, final Object self) { - final ScriptObject container = (proto != null) ? proto : (ScriptObject)self; - final Object func = container.getSpill(slot); - - if (func instanceof ScriptFunction) { - try { - return INVOKE_UA_GETTER.invokeExact(func, self); - } catch(final Error|RuntimeException t) { - throw t; - } catch(final Throwable t) { - throw new RuntimeException(t); - } - } - - return UNDEFINED; - } - - @SuppressWarnings("unused") - private static void userAccessorSetter(final ScriptObject proto, final int slot, final String name, final Object self, final Object value) { - final ScriptObject container = (proto != null) ? proto : (ScriptObject)self; - final Object func = container.getSpill(slot); - - if (func instanceof ScriptFunction) { - try { - INVOKE_UA_SETTER.invokeExact(func, self, value); - } catch(final Error|RuntimeException t) { - throw t; - } catch(final Throwable t) { - throw new RuntimeException(t); - } - } else if (name != null) { - throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self)); - } - } - private static MethodHandle findOwnMH(final String name, final Class rtype, final Class... types) { final Class own = ScriptObject.class; final MethodType mt = MH.type(rtype, types); diff -r 6fc7b51e83d6 -r fdfb4edd78d6 src/jdk/nashorn/internal/runtime/SetMethodCreator.java --- a/src/jdk/nashorn/internal/runtime/SetMethodCreator.java Thu May 23 15:51:08 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/SetMethodCreator.java Fri May 24 13:54:18 2013 +0200 @@ -183,17 +183,10 @@ private SetMethod createNewSpillPropertySetter() { final int nextSpill = getMap().getSpillLength(); - final Property property = createSpillProperty(nextSpill); + final Property property = new AccessorProperty(getName(), Property.IS_SPILL, nextSpill); return new SetMethod(createSpillMethodHandle(nextSpill, property), property); } - private Property createSpillProperty(final int nextSpill) { - final MethodHandle getter = MH.asType(MH.insertArguments(MH.arrayElementGetter(Object[].class), 1, nextSpill), Lookup.GET_OBJECT_TYPE); - final MethodHandle setter = MH.asType(MH.insertArguments(MH.arrayElementSetter(Object[].class), 1, nextSpill), Lookup.SET_OBJECT_TYPE); - - return new AccessorProperty(getName(), Property.IS_SPILL, nextSpill, getter, setter); - } - private MethodHandle createSpillMethodHandle(final int nextSpill, Property property) { final PropertyMap oldMap = getMap(); final PropertyMap newMap = getNewMap(property); diff -r 6fc7b51e83d6 -r fdfb4edd78d6 src/jdk/nashorn/internal/runtime/UserAccessorProperty.java --- a/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java Thu May 23 15:51:08 2013 +0200 +++ b/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java Fri May 24 13:54:18 2013 +0200 @@ -26,7 +26,15 @@ package jdk.nashorn.internal.runtime; import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + +import jdk.nashorn.internal.codegen.CompilerConstants; import jdk.nashorn.internal.lookup.Lookup; +import jdk.nashorn.internal.runtime.linker.Bootstrap; + +import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall; +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; +import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; /** * Property with user defined getters/setters. Actual getter and setter @@ -51,6 +59,22 @@ /** User defined setter function slot. */ private final int setterSlot; + /** Getter method handle */ + private final static CompilerConstants.Call USER_ACCESSOR_GETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class, + "userAccessorGetter", Object.class, ScriptObject.class, int.class, Object.class); + + /** Setter method handle */ + private final static CompilerConstants.Call USER_ACCESSOR_SETTER = staticCall(MethodHandles.lookup(), UserAccessorProperty.class, + "userAccessorSetter", void.class, ScriptObject.class, int.class, String.class, Object.class, Object.class); + + /** Dynamic invoker for getter */ + private static final MethodHandle INVOKE_UA_GETTER = Bootstrap.createDynamicInvoker("dyn:call", Object.class, + Object.class, Object.class); + + /** Dynamic invoker for setter */ + private static final MethodHandle INVOKE_UA_SETTER = Bootstrap.createDynamicInvoker("dyn:call", void.class, + Object.class, Object.class, Object.class); + /** * Constructor * @@ -134,8 +158,18 @@ } @Override + protected Object getObjectValue(final ScriptObject self, final ScriptObject owner) { + return userAccessorGetter(owner, getGetterSlot(), self); + } + + @Override + protected void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { + userAccessorSetter(owner, getSetterSlot(), strict ? getKey() : null, self, value); + } + + @Override public MethodHandle getGetter(final Class type) { - return Lookup.filterReturnType(ScriptObject.USER_ACCESSOR_GETTER.methodHandle(), type); + return Lookup.filterReturnType(USER_ACCESSOR_GETTER.methodHandle(), type); } @Override @@ -146,7 +180,7 @@ @Override public MethodHandle getSetter(final Class type, final PropertyMap currentMap) { - return ScriptObject.USER_ACCESSOR_SETTER.methodHandle(); + return USER_ACCESSOR_SETTER.methodHandle(); } @Override @@ -155,4 +189,44 @@ return (value instanceof ScriptFunction) ? (ScriptFunction) value : null; } + // User defined getter and setter are always called by "dyn:call". Note that the user + // getter/setter may be inherited. If so, proto is bound during lookup. In either + // inherited or self case, slot is also bound during lookup. Actual ScriptFunction + // to be called is retrieved everytime and applied. + @SuppressWarnings("unused") + static Object userAccessorGetter(final ScriptObject proto, final int slot, final Object self) { + final ScriptObject container = (proto != null) ? proto : (ScriptObject)self; + final Object func = container.getSpill(slot); + + if (func instanceof ScriptFunction) { + try { + return INVOKE_UA_GETTER.invokeExact(func, self); + } catch(final Error|RuntimeException t) { + throw t; + } catch(final Throwable t) { + throw new RuntimeException(t); + } + } + + return UNDEFINED; + } + + @SuppressWarnings("unused") + static void userAccessorSetter(final ScriptObject proto, final int slot, final String name, final Object self, final Object value) { + final ScriptObject container = (proto != null) ? proto : (ScriptObject)self; + final Object func = container.getSpill(slot); + + if (func instanceof ScriptFunction) { + try { + INVOKE_UA_SETTER.invokeExact(func, self, value); + } catch(final Error|RuntimeException t) { + throw t; + } catch(final Throwable t) { + throw new RuntimeException(t); + } + } else if (name != null) { + throw typeError("property.has.no.setter", name, ScriptRuntime.safeToString(self)); + } + } + }