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