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

Wed, 03 Jun 2015 18:08:57 +0200

author
hannesw
date
Wed, 03 Jun 2015 18:08:57 +0200
changeset 1396
d5a9705a27b1
parent 1342
10e350c05d09
child 1490
d85f981c8cf8
child 1506
89c8dd086d7b
permissions
-rw-r--r--

8066237: Fuzzing bug: Parser error on optimistic recompilation
Reviewed-by: lagergren, attila

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;
attila@963 29 import java.io.IOException;
lagergren@137 30 import java.lang.invoke.MethodHandle;
lagergren@137 31 import java.lang.invoke.MethodHandles;
lagergren@137 32 import java.lang.invoke.MethodType;
lagergren@1028 33 import java.util.Collection;
attila@963 34 import java.util.Collections;
attila@963 35 import java.util.HashSet;
hannesw@828 36 import java.util.Map;
attila@963 37 import java.util.Set;
attila@963 38 import java.util.TreeMap;
sundar@611 39 import jdk.internal.dynalink.support.NameCodec;
lagergren@137 40 import jdk.nashorn.internal.codegen.Compiler;
attila@963 41 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
lagergren@137 42 import jdk.nashorn.internal.codegen.CompilerConstants;
lagergren@137 43 import jdk.nashorn.internal.codegen.FunctionSignature;
attila@1064 44 import jdk.nashorn.internal.codegen.Namespace;
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 * This is a subclass that represents a script function that may be regenerated,
lagergren@137 60 * for example with specialization based on call site types, or lazily generated.
lagergren@137 61 * The common denominator is that it can get new invokers during its lifespan,
lagergren@505 62 * unlike {@code FinalScriptFunctionData}
lagergren@137 63 */
attila@963 64 @Logger(name="recompile")
attila@963 65 public final class RecompilableScriptFunctionData extends ScriptFunctionData implements Loggable {
attila@963 66 /** Prefix used for all recompiled script classes */
attila@963 67 public static final String RECOMPILATION_PREFIX = "Recompilation$";
lagergren@137 68
attila@963 69 /** Unique function node id for this function node */
attila@963 70 private final int functionNodeId;
sundar@489 71
attila@963 72 private final String functionName;
hannesw@828 73
hannesw@828 74 /** The line number where this function begins. */
hannesw@828 75 private final int lineNumber;
hannesw@828 76
attila@963 77 /** Source from which FunctionNode was parsed. */
attila@963 78 private transient Source source;
sundar@489 79
attila@1064 80 /** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */
attila@1064 81 private final byte[] serializedAst;
attila@1064 82
sundar@489 83 /** Token of this function within the source. */
sundar@489 84 private final long token;
lagergren@277 85
attila@1005 86 /**
attila@1005 87 * Represents the allocation strategy (property map, script object class, and method handle) for when
attila@1005 88 * this function is used as a constructor. Note that majority of functions (those not setting any this.*
attila@1005 89 * properties) will share a single canonical "default strategy" instance.
attila@1005 90 */
attila@1005 91 private final AllocationStrategy allocationStrategy;
lagergren@277 92
attila@994 93 /**
attila@994 94 * Opaque object representing parser state at the end of the function. Used when reparsing outer function
attila@994 95 * to help with skipping parsing inner functions.
attila@994 96 */
attila@994 97 private final Object endParserState;
attila@994 98
lagergren@277 99 /** Code installer used for all further recompilation/specialization of this ScriptFunction */
hannesw@828 100 private transient CodeInstaller<ScriptEnvironment> installer;
lagergren@277 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
hannesw@1249 127 * @param allocationStrategy strategy for the allocation behavior when this function is used as a constructor
attila@963 128 * @param nestedFunctions nested function map
attila@963 129 * @param externalScopeDepths external scope depths
attila@963 130 * @param internalSymbols internal symbols to method, defined in its scope
attila@1064 131 * @param serializedAst a serialized AST representation. Normally only used for split functions.
lagergren@137 132 */
attila@963 133 public RecompilableScriptFunctionData(
attila@963 134 final FunctionNode functionNode,
attila@963 135 final CodeInstaller<ScriptEnvironment> installer,
hannesw@1249 136 final AllocationStrategy allocationStrategy,
attila@963 137 final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
attila@963 138 final Map<String, Integer> externalScopeDepths,
attila@1064 139 final Set<String> internalSymbols,
attila@1064 140 final byte[] serializedAst) {
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;
hannesw@1249 154 this.allocationStrategy = allocationStrategy;
attila@1005 155 this.nestedFunctions = smallMap(nestedFunctions);
attila@1005 156 this.externalScopeDepths = smallMap(externalScopeDepths);
attila@1005 157 this.internalSymbols = smallSet(new HashSet<>(internalSymbols));
attila@963 158
attila@963 159 for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) {
attila@963 160 assert nfn.getParent() == null;
attila@963 161 nfn.setParent(this);
hannesw@828 162 }
attila@963 163
attila@1064 164 this.serializedAst = serializedAst;
attila@963 165 createLogger();
attila@963 166 }
attila@963 167
attila@1005 168 private static <K, V> Map<K, V> smallMap(final Map<K, V> map) {
attila@1005 169 if (map == null || map.isEmpty()) {
attila@1005 170 return Collections.emptyMap();
attila@1005 171 } else if (map.size() == 1) {
attila@1005 172 final Map.Entry<K, V> entry = map.entrySet().iterator().next();
attila@1005 173 return Collections.singletonMap(entry.getKey(), entry.getValue());
attila@1005 174 } else {
attila@1005 175 return map;
attila@1005 176 }
attila@1005 177 }
attila@1005 178
attila@1005 179 private static <T> Set<T> smallSet(final Set<T> set) {
attila@1005 180 if (set == null || set.isEmpty()) {
attila@1005 181 return Collections.emptySet();
attila@1005 182 } else if (set.size() == 1) {
attila@1005 183 return Collections.singleton(set.iterator().next());
attila@1005 184 } else {
attila@1005 185 return set;
attila@1005 186 }
attila@1005 187 }
attila@1005 188
attila@963 189 @Override
attila@963 190 public DebugLogger getLogger() {
attila@963 191 return log;
attila@963 192 }
attila@963 193
attila@963 194 @Override
attila@963 195 public DebugLogger initLogger(final Context ctxt) {
attila@963 196 return ctxt.getLogger(this.getClass());
attila@963 197 }
attila@963 198
attila@963 199 /**
attila@963 200 * Check if a symbol is internally defined in a function. For example
attila@963 201 * if "undefined" is internally defined in the outermost program function,
attila@963 202 * it has not been reassigned or overridden and can be optimized
attila@963 203 *
attila@963 204 * @param symbolName symbol name
attila@963 205 * @return true if symbol is internal to this ScriptFunction
attila@963 206 */
attila@963 207
attila@963 208 public boolean hasInternalSymbol(final String symbolName) {
attila@963 209 return internalSymbols.contains(symbolName);
attila@963 210 }
attila@963 211
attila@963 212 /**
attila@963 213 * Return the external symbol table
attila@963 214 * @param symbolName symbol name
attila@963 215 * @return the external symbol table with proto depths
attila@963 216 */
attila@963 217 public int getExternalSymbolDepth(final String symbolName) {
attila@1005 218 final Integer depth = externalScopeDepths.get(symbolName);
attila@1064 219 return depth == null ? -1 : depth;
attila@963 220 }
attila@963 221
attila@963 222 /**
attila@994 223 * Returns the names of all external symbols this function uses.
attila@994 224 * @return the names of all external symbols this function uses.
attila@994 225 */
attila@994 226 public Set<String> getExternalSymbolNames() {
attila@1005 227 return Collections.unmodifiableSet(externalScopeDepths.keySet());
attila@994 228 }
attila@994 229
attila@994 230 /**
attila@994 231 * Returns the opaque object representing the parser state at the end of this function's body, used to
attila@994 232 * skip parsing this function when reparsing its containing outer function.
attila@994 233 * @return the object representing the end parser state
attila@994 234 */
attila@994 235 public Object getEndParserState() {
attila@994 236 return endParserState;
attila@994 237 }
attila@994 238
attila@994 239 /**
attila@963 240 * Get the parent of this RecompilableScriptFunctionData. If we are
attila@963 241 * a nested function, we have a parent. Note that "null" return value
attila@963 242 * can also mean that we have a parent but it is unknown, so this can
attila@963 243 * only be used for conservative assumptions.
attila@963 244 * @return parent data, or null if non exists and also null IF UNKNOWN.
attila@963 245 */
attila@963 246 public RecompilableScriptFunctionData getParent() {
attila@963 247 return parent;
attila@963 248 }
attila@963 249
attila@963 250 void setParent(final RecompilableScriptFunctionData parent) {
attila@963 251 this.parent = parent;
lagergren@137 252 }
lagergren@137 253
lagergren@137 254 @Override
lagergren@137 255 String toSource() {
lagergren@137 256 if (source != null && token != 0) {
lagergren@137 257 return source.getString(Token.descPosition(token), Token.descLength(token));
lagergren@137 258 }
lagergren@137 259
lagergren@137 260 return "function " + (name == null ? "" : name) + "() { [native code] }";
lagergren@137 261 }
lagergren@137 262
attila@963 263 /**
attila@963 264 * Initialize transient fields on deserialized instances
attila@963 265 *
attila@963 266 * @param src source
attila@963 267 * @param inst code installer
attila@963 268 */
attila@963 269 public void initTransients(final Source src, final CodeInstaller<ScriptEnvironment> inst) {
attila@963 270 if (this.source == null && this.installer == null) {
attila@963 271 this.source = src;
attila@963 272 this.installer = inst;
attila@1030 273 } else if (this.source != src || !this.installer.isCompatibleWith(inst)) {
attila@963 274 // Existing values must be same as those passed as parameters
attila@963 275 throw new IllegalArgumentException();
hannesw@828 276 }
hannesw@828 277 }
hannesw@828 278
lagergren@137 279 @Override
lagergren@137 280 public String toString() {
attila@963 281 return super.toString() + '@' + functionNodeId;
attila@963 282 }
attila@963 283
attila@963 284 @Override
attila@963 285 public String toStringVerbose() {
lagergren@137 286 final StringBuilder sb = new StringBuilder();
lagergren@137 287
attila@963 288 sb.append("fnId=").append(functionNodeId).append(' ');
attila@963 289
lagergren@137 290 if (source != null) {
attila@963 291 sb.append(source.getName())
attila@963 292 .append(':')
attila@963 293 .append(lineNumber)
attila@963 294 .append(' ');
lagergren@137 295 }
lagergren@137 296
lagergren@137 297 return sb.toString() + super.toString();
lagergren@137 298 }
lagergren@137 299
attila@963 300 @Override
attila@963 301 public String getFunctionName() {
attila@963 302 return functionName;
attila@963 303 }
attila@963 304
attila@963 305 @Override
attila@963 306 public boolean inDynamicContext() {
attila@994 307 return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT);
attila@963 308 }
attila@963 309
sundar@611 310 private static String functionName(final FunctionNode fn) {
sundar@611 311 if (fn.isAnonymous()) {
sundar@611 312 return "";
sundar@611 313 }
attila@963 314 final FunctionNode.Kind kind = fn.getKind();
attila@963 315 if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
attila@963 316 final String name = NameCodec.decode(fn.getIdent().getName());
attila@963 317 return name.substring(GET_SET_PREFIX_LENGTH);
attila@963 318 }
attila@963 319 return fn.getIdent().getName();
sundar@611 320 }
sundar@611 321
lagergren@137 322 private static long tokenFor(final FunctionNode fn) {
attila@963 323 final int position = Token.descPosition(fn.getFirstToken());
attila@963 324 final long lastToken = Token.withDelimiter(fn.getLastToken());
attila@963 325 // EOL uses length field to store the line number
attila@963 326 final int length = Token.descPosition(lastToken) - position + (Token.descType(lastToken) == TokenType.EOL ? 0 : Token.descLength(lastToken));
lagergren@137 327
lagergren@137 328 return Token.toDesc(TokenType.FUNCTION, position, length);
lagergren@137 329 }
lagergren@137 330
attila@994 331 private static int getDataFlags(final FunctionNode functionNode) {
hannesw@769 332 int flags = IS_CONSTRUCTOR;
hannesw@769 333 if (functionNode.isStrict()) {
hannesw@769 334 flags |= IS_STRICT;
hannesw@769 335 }
hannesw@769 336 if (functionNode.needsCallee()) {
hannesw@769 337 flags |= NEEDS_CALLEE;
hannesw@769 338 }
hannesw@769 339 if (functionNode.usesThis() || functionNode.hasEval()) {
hannesw@769 340 flags |= USES_THIS;
hannesw@769 341 }
attila@963 342 if (functionNode.isVarArg()) {
attila@963 343 flags |= IS_VARIABLE_ARITY;
attila@963 344 }
hannesw@1396 345 if (functionNode.getKind() == FunctionNode.Kind.GETTER || functionNode.getKind() == FunctionNode.Kind.SETTER) {
hannesw@1396 346 flags |= IS_PROPERTY_ACCESSOR;
hannesw@1396 347 }
hannesw@769 348 return flags;
hannesw@769 349 }
hannesw@769 350
lagergren@137 351 @Override
attila@963 352 PropertyMap getAllocatorMap() {
attila@1005 353 return allocationStrategy.getAllocatorMap();
attila@963 354 }
attila@963 355
attila@963 356 @Override
hannesw@766 357 ScriptObject allocate(final PropertyMap map) {
attila@1005 358 return allocationStrategy.allocate(map);
lagergren@137 359 }
lagergren@137 360
attila@1064 361 boolean isSerialized() {
attila@1064 362 return serializedAst != null;
attila@1064 363 }
attila@1064 364
attila@963 365 FunctionNode reparse() {
attila@1064 366 if (isSerialized()) {
attila@1064 367 return deserialize();
attila@1064 368 }
attila@1064 369
attila@963 370 final int descPosition = Token.descPosition(token);
attila@963 371 final Context context = Context.getContextTrusted();
attila@963 372 final Parser parser = new Parser(
attila@963 373 context.getEnv(),
attila@963 374 source,
attila@963 375 new Context.ThrowErrorManager(),
attila@963 376 isStrict(),
attila@1064 377 // source starts at line 0, so even though lineNumber is the correct declaration line, back off
attila@1064 378 // one to make it exclusive
attila@963 379 lineNumber - 1,
attila@1064 380 context.getLogger(Parser.class));
attila@963 381
attila@994 382 if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) {
attila@963 383 parser.setFunctionName(functionName);
attila@963 384 }
attila@994 385 parser.setReparsedFunction(this);
attila@963 386
attila@994 387 final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition,
hannesw@1396 388 Token.descLength(token), isPropertyAccessor());
attila@994 389 // Parser generates a program AST even if we're recompiling a single function, so when we are only
attila@994 390 // recompiling a single function, extract it from the program.
attila@994 391 return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
attila@994 392 }
attila@994 393
attila@1064 394 private FunctionNode deserialize() {
attila@1064 395 final ScriptEnvironment env = installer.getOwner();
attila@1064 396 final Timing timing = env._timing;
attila@1064 397 final long t1 = System.nanoTime();
attila@1064 398 try {
attila@1064 399 return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace()));
attila@1064 400 } finally {
attila@1064 401 timing.accumulateTime("'Deserialize'", System.nanoTime() - t1);
attila@1064 402 }
attila@1064 403 }
attila@1064 404
attila@994 405 private boolean getFunctionFlag(final int flag) {
attila@994 406 return (functionFlags & flag) != 0;
attila@994 407 }
attila@994 408
attila@994 409 private boolean isProgram() {
attila@994 410 return getFunctionFlag(FunctionNode.IS_PROGRAM);
hannesw@766 411 }
hannesw@766 412
attila@963 413 TypeMap typeMap(final MethodType fnCallSiteType) {
attila@963 414 if (fnCallSiteType == null) {
attila@963 415 return null;
attila@963 416 }
attila@963 417
attila@963 418 if (CompiledFunction.isVarArgsType(fnCallSiteType)) {
attila@963 419 return null;
attila@963 420 }
attila@963 421
attila@963 422 return new TypeMap(functionNodeId, explicitParams(fnCallSiteType), needsCallee());
attila@963 423 }
attila@963 424
attila@963 425 private static ScriptObject newLocals(final ScriptObject runtimeScope) {
attila@963 426 final ScriptObject locals = Global.newEmptyInstance();
attila@963 427 locals.setProto(runtimeScope);
attila@963 428 return locals;
attila@963 429 }
attila@963 430
attila@963 431 private Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
attila@963 432 return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null);
attila@963 433 }
attila@963 434
attila@1030 435 /**
attila@1030 436 * Returns a code installer for installing new code. If we're using either optimistic typing or loader-per-compile,
attila@1030 437 * then asks for a code installer with a new class loader; otherwise just uses the current installer. We use
attila@1030 438 * a new class loader with optimistic typing so that deoptimized code can get reclaimed by GC.
attila@1030 439 * @return a code installer for installing new code.
attila@1030 440 */
attila@1030 441 private CodeInstaller<ScriptEnvironment> getInstallerForNewCode() {
attila@1030 442 final ScriptEnvironment env = installer.getOwner();
attila@1030 443 return env._optimistic_types || env._loader_per_compile ? installer.withNewLoader() : installer;
attila@1030 444 }
attila@1030 445
attila@963 446 Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType,
attila@963 447 final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints,
attila@963 448 final int[] continuationEntryPoints) {
attila@963 449 final TypeMap typeMap = typeMap(actualCallSiteType);
attila@963 450 final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
attila@963 451 final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes);
attila@963 452 final Context context = Context.getContextTrusted();
attila@963 453 return new Compiler(
attila@963 454 context,
attila@963 455 context.getEnv(),
attila@1030 456 getInstallerForNewCode(),
attila@963 457 functionNode.getSource(), // source
hannesw@991 458 context.getErrorManager(),
attila@963 459 isStrict() | functionNode.isStrict(), // is strict
attila@963 460 true, // is on demand
attila@963 461 this, // compiledFunction, i.e. this RecompilableScriptFunctionData
attila@963 462 typeMap, // type map
attila@963 463 getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points
attila@963 464 typeInformationFile,
attila@963 465 continuationEntryPoints, // continuation entry points
attila@963 466 runtimeScope); // runtime scope
attila@963 467 }
attila@963 468
attila@963 469 /**
attila@963 470 * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to
attila@963 471 * load invalidated program points map from the persistent type info cache.
attila@963 472 * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function
attila@963 473 * doesn't have it.
attila@963 474 * @param typeInformationFile the object describing the location of the persisted type information.
attila@963 475 * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if
attila@963 476 * neither an existing map or a persistent cached type info is available.
attila@963 477 */
lagergren@1082 478 @SuppressWarnings("unused")
attila@963 479 private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints(
attila@963 480 final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) {
attila@963 481 if(invalidatedProgramPoints != null) {
attila@963 482 return invalidatedProgramPoints;
attila@963 483 }
attila@963 484 final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile);
attila@963 485 return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>();
attila@963 486 }
attila@963 487
attila@963 488 private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) {
attila@963 489 // We're creating an empty script object for holding local variables. AssignSymbols will populate it with
attila@963 490 // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
attila@963 491 // CompilationEnvironment#declareLocalSymbol()).
attila@963 492
attila@963 493 if (log.isEnabled()) {
lagergren@1028 494 log.info("Parameter type specialization of '", functionName, "' signature: ", actualCallSiteType);
attila@963 495 }
attila@963 496
hannesw@1342 497 final boolean persistentCache = persist && usePersistentCodeCache();
attila@963 498 String cacheKey = null;
attila@963 499 if (persistentCache) {
attila@963 500 final TypeMap typeMap = typeMap(actualCallSiteType);
attila@963 501 final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
attila@963 502 cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
attila@1030 503 final CodeInstaller<ScriptEnvironment> newInstaller = getInstallerForNewCode();
attila@1030 504 final StoredScript script = newInstaller.loadScript(source, cacheKey);
attila@963 505
attila@963 506 if (script != null) {
attila@963 507 Compiler.updateCompilationId(script.getCompilationId());
hannesw@1337 508 return script.installFunction(this, newInstaller);
attila@963 509 }
attila@963 510 }
attila@963 511
attila@963 512 final FunctionNode fn = reparse();
attila@963 513 final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
attila@1064 514 final FunctionNode compiledFn = compiler.compile(fn,
attila@1064 515 isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL);
attila@963 516
attila@963 517 if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
attila@963 518 compiler.persistClassInfo(cacheKey, compiledFn);
attila@963 519 }
attila@963 520 return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());
attila@963 521 }
attila@963 522
attila@963 523 boolean usePersistentCodeCache() {
hannesw@1342 524 return installer != null && installer.getOwner()._persistent_cache;
attila@963 525 }
attila@963 526
attila@963 527 private MethodType explicitParams(final MethodType callSiteType) {
attila@963 528 if (CompiledFunction.isVarArgsType(callSiteType)) {
attila@963 529 return null;
attila@963 530 }
attila@963 531
attila@963 532 final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type
attila@963 533 final int callSiteParamCount = noCalleeThisType.parameterCount();
attila@963 534
attila@963 535 // Widen parameters of reference types to Object as we currently don't care for specialization among reference
attila@963 536 // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object)
attila@963 537 final Class<?>[] paramTypes = noCalleeThisType.parameterArray();
attila@963 538 boolean changed = false;
attila@963 539 for (int i = 0; i < paramTypes.length; ++i) {
attila@963 540 final Class<?> paramType = paramTypes[i];
attila@963 541 if (!(paramType.isPrimitive() || paramType == Object.class)) {
attila@963 542 paramTypes[i] = Object.class;
attila@963 543 changed = true;
attila@963 544 }
attila@963 545 }
attila@963 546 final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType;
attila@963 547
attila@963 548 if (callSiteParamCount < getArity()) {
attila@963 549 return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class));
attila@963 550 }
attila@963 551 return generalized;
attila@963 552 }
attila@963 553
attila@963 554 private FunctionNode extractFunctionFromScript(final FunctionNode script) {
attila@963 555 final Set<FunctionNode> fns = new HashSet<>();
attila@963 556 script.getBody().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
attila@963 557 @Override
attila@963 558 public boolean enterFunctionNode(final FunctionNode fn) {
attila@963 559 fns.add(fn);
attila@963 560 return false;
attila@963 561 }
attila@963 562 });
attila@963 563 assert fns.size() == 1 : "got back more than one method in recompilation";
attila@963 564 final FunctionNode f = fns.iterator().next();
attila@963 565 assert f.getId() == functionNodeId;
attila@994 566 if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) {
attila@963 567 return f.clearFlag(null, FunctionNode.IS_DECLARED);
attila@963 568 }
attila@963 569 return f;
attila@963 570 }
attila@963 571
lagergren@1096 572 private void logLookup(final boolean shouldLog, final MethodType targetType) {
lagergren@1096 573 if (shouldLog && log.isEnabled()) {
lagergren@1096 574 log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType);
lagergren@1096 575 }
lagergren@1096 576 }
lagergren@1096 577
lagergren@1096 578 private MethodHandle lookup(final FunctionInitializer fnInit, final boolean shouldLog) {
attila@963 579 final MethodType type = fnInit.getMethodType();
lagergren@1096 580 logLookup(shouldLog, type);
attila@963 581 return lookupCodeMethod(fnInit.getCode(), type);
attila@963 582 }
attila@963 583
attila@963 584 MethodHandle lookup(final FunctionNode fn) {
attila@963 585 final MethodType type = new FunctionSignature(fn).getMethodType();
lagergren@1096 586 logLookup(true, type);
attila@963 587 return lookupCodeMethod(fn.getCompileUnit().getCode(), type);
attila@963 588 }
attila@963 589
lagergren@1028 590 MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) {
lagergren@1028 591 return MH.findStatic(LOOKUP, codeClass, functionName, targetType);
attila@963 592 }
attila@963 593
attila@963 594 /**
attila@963 595 * Initializes this function data with the eagerly generated version of the code. This method can only be invoked
attila@963 596 * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it
attila@963 597 * externally will result in an exception.
lagergren@1028 598 *
hannesw@1337 599 * @param functionNode FunctionNode for this data
attila@963 600 */
hannesw@1337 601 public void initializeCode(final FunctionNode functionNode) {
attila@963 602 // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit.
hannesw@1337 603 if (!code.isEmpty() || functionNode.getId() != functionNodeId || !functionNode.getCompileUnit().isInitializing(this, functionNode)) {
attila@963 604 throw new IllegalStateException(name);
attila@963 605 }
hannesw@1337 606 addCode(lookup(functionNode), null, null, functionNode.getFlags());
hannesw@1337 607 }
hannesw@1337 608
hannesw@1337 609 /**
hannesw@1337 610 * Initializes this function with the given function code initializer.
hannesw@1337 611 * @param initializer function code initializer
hannesw@1337 612 */
hannesw@1337 613 void initializeCode(final FunctionInitializer initializer) {
lagergren@1096 614 addCode(lookup(initializer, true), null, null, initializer.getFlags());
attila@963 615 }
attila@963 616
attila@963 617 private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints,
attila@963 618 final MethodType callSiteType, final int fnFlags) {
attila@963 619 final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags);
attila@963 620 code.add(cfn);
attila@963 621 return cfn;
attila@963 622 }
attila@963 623
attila@963 624 /**
attila@963 625 * 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 626 * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end
attila@963 627 * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of
attila@963 628 * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups
attila@963 629 * for the same specialization, so we must adapt the handle to the expected type.
attila@963 630 * @param fnInit the function
attila@963 631 * @param callSiteType the call site type
attila@963 632 * @return the compiled function object, with its type matching that of the call site type.
attila@963 633 */
attila@963 634 private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) {
attila@963 635 if (isVariableArity()) {
lagergren@1096 636 return addCode(lookup(fnInit, true), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
attila@963 637 }
attila@963 638
lagergren@1096 639 final MethodHandle handle = lookup(fnInit, true);
attila@963 640 final MethodType fromType = handle.type();
attila@963 641 MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1);
attila@963 642 toType = toType.changeReturnType(fromType.returnType());
attila@963 643
attila@963 644 final int toCount = toType.parameterCount();
attila@963 645 final int fromCount = fromType.parameterCount();
attila@963 646 final int minCount = Math.min(fromCount, toCount);
attila@963 647 for(int i = 0; i < minCount; ++i) {
attila@963 648 final Class<?> fromParam = fromType.parameterType(i);
attila@963 649 final Class<?> toParam = toType.parameterType(i);
attila@963 650 // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it
attila@963 651 // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically
attila@963 652 // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there).
attila@963 653 if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) {
attila@963 654 assert fromParam.isAssignableFrom(toParam);
attila@963 655 toType = toType.changeParameterType(i, fromParam);
attila@963 656 }
attila@963 657 }
attila@963 658 if (fromCount > toCount) {
attila@963 659 toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount));
attila@963 660 } else if (fromCount < toCount) {
attila@963 661 toType = toType.dropParameterTypes(fromCount, toCount);
attila@963 662 }
attila@963 663
lagergren@1096 664 return addCode(lookup(fnInit, false).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
attila@963 665 }
attila@963 666
attila@1057 667 /**
attila@1057 668 * Returns the return type of a function specialization for particular parameter types.<br>
attila@1057 669 * <b>Be aware that the way this is implemented, it forces full materialization (compilation and installation) of
attila@1057 670 * code for that specialization.</b>
attila@1057 671 * @param callSiteType the parameter types at the call site. It must include the mandatory {@code callee} and
attila@1057 672 * {@code this} parameters, so it needs to start with at least {@code ScriptFunction.class} and
attila@1057 673 * {@code Object.class} class. Since the return type of the function is calculated from the code itself, it is
attila@1057 674 * irrelevant and should be set to {@code Object.class}.
attila@1057 675 * @param runtimeScope a current runtime scope. Can be null but when it's present it will be used as a source of
attila@1057 676 * current runtime values that can improve the compiler's type speculations (and thus reduce the need for later
attila@1057 677 * recompilations) if the specialization is not already present and thus needs to be freshly compiled.
attila@1057 678 * @return the return type of the function specialization.
attila@1057 679 */
attila@1057 680 public Class<?> getReturnType(final MethodType callSiteType, final ScriptObject runtimeScope) {
attila@1057 681 return getBest(callSiteType, runtimeScope, CompiledFunction.NO_FUNCTIONS).type().returnType();
attila@1057 682 }
hannesw@769 683
hannesw@769 684 @Override
lagergren@1028 685 synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden) {
lagergren@1028 686 CompiledFunction existingBest = super.getBest(callSiteType, runtimeScope, forbidden);
attila@963 687 if (existingBest == null) {
attila@963 688 existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType);
hannesw@769 689 }
hannesw@828 690
attila@963 691 assert existingBest != null;
attila@963 692 //we are calling a vararg method with real args
lagergren@1086 693 boolean varArgWithRealArgs = existingBest.isVarArg() && !CompiledFunction.isVarArgsType(callSiteType);
attila@963 694
attila@963 695 //if the best one is an apply to call, it has to match the callsite exactly
attila@963 696 //or we need to regenerate
attila@963 697 if (existingBest.isApplyToCall()) {
attila@963 698 final CompiledFunction best = lookupExactApplyToCall(callSiteType);
attila@963 699 if (best != null) {
attila@963 700 return best;
attila@963 701 }
lagergren@1086 702 varArgWithRealArgs = true;
attila@963 703 }
attila@963 704
lagergren@1086 705 if (varArgWithRealArgs) {
lagergren@1086 706 // special case: we had an apply to call, but we failed to make it fit.
lagergren@1086 707 // Try to generate a specialized one for this callsite. It may
lagergren@1086 708 // be another apply to call specialization, or it may not, but whatever
lagergren@1086 709 // it is, it is a specialization that is guaranteed to fit
attila@963 710 final FunctionInitializer fnInit = compileTypeSpecialization(callSiteType, runtimeScope, false);
lagergren@1086 711 existingBest = addCode(fnInit, callSiteType);
attila@963 712 }
attila@963 713
attila@963 714 return existingBest;
attila@963 715 }
attila@963 716
attila@963 717 @Override
attila@963 718 boolean isRecompilable() {
attila@963 719 return true;
attila@963 720 }
attila@963 721
attila@963 722 @Override
attila@963 723 public boolean needsCallee() {
attila@994 724 return getFunctionFlag(FunctionNode.NEEDS_CALLEE);
attila@994 725 }
attila@994 726
attila@994 727 /**
attila@994 728 * Returns the {@link FunctionNode} flags associated with this function data.
attila@994 729 * @return the {@link FunctionNode} flags associated with this function data.
attila@994 730 */
attila@994 731 public int getFunctionFlags() {
attila@994 732 return functionFlags;
attila@963 733 }
attila@963 734
attila@963 735 @Override
attila@963 736 MethodType getGenericType() {
attila@963 737 // 2 is for (callee, this)
attila@963 738 if (isVariableArity()) {
attila@963 739 return MethodType.genericMethodType(2, true);
attila@963 740 }
attila@963 741 return MethodType.genericMethodType(2 + getArity());
attila@963 742 }
attila@963 743
attila@963 744 /**
attila@963 745 * Return the function node id.
attila@963 746 * @return the function node id
attila@963 747 */
attila@963 748 public int getFunctionNodeId() {
attila@963 749 return functionNodeId;
attila@963 750 }
attila@963 751
lagergren@1028 752 /**
lagergren@1028 753 * Get the source for the script
lagergren@1028 754 * @return source
lagergren@1028 755 */
attila@963 756 public Source getSource() {
attila@963 757 return source;
attila@963 758 }
attila@963 759
attila@963 760 /**
attila@963 761 * Return a script function data based on a function id, either this function if
attila@963 762 * the id matches or a nested function based on functionId. This goes down into
attila@963 763 * nested functions until all leaves are exhausted.
attila@963 764 *
attila@963 765 * @param functionId function id
attila@963 766 * @return script function data or null if invalid id
attila@963 767 */
attila@963 768 public RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
attila@963 769 if (functionId == functionNodeId) {
attila@963 770 return this;
attila@963 771 }
attila@963 772 RecompilableScriptFunctionData data;
attila@963 773
attila@963 774 data = nestedFunctions == null ? null : nestedFunctions.get(functionId);
attila@963 775 if (data != null) {
attila@963 776 return data;
attila@963 777 }
attila@963 778 for (final RecompilableScriptFunctionData ndata : nestedFunctions.values()) {
attila@963 779 data = ndata.getScriptFunctionData(functionId);
attila@963 780 if (data != null) {
attila@963 781 return data;
attila@963 782 }
attila@963 783 }
attila@963 784 return null;
attila@963 785 }
attila@963 786
attila@963 787 /**
attila@963 788 * Check whether a certain name is a global symbol, i.e. only exists as defined
attila@963 789 * in outermost scope and not shadowed by being parameter or assignment in inner
attila@963 790 * scopes
attila@963 791 *
attila@963 792 * @param functionNode function node to check
attila@963 793 * @param symbolName symbol name
attila@963 794 * @return true if global symbol
attila@963 795 */
attila@963 796 public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) {
attila@963 797 RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId());
attila@963 798 assert data != null;
lagergren@137 799
attila@963 800 do {
attila@963 801 if (data.hasInternalSymbol(symbolName)) {
lagergren@277 802 return false;
lagergren@277 803 }
attila@963 804 data = data.getParent();
attila@963 805 } while(data != null);
attila@963 806
lagergren@277 807 return true;
lagergren@277 808 }
lagergren@277 809
attila@1064 810 /**
attila@1064 811 * Restores the {@link #getFunctionFlags()} flags to a function node. During on-demand compilation, we might need
attila@1064 812 * to restore flags to a function node that was otherwise not subjected to a full compile pipeline (e.g. its parse
attila@1064 813 * was skipped, or it's a nested function of a deserialized function.
attila@1064 814 * @param lc current lexical context
attila@1064 815 * @param fn the function node to restore flags onto
attila@1064 816 * @return the transformed function node
attila@1064 817 */
attila@1064 818 public FunctionNode restoreFlags(final LexicalContext lc, final FunctionNode fn) {
attila@1064 819 assert fn.getId() == functionNodeId;
attila@1064 820 FunctionNode newFn = fn.setFlags(lc, functionFlags);
attila@1064 821 // This compensates for missing markEval() in case the function contains an inner function
attila@1064 822 // that contains eval(), that now we didn't discover since we skipped the inner function.
attila@1064 823 if (newFn.hasNestedEval()) {
attila@1064 824 assert newFn.hasScopeBlock();
attila@1064 825 newFn = newFn.setBody(lc, newFn.getBody().setNeedsScope(null));
attila@1064 826 }
attila@1064 827 return newFn;
attila@1064 828 }
attila@1064 829
attila@963 830 private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
attila@963 831 in.defaultReadObject();
attila@963 832 createLogger();
lagergren@277 833 }
lagergren@277 834
attila@963 835 private void createLogger() {
attila@963 836 log = initLogger(Context.getContextTrusted());
lagergren@277 837 }
lagergren@137 838 }

mercurial