8011630: JSON parsing performance issue
authorhannesw
Fri, 24 May 2013 13:54:18 +0200
changeset 17770 3c8602ec5565
parent 17769 14ea7feaf658
child 17771 9fedae4933e4
8011630: JSON parsing performance issue Reviewed-by: lagergren, sundar
nashorn/src/jdk/nashorn/internal/objects/NativeArguments.java
nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java
nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
nashorn/src/jdk/nashorn/internal/runtime/Property.java
nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java
nashorn/src/jdk/nashorn/internal/runtime/SetMethodCreator.java
nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeArguments.java	Thu May 23 15:51:08 2013 +0200
+++ b/nashorn/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;
     }
--- a/nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java	Thu May 23 15:51:08 2013 +0200
+++ b/nashorn/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);
-
-        this.getters = new MethodHandle[NOOF_TYPES];
+        super(property);
 
-        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);
--- a/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java	Thu May 23 15:51:08 2013 +0200
+++ b/nashorn/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);
+    }
+
 }
 
--- a/nashorn/src/jdk/nashorn/internal/runtime/Property.java	Thu May 23 15:51:08 2013 +0200
+++ b/nashorn/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.
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java	Thu May 23 15:51:08 2013 +0200
+++ b/nashorn/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);
--- a/nashorn/src/jdk/nashorn/internal/runtime/SetMethodCreator.java	Thu May 23 15:51:08 2013 +0200
+++ b/nashorn/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);
--- a/nashorn/src/jdk/nashorn/internal/runtime/UserAccessorProperty.java	Thu May 23 15:51:08 2013 +0200
+++ b/nashorn/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));
+        }
+    }
+
 }