Thu, 11 Sep 2014 17:12:38 +0200
8058100: Reduce the RecompilableScriptFunctionData footprint
Reviewed-by: jlaskey, lagergren
1.1 --- a/src/jdk/nashorn/internal/codegen/Compiler.java Wed Sep 10 12:37:44 2014 +0200 1.2 +++ b/src/jdk/nashorn/internal/codegen/Compiler.java Thu Sep 11 17:12:38 2014 +0200 1.3 @@ -736,13 +736,6 @@ 1.4 return name.replace('/', '.'); 1.5 } 1.6 1.7 - RecompilableScriptFunctionData getProgram() { 1.8 - if (compiledFunction == null) { 1.9 - return null; 1.10 - } 1.11 - return compiledFunction.getProgram(); 1.12 - } 1.13 - 1.14 RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { 1.15 return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId); 1.16 }
2.1 --- a/src/jdk/nashorn/internal/codegen/FindScopeDepths.java Wed Sep 10 12:37:44 2014 +0200 2.2 +++ b/src/jdk/nashorn/internal/codegen/FindScopeDepths.java Thu Sep 11 17:12:38 2014 +0200 2.3 @@ -25,8 +25,6 @@ 2.4 2.5 package jdk.nashorn.internal.codegen; 2.6 2.7 -import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getClassName; 2.8 -import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getPaddedFieldCount; 2.9 import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; 2.10 2.11 import java.util.HashMap; 2.12 @@ -34,6 +32,7 @@ 2.13 import java.util.Iterator; 2.14 import java.util.Map; 2.15 import java.util.Set; 2.16 +import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor; 2.17 import jdk.nashorn.internal.ir.Block; 2.18 import jdk.nashorn.internal.ir.FunctionNode; 2.19 import jdk.nashorn.internal.ir.FunctionNode.CompilationState; 2.20 @@ -44,7 +43,6 @@ 2.21 import jdk.nashorn.internal.ir.WithNode; 2.22 import jdk.nashorn.internal.ir.visitor.NodeVisitor; 2.23 import jdk.nashorn.internal.runtime.Context; 2.24 -import jdk.nashorn.internal.runtime.PropertyMap; 2.25 import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; 2.26 import jdk.nashorn.internal.runtime.logging.DebugLogger; 2.27 import jdk.nashorn.internal.runtime.logging.Loggable; 2.28 @@ -208,14 +206,10 @@ 2.29 2.30 assert nestedFunctions != null; 2.31 // Generate the object class and property map in case this function is ever used as constructor 2.32 - final int fieldCount = getPaddedFieldCount(newFunctionNode.getThisProperties()); 2.33 - final String allocatorClassName = Compiler.binaryName(getClassName(fieldCount)); 2.34 - final PropertyMap allocatorMap = PropertyMap.newMap(null, allocatorClassName, 0, fieldCount, 0); 2.35 final RecompilableScriptFunctionData data = new RecompilableScriptFunctionData( 2.36 newFunctionNode, 2.37 compiler.getCodeInstaller(), 2.38 - allocatorClassName, 2.39 - allocatorMap, 2.40 + new AllocatorDescriptor(newFunctionNode.getThisProperties()), 2.41 nestedFunctions, 2.42 externalSymbolDepths.get(fnId), 2.43 internalSymbols.get(fnId)
3.1 --- a/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java Wed Sep 10 12:37:44 2014 +0200 3.2 +++ b/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java Thu Sep 11 17:12:38 2014 +0200 3.3 @@ -53,7 +53,6 @@ 3.4 import java.util.Iterator; 3.5 import java.util.LinkedList; 3.6 import java.util.List; 3.7 - 3.8 import jdk.nashorn.internal.codegen.ClassEmitter.Flag; 3.9 import jdk.nashorn.internal.codegen.types.Type; 3.10 import jdk.nashorn.internal.runtime.AccessorProperty; 3.11 @@ -826,5 +825,45 @@ 3.12 return MH.findStatic(MethodHandles.lookup(), ObjectClassGenerator.class, name, MH.type(rtype, types)); 3.13 } 3.14 3.15 + /** 3.16 + * Describes the allocator class name and property map for a constructor function with the specified 3.17 + * number of "this" properties that it initializes. 3.18 + * 3.19 + */ 3.20 + public static class AllocatorDescriptor { 3.21 + private final String allocatorClassName; 3.22 + private final PropertyMap allocatorMap; 3.23 3.24 + /** 3.25 + * Creates a new allocator descriptor 3.26 + * @param thisProperties the number of "this" properties that the function initializes 3.27 + */ 3.28 + public AllocatorDescriptor(final int thisProperties) { 3.29 + final int paddedFieldCount = getPaddedFieldCount(thisProperties); 3.30 + this.allocatorClassName = Compiler.binaryName(getClassName(paddedFieldCount)); 3.31 + this.allocatorMap = PropertyMap.newMap(null, allocatorClassName, 0, paddedFieldCount, 0); 3.32 + } 3.33 + 3.34 + /** 3.35 + * Returns the name of the class that the function allocates 3.36 + * @return the name of the class that the function allocates 3.37 + */ 3.38 + public String getAllocatorClassName() { 3.39 + return allocatorClassName; 3.40 + } 3.41 + 3.42 + /** 3.43 + * Returns the allocator map for the function. 3.44 + * @return the allocator map for the function. 3.45 + */ 3.46 + public PropertyMap getAllocatorMap() { 3.47 + return allocatorMap; 3.48 + } 3.49 + 3.50 + @Override 3.51 + public String toString() { 3.52 + return "AllocatorDescriptor[allocatorClassName=" + allocatorClassName + ", allocatorMap.size=" + 3.53 + allocatorMap.size() + "]"; 3.54 + } 3.55 + } 3.56 }
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/src/jdk/nashorn/internal/runtime/AllocationStrategy.java Thu Sep 11 17:12:38 2014 +0200 4.3 @@ -0,0 +1,104 @@ 4.4 +/* 4.5 + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. 4.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4.7 + * 4.8 + * This code is free software; you can redistribute it and/or modify it 4.9 + * under the terms of the GNU General Public License version 2 only, as 4.10 + * published by the Free Software Foundation. Oracle designates this 4.11 + * particular file as subject to the "Classpath" exception as provided 4.12 + * by Oracle in the LICENSE file that accompanied this code. 4.13 + * 4.14 + * This code is distributed in the hope that it will be useful, but WITHOUT 4.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 4.16 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 4.17 + * version 2 for more details (a copy is included in the LICENSE file that 4.18 + * accompanied this code). 4.19 + * 4.20 + * You should have received a copy of the GNU General Public License version 4.21 + * 2 along with this work; if not, write to the Free Software Foundation, 4.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 4.23 + * 4.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 4.25 + * or visit www.oracle.com if you need additional information or have any 4.26 + * questions. 4.27 + */ 4.28 +package jdk.nashorn.internal.runtime; 4.29 + 4.30 +import static jdk.nashorn.internal.lookup.Lookup.MH; 4.31 + 4.32 +import java.io.Serializable; 4.33 +import java.lang.invoke.MethodHandle; 4.34 +import java.lang.invoke.MethodHandles; 4.35 +import jdk.nashorn.internal.codegen.CompilerConstants; 4.36 +import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor; 4.37 + 4.38 +/** 4.39 + * Encapsulates the allocation strategy for a function when used as a constructor. Basically the same as 4.40 + * {@link AllocatorDescriptor}, but with an additionally cached resolved method handle. There is also a 4.41 + * canonical default allocation strategy for functions that don't assign any "this" properties (vast majority 4.42 + * of all functions), therefore saving some storage space in {@link RecompilableScriptFunctionData} that would 4.43 + * otherwise be lost to identical tuples of (map, className, handle) fields. 4.44 + */ 4.45 +final class AllocationStrategy implements Serializable { 4.46 + private static final long serialVersionUID = 1L; 4.47 + 4.48 + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); 4.49 + 4.50 + private static final AllocationStrategy DEFAULT_STRATEGY = new AllocationStrategy(new AllocatorDescriptor(0)); 4.51 + 4.52 + /** Allocator map from allocator descriptor */ 4.53 + private final PropertyMap allocatorMap; 4.54 + 4.55 + /** Name of class where allocator function resides */ 4.56 + private final String allocatorClassName; 4.57 + 4.58 + /** lazily generated allocator */ 4.59 + private transient MethodHandle allocator; 4.60 + 4.61 + private AllocationStrategy(final AllocatorDescriptor desc) { 4.62 + this.allocatorMap = desc.getAllocatorMap(); 4.63 + // These classes get loaded, so an interned variant of their name is most likely around anyway. 4.64 + this.allocatorClassName = desc.getAllocatorClassName().intern(); 4.65 + } 4.66 + 4.67 + private boolean matches(final AllocatorDescriptor desc) { 4.68 + return desc.getAllocatorMap().size() == allocatorMap.size() && 4.69 + desc.getAllocatorClassName().equals(allocatorClassName); 4.70 + } 4.71 + 4.72 + static AllocationStrategy get(final AllocatorDescriptor desc) { 4.73 + return DEFAULT_STRATEGY.matches(desc) ? DEFAULT_STRATEGY : new AllocationStrategy(desc); 4.74 + } 4.75 + 4.76 + PropertyMap getAllocatorMap() { 4.77 + return allocatorMap; 4.78 + } 4.79 + 4.80 + ScriptObject allocate(final PropertyMap map) { 4.81 + try { 4.82 + if (allocator == null) { 4.83 + allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), 4.84 + CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class)); 4.85 + } 4.86 + return (ScriptObject)allocator.invokeExact(map); 4.87 + } catch (final RuntimeException | Error e) { 4.88 + throw e; 4.89 + } catch (final Throwable t) { 4.90 + throw new RuntimeException(t); 4.91 + } 4.92 + } 4.93 + 4.94 + private Object readResolve() { 4.95 + if(allocatorMap.size() == DEFAULT_STRATEGY.allocatorMap.size() && 4.96 + allocatorClassName.equals(DEFAULT_STRATEGY.allocatorClassName)) { 4.97 + return DEFAULT_STRATEGY; 4.98 + } 4.99 + return this; 4.100 + } 4.101 + 4.102 + @Override 4.103 + public String toString() { 4.104 + return "AllocationStrategy[allocatorClassName=" + allocatorClassName + ", allocatorMap.size=" + 4.105 + allocatorMap.size() + "]"; 4.106 + } 4.107 +}
5.1 --- a/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Wed Sep 10 12:37:44 2014 +0200 5.2 +++ b/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java Thu Sep 11 17:12:38 2014 +0200 5.3 @@ -42,6 +42,7 @@ 5.4 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases; 5.5 import jdk.nashorn.internal.codegen.CompilerConstants; 5.6 import jdk.nashorn.internal.codegen.FunctionSignature; 5.7 +import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor; 5.8 import jdk.nashorn.internal.codegen.OptimisticTypesPersistence; 5.9 import jdk.nashorn.internal.codegen.TypeMap; 5.10 import jdk.nashorn.internal.codegen.types.Type; 5.11 @@ -55,7 +56,6 @@ 5.12 import jdk.nashorn.internal.runtime.logging.DebugLogger; 5.13 import jdk.nashorn.internal.runtime.logging.Loggable; 5.14 import jdk.nashorn.internal.runtime.logging.Logger; 5.15 - 5.16 /** 5.17 * This is a subclass that represents a script function that may be regenerated, 5.18 * for example with specialization based on call site types, or lazily generated. 5.19 @@ -81,8 +81,12 @@ 5.20 /** Token of this function within the source. */ 5.21 private final long token; 5.22 5.23 - /** Allocator map from makeMap() */ 5.24 - private final PropertyMap allocatorMap; 5.25 + /** 5.26 + * Represents the allocation strategy (property map, script object class, and method handle) for when 5.27 + * this function is used as a constructor. Note that majority of functions (those not setting any this.* 5.28 + * properties) will share a single canonical "default strategy" instance. 5.29 + */ 5.30 + private final AllocationStrategy allocationStrategy; 5.31 5.32 /** 5.33 * Opaque object representing parser state at the end of the function. Used when reparsing outer function 5.34 @@ -93,12 +97,6 @@ 5.35 /** Code installer used for all further recompilation/specialization of this ScriptFunction */ 5.36 private transient CodeInstaller<ScriptEnvironment> installer; 5.37 5.38 - /** Name of class where allocator function resides */ 5.39 - private final String allocatorClassName; 5.40 - 5.41 - /** lazily generated allocator */ 5.42 - private transient MethodHandle allocator; 5.43 - 5.44 private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions; 5.45 5.46 /** Id to parent function if one exists */ 5.47 @@ -124,8 +122,7 @@ 5.48 * 5.49 * @param functionNode functionNode that represents this function code 5.50 * @param installer installer for code regeneration versions of this function 5.51 - * @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor 5.52 - * @param allocatorMap allocator map to seed instances with, when constructing 5.53 + * @param allocationDescriptor descriptor for the allocation behavior when this function is used as a constructor 5.54 * @param nestedFunctions nested function map 5.55 * @param externalScopeDepths external scope depths 5.56 * @param internalSymbols internal symbols to method, defined in its scope 5.57 @@ -133,8 +130,7 @@ 5.58 public RecompilableScriptFunctionData( 5.59 final FunctionNode functionNode, 5.60 final CodeInstaller<ScriptEnvironment> installer, 5.61 - final String allocatorClassName, 5.62 - final PropertyMap allocatorMap, 5.63 + final AllocatorDescriptor allocationDescriptor, 5.64 final Map<Integer, RecompilableScriptFunctionData> nestedFunctions, 5.65 final Map<String, Integer> externalScopeDepths, 5.66 final Set<String> internalSymbols) { 5.67 @@ -151,11 +147,10 @@ 5.68 this.endParserState = functionNode.getEndParserState(); 5.69 this.token = tokenFor(functionNode); 5.70 this.installer = installer; 5.71 - this.allocatorClassName = allocatorClassName; 5.72 - this.allocatorMap = allocatorMap; 5.73 - this.nestedFunctions = nestedFunctions; 5.74 - this.externalScopeDepths = externalScopeDepths; 5.75 - this.internalSymbols = new HashSet<>(internalSymbols); 5.76 + this.allocationStrategy = AllocationStrategy.get(allocationDescriptor); 5.77 + this.nestedFunctions = smallMap(nestedFunctions); 5.78 + this.externalScopeDepths = smallMap(externalScopeDepths); 5.79 + this.internalSymbols = smallSet(new HashSet<>(internalSymbols)); 5.80 5.81 for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) { 5.82 assert nfn.getParent() == null; 5.83 @@ -165,6 +160,27 @@ 5.84 createLogger(); 5.85 } 5.86 5.87 + private static <K, V> Map<K, V> smallMap(final Map<K, V> map) { 5.88 + if (map == null || map.isEmpty()) { 5.89 + return Collections.emptyMap(); 5.90 + } else if (map.size() == 1) { 5.91 + final Map.Entry<K, V> entry = map.entrySet().iterator().next(); 5.92 + return Collections.singletonMap(entry.getKey(), entry.getValue()); 5.93 + } else { 5.94 + return map; 5.95 + } 5.96 + } 5.97 + 5.98 + private static <T> Set<T> smallSet(final Set<T> set) { 5.99 + if (set == null || set.isEmpty()) { 5.100 + return Collections.emptySet(); 5.101 + } else if (set.size() == 1) { 5.102 + return Collections.singleton(set.iterator().next()); 5.103 + } else { 5.104 + return set; 5.105 + } 5.106 + } 5.107 + 5.108 @Override 5.109 public DebugLogger getLogger() { 5.110 return log; 5.111 @@ -194,11 +210,7 @@ 5.112 * @return the external symbol table with proto depths 5.113 */ 5.114 public int getExternalSymbolDepth(final String symbolName) { 5.115 - final Map<String, Integer> map = externalScopeDepths; 5.116 - if (map == null) { 5.117 - return -1; 5.118 - } 5.119 - final Integer depth = map.get(symbolName); 5.120 + final Integer depth = externalScopeDepths.get(symbolName); 5.121 if (depth == null) { 5.122 return -1; 5.123 } 5.124 @@ -210,8 +222,7 @@ 5.125 * @return the names of all external symbols this function uses. 5.126 */ 5.127 public Set<String> getExternalSymbolNames() { 5.128 - return externalScopeDepths == null ? Collections.<String>emptySet() : 5.129 - Collections.unmodifiableSet(externalScopeDepths.keySet()); 5.130 + return Collections.unmodifiableSet(externalScopeDepths.keySet()); 5.131 } 5.132 5.133 /** 5.134 @@ -334,25 +345,12 @@ 5.135 5.136 @Override 5.137 PropertyMap getAllocatorMap() { 5.138 - return allocatorMap; 5.139 + return allocationStrategy.getAllocatorMap(); 5.140 } 5.141 5.142 @Override 5.143 ScriptObject allocate(final PropertyMap map) { 5.144 - try { 5.145 - ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try 5.146 - return allocator == null ? null : (ScriptObject)allocator.invokeExact(map); 5.147 - } catch (final RuntimeException | Error e) { 5.148 - throw e; 5.149 - } catch (final Throwable t) { 5.150 - throw new RuntimeException(t); 5.151 - } 5.152 - } 5.153 - 5.154 - private void ensureHasAllocator() throws ClassNotFoundException { 5.155 - if (allocator == null && allocatorClassName != null) { 5.156 - this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class)); 5.157 - } 5.158 + return allocationStrategy.allocate(map); 5.159 } 5.160 5.161 FunctionNode reparse() { 5.162 @@ -756,21 +754,6 @@ 5.163 } 5.164 5.165 /** 5.166 - * Get the uppermost parent, the program, for this data 5.167 - * @return program 5.168 - */ 5.169 - public RecompilableScriptFunctionData getProgram() { 5.170 - RecompilableScriptFunctionData program = this; 5.171 - while (true) { 5.172 - final RecompilableScriptFunctionData p = program.getParent(); 5.173 - if (p == null) { 5.174 - return program; 5.175 - } 5.176 - program = p; 5.177 - } 5.178 - } 5.179 - 5.180 - /** 5.181 * Check whether a certain name is a global symbol, i.e. only exists as defined 5.182 * in outermost scope and not shadowed by being parameter or assignment in inner 5.183 * scopes