src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java

Mon, 08 Sep 2014 18:40:58 +0200

author
attila
date
Mon, 08 Sep 2014 18:40:58 +0200
changeset 994
f5be4bdd0f6e
parent 991
b7a2db4de254
child 1005
2cad9bf911a4
permissions
-rw-r--r--

8057148: Skip nested functions on reparse
Reviewed-by: hannesw, lagergren

lagergren@137 1 /*
attila@963 2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
lagergren@137 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
lagergren@137 4 *
lagergren@137 5 * This code is free software; you can redistribute it and/or modify it
lagergren@137 6 * under the terms of the GNU General Public License version 2 only, as
lagergren@137 7 * published by the Free Software Foundation. Oracle designates this
lagergren@137 8 * particular file as subject to the "Classpath" exception as provided
lagergren@137 9 * by Oracle in the LICENSE file that accompanied this code.
lagergren@137 10 *
lagergren@137 11 * This code is distributed in the hope that it will be useful, but WITHOUT
lagergren@137 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
lagergren@137 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
lagergren@137 14 * version 2 for more details (a copy is included in the LICENSE file that
lagergren@137 15 * accompanied this code).
lagergren@137 16 *
lagergren@137 17 * You should have received a copy of the GNU General Public License version
lagergren@137 18 * 2 along with this work; if not, write to the Free Software Foundation,
lagergren@137 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
lagergren@137 20 *
lagergren@137 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
lagergren@137 22 * or visit www.oracle.com if you need additional information or have any
lagergren@137 23 * questions.
lagergren@137 24 */
lagergren@137 25
lagergren@137 26 package jdk.nashorn.internal.runtime;
lagergren@137 27
lagergren@211 28 import static jdk.nashorn.internal.lookup.Lookup.MH;
lagergren@211 29
attila@963 30 import java.io.IOException;
lagergren@137 31 import java.lang.invoke.MethodHandle;
lagergren@137 32 import java.lang.invoke.MethodHandles;
lagergren@137 33 import java.lang.invoke.MethodType;
attila@963 34 import java.util.Collections;
attila@963 35 import java.util.HashMap;
attila@963 36 import java.util.HashSet;
hannesw@828 37 import java.util.Map;
attila@963 38 import java.util.Set;
attila@963 39 import java.util.TreeMap;
sundar@611 40 import jdk.internal.dynalink.support.NameCodec;
lagergren@137 41 import jdk.nashorn.internal.codegen.Compiler;
attila@963 42 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
lagergren@137 43 import jdk.nashorn.internal.codegen.CompilerConstants;
lagergren@137 44 import jdk.nashorn.internal.codegen.FunctionSignature;
attila@963 45 import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
attila@963 46 import jdk.nashorn.internal.codegen.TypeMap;
lagergren@247 47 import jdk.nashorn.internal.codegen.types.Type;
lagergren@137 48 import jdk.nashorn.internal.ir.FunctionNode;
attila@963 49 import jdk.nashorn.internal.ir.LexicalContext;
attila@963 50 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
attila@963 51 import jdk.nashorn.internal.objects.Global;
attila@963 52 import jdk.nashorn.internal.parser.Parser;
lagergren@137 53 import jdk.nashorn.internal.parser.Token;
lagergren@137 54 import jdk.nashorn.internal.parser.TokenType;
attila@963 55 import jdk.nashorn.internal.runtime.logging.DebugLogger;
attila@963 56 import jdk.nashorn.internal.runtime.logging.Loggable;
attila@963 57 import jdk.nashorn.internal.runtime.logging.Logger;
lagergren@137 58
lagergren@137 59 /**
lagergren@137 60 * This is a subclass that represents a script function that may be regenerated,
lagergren@137 61 * for example with specialization based on call site types, or lazily generated.
lagergren@137 62 * The common denominator is that it can get new invokers during its lifespan,
lagergren@505 63 * unlike {@code FinalScriptFunctionData}
lagergren@137 64 */
attila@963 65 @Logger(name="recompile")
attila@963 66 public final class RecompilableScriptFunctionData extends ScriptFunctionData implements Loggable {
attila@963 67 /** Prefix used for all recompiled script classes */
attila@963 68 public static final String RECOMPILATION_PREFIX = "Recompilation$";
lagergren@137 69
attila@963 70 /** Unique function node id for this function node */
attila@963 71 private final int functionNodeId;
sundar@489 72
attila@963 73 private final String functionName;
hannesw@828 74
hannesw@828 75 /** The line number where this function begins. */
hannesw@828 76 private final int lineNumber;
hannesw@828 77
attila@963 78 /** Source from which FunctionNode was parsed. */
attila@963 79 private transient Source source;
sundar@489 80
sundar@489 81 /** Token of this function within the source. */
sundar@489 82 private final long token;
lagergren@277 83
lagergren@277 84 /** Allocator map from makeMap() */
lagergren@277 85 private final PropertyMap allocatorMap;
lagergren@277 86
attila@994 87 /**
attila@994 88 * Opaque object representing parser state at the end of the function. Used when reparsing outer function
attila@994 89 * to help with skipping parsing inner functions.
attila@994 90 */
attila@994 91 private final Object endParserState;
attila@994 92
lagergren@277 93 /** Code installer used for all further recompilation/specialization of this ScriptFunction */
hannesw@828 94 private transient CodeInstaller<ScriptEnvironment> installer;
lagergren@277 95
lagergren@277 96 /** Name of class where allocator function resides */
lagergren@137 97 private final String allocatorClassName;
lagergren@137 98
lagergren@137 99 /** lazily generated allocator */
hannesw@828 100 private transient MethodHandle allocator;
lagergren@137 101
attila@963 102 private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions;
attila@963 103
attila@963 104 /** Id to parent function if one exists */
attila@963 105 private RecompilableScriptFunctionData parent;
attila@963 106
attila@994 107 /** Copy of the {@link FunctionNode} flags. */
attila@994 108 private final int functionFlags;
attila@963 109
lagergren@137 110 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
lagergren@137 111
attila@963 112 private transient DebugLogger log;
lagergren@277 113
attila@963 114 private final Map<String, Integer> externalScopeDepths;
attila@963 115
attila@963 116 private final Set<String> internalSymbols;
attila@963 117
attila@963 118 private static final int GET_SET_PREFIX_LENGTH = "*et ".length();
lagergren@277 119
hannesw@828 120 private static final long serialVersionUID = 4914839316174633726L;
hannesw@828 121
lagergren@277 122 /**
lagergren@137 123 * Constructor - public as scripts use it
lagergren@137 124 *
attila@963 125 * @param functionNode functionNode that represents this function code
attila@963 126 * @param installer installer for code regeneration versions of this function
attila@963 127 * @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor
attila@963 128 * @param allocatorMap allocator map to seed instances with, when constructing
attila@963 129 * @param nestedFunctions nested function map
attila@963 130 * @param externalScopeDepths external scope depths
attila@963 131 * @param internalSymbols internal symbols to method, defined in its scope
lagergren@137 132 */
attila@963 133 public RecompilableScriptFunctionData(
attila@963 134 final FunctionNode functionNode,
attila@963 135 final CodeInstaller<ScriptEnvironment> installer,
attila@963 136 final String allocatorClassName,
attila@963 137 final PropertyMap allocatorMap,
attila@963 138 final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
attila@963 139 final Map<String, Integer> externalScopeDepths,
attila@963 140 final Set<String> internalSymbols) {
attila@963 141
sundar@611 142 super(functionName(functionNode),
attila@963 143 Math.min(functionNode.getParameters().size(), MAX_ARITY),
attila@994 144 getDataFlags(functionNode));
attila@963 145
attila@963 146 this.functionName = functionNode.getName();
attila@963 147 this.lineNumber = functionNode.getLineNumber();
attila@994 148 this.functionFlags = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0);
attila@963 149 this.functionNodeId = functionNode.getId();
attila@963 150 this.source = functionNode.getSource();
attila@994 151 this.endParserState = functionNode.getEndParserState();
attila@963 152 this.token = tokenFor(functionNode);
attila@963 153 this.installer = installer;
attila@963 154 this.allocatorClassName = allocatorClassName;
attila@963 155 this.allocatorMap = allocatorMap;
attila@963 156 this.nestedFunctions = nestedFunctions;
attila@963 157 this.externalScopeDepths = externalScopeDepths;
attila@963 158 this.internalSymbols = new HashSet<>(internalSymbols);
attila@963 159
attila@963 160 for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) {
attila@963 161 assert nfn.getParent() == null;
attila@963 162 nfn.setParent(this);
hannesw@828 163 }
attila@963 164
attila@963 165 createLogger();
attila@963 166 }
attila@963 167
attila@963 168 @Override
attila@963 169 public DebugLogger getLogger() {
attila@963 170 return log;
attila@963 171 }
attila@963 172
attila@963 173 @Override
attila@963 174 public DebugLogger initLogger(final Context ctxt) {
attila@963 175 return ctxt.getLogger(this.getClass());
attila@963 176 }
attila@963 177
attila@963 178 /**
attila@963 179 * Check if a symbol is internally defined in a function. For example
attila@963 180 * if "undefined" is internally defined in the outermost program function,
attila@963 181 * it has not been reassigned or overridden and can be optimized
attila@963 182 *
attila@963 183 * @param symbolName symbol name
attila@963 184 * @return true if symbol is internal to this ScriptFunction
attila@963 185 */
attila@963 186
attila@963 187 public boolean hasInternalSymbol(final String symbolName) {
attila@963 188 return internalSymbols.contains(symbolName);
attila@963 189 }
attila@963 190
attila@963 191 /**
attila@963 192 * Return the external symbol table
attila@963 193 * @param symbolName symbol name
attila@963 194 * @return the external symbol table with proto depths
attila@963 195 */
attila@963 196 public int getExternalSymbolDepth(final String symbolName) {
attila@963 197 final Map<String, Integer> map = externalScopeDepths;
attila@963 198 if (map == null) {
attila@963 199 return -1;
attila@963 200 }
attila@963 201 final Integer depth = map.get(symbolName);
attila@963 202 if (depth == null) {
attila@963 203 return -1;
attila@963 204 }
attila@963 205 return depth;
attila@963 206 }
attila@963 207
attila@963 208 /**
attila@994 209 * Returns the names of all external symbols this function uses.
attila@994 210 * @return the names of all external symbols this function uses.
attila@994 211 */
attila@994 212 public Set<String> getExternalSymbolNames() {
attila@994 213 return externalScopeDepths == null ? Collections.<String>emptySet() :
attila@994 214 Collections.unmodifiableSet(externalScopeDepths.keySet());
attila@994 215 }
attila@994 216
attila@994 217 /**
attila@994 218 * Returns the opaque object representing the parser state at the end of this function's body, used to
attila@994 219 * skip parsing this function when reparsing its containing outer function.
attila@994 220 * @return the object representing the end parser state
attila@994 221 */
attila@994 222 public Object getEndParserState() {
attila@994 223 return endParserState;
attila@994 224 }
attila@994 225
attila@994 226 /**
attila@963 227 * Get the parent of this RecompilableScriptFunctionData. If we are
attila@963 228 * a nested function, we have a parent. Note that "null" return value
attila@963 229 * can also mean that we have a parent but it is unknown, so this can
attila@963 230 * only be used for conservative assumptions.
attila@963 231 * @return parent data, or null if non exists and also null IF UNKNOWN.
attila@963 232 */
attila@963 233 public RecompilableScriptFunctionData getParent() {
attila@963 234 return parent;
attila@963 235 }
attila@963 236
attila@963 237 void setParent(final RecompilableScriptFunctionData parent) {
attila@963 238 this.parent = parent;
lagergren@137 239 }
lagergren@137 240
lagergren@137 241 @Override
lagergren@137 242 String toSource() {
lagergren@137 243 if (source != null && token != 0) {
lagergren@137 244 return source.getString(Token.descPosition(token), Token.descLength(token));
lagergren@137 245 }
lagergren@137 246
lagergren@137 247 return "function " + (name == null ? "" : name) + "() { [native code] }";
lagergren@137 248 }
lagergren@137 249
attila@963 250 /**
attila@963 251 * Initialize transient fields on deserialized instances
attila@963 252 *
attila@963 253 * @param src source
attila@963 254 * @param inst code installer
attila@963 255 */
attila@963 256 public void initTransients(final Source src, final CodeInstaller<ScriptEnvironment> inst) {
attila@963 257 if (this.source == null && this.installer == null) {
attila@963 258 this.source = src;
attila@963 259 this.installer = inst;
attila@963 260 } else if (this.source != src || this.installer != inst) {
attila@963 261 // Existing values must be same as those passed as parameters
attila@963 262 throw new IllegalArgumentException();
hannesw@828 263 }
hannesw@828 264 }
hannesw@828 265
lagergren@137 266 @Override
lagergren@137 267 public String toString() {
attila@963 268 return super.toString() + '@' + functionNodeId;
attila@963 269 }
attila@963 270
attila@963 271 @Override
attila@963 272 public String toStringVerbose() {
lagergren@137 273 final StringBuilder sb = new StringBuilder();
lagergren@137 274
attila@963 275 sb.append("fnId=").append(functionNodeId).append(' ');
attila@963 276
lagergren@137 277 if (source != null) {
attila@963 278 sb.append(source.getName())
attila@963 279 .append(':')
attila@963 280 .append(lineNumber)
attila@963 281 .append(' ');
lagergren@137 282 }
lagergren@137 283
lagergren@137 284 return sb.toString() + super.toString();
lagergren@137 285 }
lagergren@137 286
attila@963 287 @Override
attila@963 288 public String getFunctionName() {
attila@963 289 return functionName;
attila@963 290 }
attila@963 291
attila@963 292 @Override
attila@963 293 public boolean inDynamicContext() {
attila@994 294 return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT);
attila@963 295 }
attila@963 296
sundar@611 297 private static String functionName(final FunctionNode fn) {
sundar@611 298 if (fn.isAnonymous()) {
sundar@611 299 return "";
sundar@611 300 }
attila@963 301 final FunctionNode.Kind kind = fn.getKind();
attila@963 302 if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
attila@963 303 final String name = NameCodec.decode(fn.getIdent().getName());
attila@963 304 return name.substring(GET_SET_PREFIX_LENGTH);
attila@963 305 }
attila@963 306 return fn.getIdent().getName();
sundar@611 307 }
sundar@611 308
lagergren@137 309 private static long tokenFor(final FunctionNode fn) {
attila@963 310 final int position = Token.descPosition(fn.getFirstToken());
attila@963 311 final long lastToken = Token.withDelimiter(fn.getLastToken());
attila@963 312 // EOL uses length field to store the line number
attila@963 313 final int length = Token.descPosition(lastToken) - position + (Token.descType(lastToken) == TokenType.EOL ? 0 : Token.descLength(lastToken));
lagergren@137 314
lagergren@137 315 return Token.toDesc(TokenType.FUNCTION, position, length);
lagergren@137 316 }
lagergren@137 317
attila@994 318 private static int getDataFlags(final FunctionNode functionNode) {
hannesw@769 319 int flags = IS_CONSTRUCTOR;
hannesw@769 320 if (functionNode.isStrict()) {
hannesw@769 321 flags |= IS_STRICT;
hannesw@769 322 }
hannesw@769 323 if (functionNode.needsCallee()) {
hannesw@769 324 flags |= NEEDS_CALLEE;
hannesw@769 325 }
hannesw@769 326 if (functionNode.usesThis() || functionNode.hasEval()) {
hannesw@769 327 flags |= USES_THIS;
hannesw@769 328 }
attila@963 329 if (functionNode.isVarArg()) {
attila@963 330 flags |= IS_VARIABLE_ARITY;
attila@963 331 }
hannesw@769 332 return flags;
hannesw@769 333 }
hannesw@769 334
lagergren@137 335 @Override
attila@963 336 PropertyMap getAllocatorMap() {
attila@963 337 return allocatorMap;
attila@963 338 }
attila@963 339
attila@963 340 @Override
hannesw@766 341 ScriptObject allocate(final PropertyMap map) {
lagergren@137 342 try {
lagergren@137 343 ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try
hannesw@766 344 return allocator == null ? null : (ScriptObject)allocator.invokeExact(map);
lagergren@137 345 } catch (final RuntimeException | Error e) {
lagergren@137 346 throw e;
lagergren@137 347 } catch (final Throwable t) {
lagergren@137 348 throw new RuntimeException(t);
lagergren@137 349 }
lagergren@137 350 }
lagergren@137 351
lagergren@137 352 private void ensureHasAllocator() throws ClassNotFoundException {
lagergren@137 353 if (allocator == null && allocatorClassName != null) {
lagergren@211 354 this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
lagergren@137 355 }
lagergren@137 356 }
lagergren@137 357
attila@963 358 FunctionNode reparse() {
attila@963 359 // NOTE: If we aren't recompiling the top-level program, we decrease functionNodeId 'cause we'll have a synthetic program node
attila@963 360 final int descPosition = Token.descPosition(token);
attila@963 361 final Context context = Context.getContextTrusted();
attila@963 362 final Parser parser = new Parser(
attila@963 363 context.getEnv(),
attila@963 364 source,
attila@963 365 new Context.ThrowErrorManager(),
attila@963 366 isStrict(),
attila@963 367 lineNumber - 1,
attila@963 368 context.getLogger(Parser.class)); // source starts at line 0, so even though lineNumber is the correct declaration line, back off one to make it exclusive
attila@963 369
attila@994 370 if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) {
attila@963 371 parser.setFunctionName(functionName);
attila@963 372 }
attila@994 373 parser.setReparsedFunction(this);
attila@963 374
attila@994 375 final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition,
attila@994 376 Token.descLength(token), true);
attila@994 377 // Parser generates a program AST even if we're recompiling a single function, so when we are only
attila@994 378 // recompiling a single function, extract it from the program.
attila@994 379 return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
attila@994 380 }
attila@994 381
attila@994 382 private boolean getFunctionFlag(final int flag) {
attila@994 383 return (functionFlags & flag) != 0;
attila@994 384 }
attila@994 385
attila@994 386 private boolean isProgram() {
attila@994 387 return getFunctionFlag(FunctionNode.IS_PROGRAM);
hannesw@766 388 }
hannesw@766 389
attila@963 390 TypeMap typeMap(final MethodType fnCallSiteType) {
attila@963 391 if (fnCallSiteType == null) {
attila@963 392 return null;
attila@963 393 }
attila@963 394
attila@963 395 if (CompiledFunction.isVarArgsType(fnCallSiteType)) {
attila@963 396 return null;
attila@963 397 }
attila@963 398
attila@963 399 return new TypeMap(functionNodeId, explicitParams(fnCallSiteType), needsCallee());
attila@963 400 }
attila@963 401
attila@963 402 private static ScriptObject newLocals(final ScriptObject runtimeScope) {
attila@963 403 final ScriptObject locals = Global.newEmptyInstance();
attila@963 404 locals.setProto(runtimeScope);
attila@963 405 return locals;
attila@963 406 }
attila@963 407
attila@963 408 private Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
attila@963 409 return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null);
attila@963 410 }
attila@963 411
attila@963 412 Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType,
attila@963 413 final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints,
attila@963 414 final int[] continuationEntryPoints) {
attila@963 415 final TypeMap typeMap = typeMap(actualCallSiteType);
attila@963 416 final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
attila@963 417 final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes);
attila@963 418 final Context context = Context.getContextTrusted();
attila@963 419 return new Compiler(
attila@963 420 context,
attila@963 421 context.getEnv(),
attila@963 422 installer,
attila@963 423 functionNode.getSource(), // source
hannesw@991 424 context.getErrorManager(),
attila@963 425 isStrict() | functionNode.isStrict(), // is strict
attila@963 426 true, // is on demand
attila@963 427 this, // compiledFunction, i.e. this RecompilableScriptFunctionData
attila@963 428 typeMap, // type map
attila@963 429 getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points
attila@963 430 typeInformationFile,
attila@963 431 continuationEntryPoints, // continuation entry points
attila@963 432 runtimeScope); // runtime scope
attila@963 433 }
attila@963 434
attila@963 435 /**
attila@963 436 * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to
attila@963 437 * load invalidated program points map from the persistent type info cache.
attila@963 438 * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function
attila@963 439 * doesn't have it.
attila@963 440 * @param typeInformationFile the object describing the location of the persisted type information.
attila@963 441 * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if
attila@963 442 * neither an existing map or a persistent cached type info is available.
attila@963 443 */
attila@963 444 private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints(
attila@963 445 final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) {
attila@963 446 if(invalidatedProgramPoints != null) {
attila@963 447 return invalidatedProgramPoints;
attila@963 448 }
attila@963 449 final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile);
attila@963 450 return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>();
attila@963 451 }
attila@963 452
attila@963 453 private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) {
attila@963 454 // We're creating an empty script object for holding local variables. AssignSymbols will populate it with
attila@963 455 // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
attila@963 456 // CompilationEnvironment#declareLocalSymbol()).
attila@963 457
attila@963 458 if (log.isEnabled()) {
attila@963 459 log.info("Type specialization of '", functionName, "' signature: ", actualCallSiteType);
attila@963 460 }
attila@963 461
attila@963 462 final boolean persistentCache = usePersistentCodeCache() && persist;
attila@963 463 String cacheKey = null;
attila@963 464 if (persistentCache) {
attila@963 465 final TypeMap typeMap = typeMap(actualCallSiteType);
attila@963 466 final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
attila@963 467 cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
attila@963 468 final StoredScript script = installer.loadScript(source, cacheKey);
attila@963 469
attila@963 470 if (script != null) {
attila@963 471 Compiler.updateCompilationId(script.getCompilationId());
attila@963 472 return install(script);
attila@963 473 }
attila@963 474 }
attila@963 475
attila@963 476 final FunctionNode fn = reparse();
attila@963 477 final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
attila@963 478 final FunctionNode compiledFn = compiler.compile(fn, CompilationPhases.COMPILE_ALL);
attila@963 479
attila@963 480 if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
attila@963 481 compiler.persistClassInfo(cacheKey, compiledFn);
attila@963 482 }
attila@963 483 return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());
attila@963 484 }
attila@963 485
attila@963 486
attila@963 487 /**
attila@963 488 * Install this script using the given {@code installer}.
attila@963 489 *
attila@963 490 * @param script the compiled script
attila@963 491 * @return the function initializer
attila@963 492 */
attila@963 493 private FunctionInitializer install(final StoredScript script) {
attila@963 494
attila@963 495 final Map<String, Class<?>> installedClasses = new HashMap<>();
attila@963 496 final String mainClassName = script.getMainClassName();
attila@963 497 final byte[] mainClassBytes = script.getClassBytes().get(mainClassName);
attila@963 498
attila@963 499 final Class<?> mainClass = installer.install(mainClassName, mainClassBytes);
attila@963 500
attila@963 501 installedClasses.put(mainClassName, mainClass);
attila@963 502
attila@963 503 for (final Map.Entry<String, byte[]> entry : script.getClassBytes().entrySet()) {
attila@963 504 final String className = entry.getKey();
attila@963 505 final byte[] code = entry.getValue();
attila@963 506
attila@963 507 if (className.equals(mainClassName)) {
attila@963 508 continue;
attila@963 509 }
attila@963 510
attila@963 511 installedClasses.put(className, installer.install(className, code));
attila@963 512 }
attila@963 513
attila@963 514 final Map<Integer, FunctionInitializer> initializers = script.getInitializers();
attila@963 515 assert initializers != null;
attila@963 516 assert initializers.size() == 1;
attila@963 517 final FunctionInitializer initializer = initializers.values().iterator().next();
attila@963 518
attila@963 519 final Object[] constants = script.getConstants();
attila@963 520 for (int i = 0; i < constants.length; i++) {
attila@963 521 if (constants[i] instanceof RecompilableScriptFunctionData) {
attila@963 522 // replace deserialized function data with the ones we already have
attila@963 523 constants[i] = getScriptFunctionData(((RecompilableScriptFunctionData) constants[i]).getFunctionNodeId());
attila@963 524 }
attila@963 525 }
attila@963 526
attila@963 527 installer.initialize(installedClasses.values(), source, constants);
attila@963 528 initializer.setCode(installedClasses.get(initializer.getClassName()));
attila@963 529 return initializer;
attila@963 530 }
attila@963 531
attila@963 532 boolean usePersistentCodeCache() {
attila@963 533 final ScriptEnvironment env = installer.getOwner();
attila@963 534 return env._persistent_cache && env._optimistic_types;
attila@963 535 }
attila@963 536
attila@963 537 private MethodType explicitParams(final MethodType callSiteType) {
attila@963 538 if (CompiledFunction.isVarArgsType(callSiteType)) {
attila@963 539 return null;
attila@963 540 }
attila@963 541
attila@963 542 final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type
attila@963 543 final int callSiteParamCount = noCalleeThisType.parameterCount();
attila@963 544
attila@963 545 // Widen parameters of reference types to Object as we currently don't care for specialization among reference
attila@963 546 // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object)
attila@963 547 final Class<?>[] paramTypes = noCalleeThisType.parameterArray();
attila@963 548 boolean changed = false;
attila@963 549 for (int i = 0; i < paramTypes.length; ++i) {
attila@963 550 final Class<?> paramType = paramTypes[i];
attila@963 551 if (!(paramType.isPrimitive() || paramType == Object.class)) {
attila@963 552 paramTypes[i] = Object.class;
attila@963 553 changed = true;
attila@963 554 }
attila@963 555 }
attila@963 556 final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType;
attila@963 557
attila@963 558 if (callSiteParamCount < getArity()) {
attila@963 559 return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class));
attila@963 560 }
attila@963 561 return generalized;
attila@963 562 }
attila@963 563
attila@963 564 private FunctionNode extractFunctionFromScript(final FunctionNode script) {
attila@963 565 final Set<FunctionNode> fns = new HashSet<>();
attila@963 566 script.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
attila@963 567 @Override
attila@963 568 public boolean enterFunctionNode(final FunctionNode fn) {
attila@963 569 fns.add(fn);
attila@963 570 return false;
attila@963 571 }
attila@963 572 });
attila@963 573 assert fns.size() == 1 : "got back more than one method in recompilation";
attila@963 574 final FunctionNode f = fns.iterator().next();
attila@963 575 assert f.getId() == functionNodeId;
attila@994 576 if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) {
attila@963 577 return f.clearFlag(null, FunctionNode.IS_DECLARED);
attila@963 578 }
attila@963 579 return f;
attila@963 580 }
attila@963 581
attila@963 582 MethodHandle lookup(final FunctionInitializer fnInit) {
attila@963 583 final MethodType type = fnInit.getMethodType();
attila@963 584 return lookupCodeMethod(fnInit.getCode(), type);
attila@963 585 }
attila@963 586
attila@963 587 MethodHandle lookup(final FunctionNode fn) {
attila@963 588 final MethodType type = new FunctionSignature(fn).getMethodType();
attila@963 589 return lookupCodeMethod(fn.getCompileUnit().getCode(), type);
attila@963 590 }
attila@963 591
attila@963 592 MethodHandle lookupCodeMethod(final Class<?> code, final MethodType targetType) {
attila@963 593 log.info("Looking up ", DebugLogger.quote(name), " type=", targetType);
attila@963 594 return MH.findStatic(LOOKUP, code, functionName, targetType);
attila@963 595 }
attila@963 596
attila@963 597 /**
attila@963 598 * Initializes this function data with the eagerly generated version of the code. This method can only be invoked
attila@963 599 * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it
attila@963 600 * externally will result in an exception.
attila@963 601 */
attila@963 602 public void initializeCode(final FunctionInitializer initializer) {
attila@963 603 // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit.
attila@963 604 if(!code.isEmpty()) {
attila@963 605 throw new IllegalStateException(name);
attila@963 606 }
attila@963 607 addCode(lookup(initializer), null, null, initializer.getFlags());
attila@963 608 }
attila@963 609
attila@963 610 private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints,
attila@963 611 final MethodType callSiteType, final int fnFlags) {
attila@963 612 final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags);
attila@963 613 code.add(cfn);
attila@963 614 return cfn;
attila@963 615 }
attila@963 616
attila@963 617 /**
attila@963 618 * Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site
attila@963 619 * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end
attila@963 620 * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of
attila@963 621 * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups
attila@963 622 * for the same specialization, so we must adapt the handle to the expected type.
attila@963 623 * @param fnInit the function
attila@963 624 * @param callSiteType the call site type
attila@963 625 * @return the compiled function object, with its type matching that of the call site type.
attila@963 626 */
attila@963 627 private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) {
attila@963 628 if (isVariableArity()) {
attila@963 629 return addCode(lookup(fnInit), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
attila@963 630 }
attila@963 631
attila@963 632 final MethodHandle handle = lookup(fnInit);
attila@963 633 final MethodType fromType = handle.type();
attila@963 634 MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1);
attila@963 635 toType = toType.changeReturnType(fromType.returnType());
attila@963 636
attila@963 637 final int toCount = toType.parameterCount();
attila@963 638 final int fromCount = fromType.parameterCount();
attila@963 639 final int minCount = Math.min(fromCount, toCount);
attila@963 640 for(int i = 0; i < minCount; ++i) {
attila@963 641 final Class<?> fromParam = fromType.parameterType(i);
attila@963 642 final Class<?> toParam = toType.parameterType(i);
attila@963 643 // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it
attila@963 644 // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically
attila@963 645 // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there).
attila@963 646 if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) {
attila@963 647 assert fromParam.isAssignableFrom(toParam);
attila@963 648 toType = toType.changeParameterType(i, fromParam);
attila@963 649 }
attila@963 650 }
attila@963 651 if (fromCount > toCount) {
attila@963 652 toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount));
attila@963 653 } else if (fromCount < toCount) {
attila@963 654 toType = toType.dropParameterTypes(fromCount, toCount);
attila@963 655 }
attila@963 656
attila@963 657 return addCode(lookup(fnInit).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
attila@963 658 }
attila@963 659
hannesw@769 660
hannesw@769 661 @Override
attila@963 662 synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope) {
attila@963 663 CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope);
attila@963 664 if (existingBest == null) {
attila@963 665 existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType);
hannesw@769 666 }
hannesw@828 667
attila@963 668 assert existingBest != null;
attila@963 669 //we are calling a vararg method with real args
attila@963 670 boolean applyToCall = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType);
attila@963 671
attila@963 672 //if the best one is an apply to call, it has to match the callsite exactly
attila@963 673 //or we need to regenerate
attila@963 674 if (existingBest.isApplyToCall()) {
attila@963 675 final CompiledFunction best = lookupExactApplyToCall(callSiteType);
attila@963 676 if (best != null) {
attila@963 677 return best;
attila@963 678 }
attila@963 679 applyToCall = true;
attila@963 680 }
attila@963 681
attila@963 682 if (applyToCall) {
attila@963 683 final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false);
attila@963 684 if ((fnInit.getFlags() & FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION) != 0) { //did the specialization work
attila@963 685 existingBest = addCode(fnInit, callSiteType);
attila@963 686 }
attila@963 687 }
attila@963 688
attila@963 689 return existingBest;
attila@963 690 }
attila@963 691
attila@963 692 @Override
attila@963 693 boolean isRecompilable() {
attila@963 694 return true;
attila@963 695 }
attila@963 696
attila@963 697 @Override
attila@963 698 public boolean needsCallee() {
attila@994 699 return getFunctionFlag(FunctionNode.NEEDS_CALLEE);
attila@994 700 }
attila@994 701
attila@994 702 /**
attila@994 703 * Returns the {@link FunctionNode} flags associated with this function data.
attila@994 704 * @return the {@link FunctionNode} flags associated with this function data.
attila@994 705 */
attila@994 706 public int getFunctionFlags() {
attila@994 707 return functionFlags;
attila@963 708 }
attila@963 709
attila@963 710 @Override
attila@963 711 MethodType getGenericType() {
attila@963 712 // 2 is for (callee, this)
attila@963 713 if (isVariableArity()) {
attila@963 714 return MethodType.genericMethodType(2, true);
attila@963 715 }
attila@963 716 return MethodType.genericMethodType(2 + getArity());
attila@963 717 }
attila@963 718
attila@963 719 /**
attila@963 720 * Return the function node id.
attila@963 721 * @return the function node id
attila@963 722 */
attila@963 723 public int getFunctionNodeId() {
attila@963 724 return functionNodeId;
attila@963 725 }
attila@963 726
attila@963 727 public Source getSource() {
attila@963 728 return source;
attila@963 729 }
attila@963 730
attila@963 731 /**
attila@963 732 * Return a script function data based on a function id, either this function if
attila@963 733 * the id matches or a nested function based on functionId. This goes down into
attila@963 734 * nested functions until all leaves are exhausted.
attila@963 735 *
attila@963 736 * @param functionId function id
attila@963 737 * @return script function data or null if invalid id
attila@963 738 */
attila@963 739 public RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
attila@963 740 if (functionId == functionNodeId) {
attila@963 741 return this;
attila@963 742 }
attila@963 743 RecompilableScriptFunctionData data;
attila@963 744
attila@963 745 data = nestedFunctions == null ? null : nestedFunctions.get(functionId);
attila@963 746 if (data != null) {
attila@963 747 return data;
attila@963 748 }
attila@963 749 for (final RecompilableScriptFunctionData ndata : nestedFunctions.values()) {
attila@963 750 data = ndata.getScriptFunctionData(functionId);
attila@963 751 if (data != null) {
attila@963 752 return data;
attila@963 753 }
attila@963 754 }
attila@963 755 return null;
attila@963 756 }
attila@963 757
attila@963 758 /**
attila@963 759 * Get the uppermost parent, the program, for this data
attila@963 760 * @return program
attila@963 761 */
attila@963 762 public RecompilableScriptFunctionData getProgram() {
attila@963 763 RecompilableScriptFunctionData program = this;
attila@963 764 while (true) {
attila@963 765 final RecompilableScriptFunctionData p = program.getParent();
attila@963 766 if (p == null) {
attila@963 767 return program;
attila@963 768 }
attila@963 769 program = p;
hannesw@828 770 }
hannesw@769 771 }
hannesw@769 772
attila@963 773 /**
attila@963 774 * Check whether a certain name is a global symbol, i.e. only exists as defined
attila@963 775 * in outermost scope and not shadowed by being parameter or assignment in inner
attila@963 776 * scopes
attila@963 777 *
attila@963 778 * @param functionNode function node to check
attila@963 779 * @param symbolName symbol name
attila@963 780 * @return true if global symbol
attila@963 781 */
attila@963 782 public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) {
attila@963 783 RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId());
attila@963 784 assert data != null;
lagergren@137 785
attila@963 786 do {
attila@963 787 if (data.hasInternalSymbol(symbolName)) {
lagergren@277 788 return false;
lagergren@277 789 }
attila@963 790 data = data.getParent();
attila@963 791 } while(data != null);
attila@963 792
lagergren@277 793 return true;
lagergren@277 794 }
lagergren@277 795
attila@963 796 private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
attila@963 797 in.defaultReadObject();
attila@963 798 createLogger();
lagergren@277 799 }
lagergren@277 800
attila@963 801 private void createLogger() {
attila@963 802 log = initLogger(Context.getContextTrusted());
lagergren@277 803 }
lagergren@137 804 }

mercurial