8058100: Reduce the RecompilableScriptFunctionData footprint
Reviewed-by: jlaskey, lagergren
--- 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