8058100: Reduce the RecompilableScriptFunctionData footprint
authorattila
Thu, 11 Sep 2014 17:12:38 +0200
changeset 26510 a6f3dd80da4c
parent 26509 00cc73c2862d
child 26511 65722244e6b2
8058100: Reduce the RecompilableScriptFunctionData footprint Reviewed-by: jlaskey, lagergren
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ObjectClassGenerator.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AllocationStrategy.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java	Wed Sep 10 19:37:52 2014 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java	Thu Sep 11 17:12:38 2014 +0200
@@ -736,13 +736,6 @@
         return name.replace('/', '.');
     }
 
-    RecompilableScriptFunctionData getProgram() {
-        if (compiledFunction == null) {
-            return null;
-        }
-        return compiledFunction.getProgram();
-    }
-
     RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
         return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId);
     }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java	Wed Sep 10 19:37:52 2014 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java	Thu Sep 11 17:12:38 2014 +0200
@@ -25,8 +25,6 @@
 
 package jdk.nashorn.internal.codegen;
 
-import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getClassName;
-import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getPaddedFieldCount;
 import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
 
 import java.util.HashMap;
@@ -34,6 +32,7 @@
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
+import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor;
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.FunctionNode;
 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
@@ -44,7 +43,6 @@
 import jdk.nashorn.internal.ir.WithNode;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Context;
-import jdk.nashorn.internal.runtime.PropertyMap;
 import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
 import jdk.nashorn.internal.runtime.logging.DebugLogger;
 import jdk.nashorn.internal.runtime.logging.Loggable;
@@ -208,14 +206,10 @@
 
         assert nestedFunctions != null;
         // Generate the object class and property map in case this function is ever used as constructor
-        final int         fieldCount         = getPaddedFieldCount(newFunctionNode.getThisProperties());
-        final String      allocatorClassName = Compiler.binaryName(getClassName(fieldCount));
-        final PropertyMap allocatorMap       = PropertyMap.newMap(null, allocatorClassName, 0, fieldCount, 0);
         final RecompilableScriptFunctionData data = new RecompilableScriptFunctionData(
                 newFunctionNode,
                 compiler.getCodeInstaller(),
-                allocatorClassName,
-                allocatorMap,
+                new AllocatorDescriptor(newFunctionNode.getThisProperties()),
                 nestedFunctions,
                 externalSymbolDepths.get(fnId),
                 internalSymbols.get(fnId)
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ObjectClassGenerator.java	Wed Sep 10 19:37:52 2014 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ObjectClassGenerator.java	Thu Sep 11 17:12:38 2014 +0200
@@ -53,7 +53,6 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
-
 import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.runtime.AccessorProperty;
@@ -826,5 +825,45 @@
         return MH.findStatic(MethodHandles.lookup(), ObjectClassGenerator.class, name, MH.type(rtype, types));
     }
 
+    /**
+     * Describes the allocator class name and property map for a constructor function with the specified
+     * number of "this" properties that it initializes.
+     *
+     */
+    public static class AllocatorDescriptor {
+        private final String allocatorClassName;
+        private final PropertyMap allocatorMap;
 
+        /**
+         * Creates a new allocator descriptor
+         * @param thisProperties the number of "this" properties that the function initializes
+         */
+        public AllocatorDescriptor(final int thisProperties) {
+            final int paddedFieldCount = getPaddedFieldCount(thisProperties);
+            this.allocatorClassName = Compiler.binaryName(getClassName(paddedFieldCount));
+            this.allocatorMap = PropertyMap.newMap(null, allocatorClassName, 0, paddedFieldCount, 0);
+        }
+
+        /**
+         * Returns the name of the class that the function allocates
+         * @return the name of the class that the function allocates
+         */
+        public String getAllocatorClassName() {
+            return allocatorClassName;
+        }
+
+        /**
+         * Returns the allocator map for the function.
+         * @return the allocator map for the function.
+         */
+        public PropertyMap getAllocatorMap() {
+            return allocatorMap;
+        }
+
+        @Override
+        public String toString() {
+            return "AllocatorDescriptor[allocatorClassName=" + allocatorClassName + ", allocatorMap.size=" +
+                    allocatorMap.size() + "]";
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/AllocationStrategy.java	Thu Sep 11 17:12:38 2014 +0200
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2014, 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 static jdk.nashorn.internal.lookup.Lookup.MH;
+
+import java.io.Serializable;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import jdk.nashorn.internal.codegen.CompilerConstants;
+import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor;
+
+/**
+ * Encapsulates the allocation strategy for a function when used as a constructor. Basically the same as
+ * {@link AllocatorDescriptor}, but with an additionally cached resolved method handle. There is also a
+ * canonical default allocation strategy for functions that don't assign any "this" properties (vast majority
+ * of all functions), therefore saving some storage space in {@link RecompilableScriptFunctionData} that would
+ * otherwise be lost to identical tuples of (map, className, handle) fields.
+ */
+final class AllocationStrategy implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+
+    private static final AllocationStrategy DEFAULT_STRATEGY = new AllocationStrategy(new AllocatorDescriptor(0));
+
+    /** Allocator map from allocator descriptor */
+    private final PropertyMap allocatorMap;
+
+    /** Name of class where allocator function resides */
+    private final String allocatorClassName;
+
+    /** lazily generated allocator */
+    private transient MethodHandle allocator;
+
+    private AllocationStrategy(final AllocatorDescriptor desc) {
+        this.allocatorMap = desc.getAllocatorMap();
+        // These classes get loaded, so an interned variant of their name is most likely around anyway.
+        this.allocatorClassName = desc.getAllocatorClassName().intern();
+    }
+
+    private boolean matches(final AllocatorDescriptor desc) {
+        return desc.getAllocatorMap().size() == allocatorMap.size() &&
+                desc.getAllocatorClassName().equals(allocatorClassName);
+    }
+
+    static AllocationStrategy get(final AllocatorDescriptor desc) {
+        return DEFAULT_STRATEGY.matches(desc) ? DEFAULT_STRATEGY : new AllocationStrategy(desc);
+    }
+
+    PropertyMap getAllocatorMap() {
+        return allocatorMap;
+    }
+
+    ScriptObject allocate(final PropertyMap map) {
+        try {
+            if (allocator == null) {
+                allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName),
+                        CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
+            }
+            return (ScriptObject)allocator.invokeExact(map);
+        } catch (final RuntimeException | Error e) {
+            throw e;
+        } catch (final Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+
+    private Object readResolve() {
+        if(allocatorMap.size() == DEFAULT_STRATEGY.allocatorMap.size() &&
+                allocatorClassName.equals(DEFAULT_STRATEGY.allocatorClassName)) {
+            return DEFAULT_STRATEGY;
+        }
+        return this;
+    }
+
+    @Override
+    public String toString() {
+        return "AllocationStrategy[allocatorClassName=" + allocatorClassName + ", allocatorMap.size=" +
+                allocatorMap.size() + "]";
+    }
+}
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Wed Sep 10 19:37:52 2014 -0700
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java	Thu Sep 11 17:12:38 2014 +0200
@@ -42,6 +42,7 @@
 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
 import jdk.nashorn.internal.codegen.CompilerConstants;
 import jdk.nashorn.internal.codegen.FunctionSignature;
+import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor;
 import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
 import jdk.nashorn.internal.codegen.TypeMap;
 import jdk.nashorn.internal.codegen.types.Type;
@@ -55,7 +56,6 @@
 import jdk.nashorn.internal.runtime.logging.DebugLogger;
 import jdk.nashorn.internal.runtime.logging.Loggable;
 import jdk.nashorn.internal.runtime.logging.Logger;
-
 /**
  * This is a subclass that represents a script function that may be regenerated,
  * for example with specialization based on call site types, or lazily generated.
@@ -81,8 +81,12 @@
     /** Token of this function within the source. */
     private final long token;
 
-    /** Allocator map from makeMap() */
-    private final PropertyMap allocatorMap;
+    /**
+     * Represents the allocation strategy (property map, script object class, and method handle) for when
+     * this function is used as a constructor. Note that majority of functions (those not setting any this.*
+     * properties) will share a single canonical "default strategy" instance.
+     */
+    private final AllocationStrategy allocationStrategy;
 
     /**
      * Opaque object representing parser state at the end of the function. Used when reparsing outer function
@@ -93,12 +97,6 @@
     /** Code installer used for all further recompilation/specialization of this ScriptFunction */
     private transient CodeInstaller<ScriptEnvironment> installer;
 
-    /** Name of class where allocator function resides */
-    private final String allocatorClassName;
-
-    /** lazily generated allocator */
-    private transient MethodHandle allocator;
-
     private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions;
 
     /** Id to parent function if one exists */
@@ -124,8 +122,7 @@
      *
      * @param functionNode        functionNode that represents this function code
      * @param installer           installer for code regeneration versions of this function
-     * @param allocatorClassName  name of our allocator class, will be looked up dynamically if used as a constructor
-     * @param allocatorMap        allocator map to seed instances with, when constructing
+     * @param allocationDescriptor descriptor for the allocation behavior when this function is used as a constructor
      * @param nestedFunctions     nested function map
      * @param externalScopeDepths external scope depths
      * @param internalSymbols     internal symbols to method, defined in its scope
@@ -133,8 +130,7 @@
     public RecompilableScriptFunctionData(
         final FunctionNode functionNode,
         final CodeInstaller<ScriptEnvironment> installer,
-        final String allocatorClassName,
-        final PropertyMap allocatorMap,
+        final AllocatorDescriptor allocationDescriptor,
         final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
         final Map<String, Integer> externalScopeDepths,
         final Set<String> internalSymbols) {
@@ -151,11 +147,10 @@
         this.endParserState      = functionNode.getEndParserState();
         this.token               = tokenFor(functionNode);
         this.installer           = installer;
-        this.allocatorClassName  = allocatorClassName;
-        this.allocatorMap        = allocatorMap;
-        this.nestedFunctions     = nestedFunctions;
-        this.externalScopeDepths = externalScopeDepths;
-        this.internalSymbols     = new HashSet<>(internalSymbols);
+        this.allocationStrategy  = AllocationStrategy.get(allocationDescriptor);
+        this.nestedFunctions     = smallMap(nestedFunctions);
+        this.externalScopeDepths = smallMap(externalScopeDepths);
+        this.internalSymbols     = smallSet(new HashSet<>(internalSymbols));
 
         for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) {
             assert nfn.getParent() == null;
@@ -165,6 +160,27 @@
         createLogger();
     }
 
+    private static <K, V> Map<K, V> smallMap(final Map<K, V> map) {
+        if (map == null || map.isEmpty()) {
+            return Collections.emptyMap();
+        } else if (map.size() == 1) {
+            final Map.Entry<K, V> entry = map.entrySet().iterator().next();
+            return Collections.singletonMap(entry.getKey(), entry.getValue());
+        } else {
+            return map;
+        }
+    }
+
+    private static <T> Set<T> smallSet(final Set<T> set) {
+        if (set == null || set.isEmpty()) {
+            return Collections.emptySet();
+        } else if (set.size() == 1) {
+            return Collections.singleton(set.iterator().next());
+        } else {
+            return set;
+        }
+    }
+
     @Override
     public DebugLogger getLogger() {
         return log;
@@ -194,11 +210,7 @@
      * @return the external symbol table with proto depths
      */
     public int getExternalSymbolDepth(final String symbolName) {
-        final Map<String, Integer> map = externalScopeDepths;
-        if (map == null) {
-            return -1;
-        }
-        final Integer depth = map.get(symbolName);
+        final Integer depth = externalScopeDepths.get(symbolName);
         if (depth == null) {
             return -1;
         }
@@ -210,8 +222,7 @@
      * @return the names of all external symbols this function uses.
      */
     public Set<String> getExternalSymbolNames() {
-        return externalScopeDepths == null ? Collections.<String>emptySet() :
-            Collections.unmodifiableSet(externalScopeDepths.keySet());
+        return Collections.unmodifiableSet(externalScopeDepths.keySet());
     }
 
     /**
@@ -334,25 +345,12 @@
 
     @Override
     PropertyMap getAllocatorMap() {
-        return allocatorMap;
+        return allocationStrategy.getAllocatorMap();
     }
 
     @Override
     ScriptObject allocate(final PropertyMap map) {
-        try {
-            ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try
-            return allocator == null ? null : (ScriptObject)allocator.invokeExact(map);
-        } catch (final RuntimeException | Error e) {
-            throw e;
-        } catch (final Throwable t) {
-            throw new RuntimeException(t);
-        }
-    }
-
-    private void ensureHasAllocator() throws ClassNotFoundException {
-        if (allocator == null && allocatorClassName != null) {
-            this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
-        }
+        return allocationStrategy.allocate(map);
     }
 
     FunctionNode reparse() {
@@ -756,21 +754,6 @@
     }
 
     /**
-     * Get the uppermost parent, the program, for this data
-     * @return program
-     */
-    public RecompilableScriptFunctionData getProgram() {
-        RecompilableScriptFunctionData program = this;
-        while (true) {
-            final RecompilableScriptFunctionData p = program.getParent();
-            if (p == null) {
-                return program;
-            }
-            program = p;
-        }
-    }
-
-    /**
      * Check whether a certain name is a global symbol, i.e. only exists as defined
      * in outermost scope and not shadowed by being parameter or assignment in inner
      * scopes