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  */
25 
26 package jdk.nashorn.internal.runtime;
27 
28 import static jdk.nashorn.internal.lookup.Lookup.MH;
29 
30 import java.io.IOException;
31 import java.io.ObjectOutputStream;
32 import java.io.Serializable;
33 import java.lang.invoke.MethodHandle;
34 import java.lang.invoke.MethodHandles;
35 import java.lang.invoke.MethodType;
36 import java.lang.ref.Reference;
37 import java.lang.ref.SoftReference;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.IdentityHashMap;
42 import java.util.Map;
43 import java.util.Set;
44 import java.util.TreeMap;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.LinkedBlockingDeque;
47 import java.util.concurrent.ThreadPoolExecutor;
48 import java.util.concurrent.TimeUnit;
49 import jdk.nashorn.internal.codegen.Compiler;
50 import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
51 import jdk.nashorn.internal.codegen.CompilerConstants;
52 import jdk.nashorn.internal.codegen.FunctionSignature;
53 import jdk.nashorn.internal.codegen.Namespace;
54 import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
55 import jdk.nashorn.internal.codegen.TypeMap;
56 import jdk.nashorn.internal.codegen.types.Type;
57 import jdk.nashorn.internal.ir.Block;
58 import jdk.nashorn.internal.ir.ForNode;
59 import jdk.nashorn.internal.ir.FunctionNode;
60 import jdk.nashorn.internal.ir.IdentNode;
61 import jdk.nashorn.internal.ir.LexicalContext;
62 import jdk.nashorn.internal.ir.Node;
63 import jdk.nashorn.internal.ir.SwitchNode;
64 import jdk.nashorn.internal.ir.Symbol;
65 import jdk.nashorn.internal.ir.TryNode;
66 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
67 import jdk.nashorn.internal.objects.Global;
68 import jdk.nashorn.internal.parser.Parser;
69 import jdk.nashorn.internal.parser.Token;
70 import jdk.nashorn.internal.parser.TokenType;
71 import jdk.nashorn.internal.runtime.linker.NameCodec;
72 import jdk.nashorn.internal.runtime.logging.DebugLogger;
73 import jdk.nashorn.internal.runtime.logging.Loggable;
74 import jdk.nashorn.internal.runtime.logging.Logger;
75 import jdk.nashorn.internal.runtime.options.Options;
76 /**
77  * This is a subclass that represents a script function that may be regenerated,
78  * for example with specialization based on call site types, or lazily generated.
79  * The common denominator is that it can get new invokers during its lifespan,
80  * unlike {@code FinalScriptFunctionData}
81  */
82 @Logger(name="recompile")
83 public final class RecompilableScriptFunctionData extends ScriptFunctionData implements Loggable {
84     /** Prefix used for all recompiled script classes */
85     public static final String RECOMPILATION_PREFIX = "Recompilation$";
86 
87     private static final ExecutorService astSerializerExecutorService = createAstSerializerExecutorService();
88 
89     /** Unique function node id for this function node */
90     private final int functionNodeId;
91 
92     private final String functionName;
93 
94     /** The line number where this function begins. */
95     private final int lineNumber;
96 
97     /** Source from which FunctionNode was parsed. */
98     private transient Source source;
99 
100     /**
101      * Cached form of the AST. Either a {@code SerializedAst} object used by split functions as they can't be
102      * reparsed from source, or a soft reference to a {@code FunctionNode} for other functions (it is safe
103      * to be cleared as they can be reparsed).
104      */
105     private volatile transient Object cachedAst;
106 
107     /** Token of this function within the source. */
108     private final long token;
109 
110     /**
111      * Represents the allocation strategy (property map, script object class, and method handle) for when
112      * this function is used as a constructor. Note that majority of functions (those not setting any this.*
113      * properties) will share a single canonical "default strategy" instance.
114      */
115     private final AllocationStrategy allocationStrategy;
116 
117     /**
118      * Opaque object representing parser state at the end of the function. Used when reparsing outer function
119      * to help with skipping parsing inner functions.
120      */
121     private final Object endParserState;
122 
123     /** Code installer used for all further recompilation/specialization of this ScriptFunction */
124     private transient CodeInstaller installer;
125 
126     private final Map<Integer, RecompilableScriptFunctionData> nestedFunctions;
127 
128     /** Id to parent function if one exists */
129     private RecompilableScriptFunctionData parent;
130 
131     /** Copy of the {@link FunctionNode} flags. */
132     private final int functionFlags;
133 
134     private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
135 
136     private transient DebugLogger log;
137 
138     private final Map<String, Integer> externalScopeDepths;
139 
140     private final Set<String> internalSymbols;
141 
142     private static final int GET_SET_PREFIX_LENGTH = "*et ".length();
143 
144     private static final long serialVersionUID = 4914839316174633726L;
145 
146     /**
147      * Constructor - public as scripts use it
148      *
149      * @param functionNode        functionNode that represents this function code
150      * @param installer           installer for code regeneration versions of this function
151      * @param allocationStrategy  strategy for the allocation behavior when this function is used as a constructor
152      * @param nestedFunctions     nested function map
153      * @param externalScopeDepths external scope depths
154      * @param internalSymbols     internal symbols to method, defined in its scope
155      */
RecompilableScriptFunctionData( final FunctionNode functionNode, final CodeInstaller installer, final AllocationStrategy allocationStrategy, final Map<Integer, RecompilableScriptFunctionData> nestedFunctions, final Map<String, Integer> externalScopeDepths, final Set<String> internalSymbols)156     public RecompilableScriptFunctionData(
157         final FunctionNode functionNode,
158         final CodeInstaller installer,
159         final AllocationStrategy allocationStrategy,
160         final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
161         final Map<String, Integer> externalScopeDepths,
162         final Set<String> internalSymbols) {
163 
164         super(functionName(functionNode),
165               Math.min(functionNode.getParameters().size(), MAX_ARITY),
166               getDataFlags(functionNode));
167 
168         this.functionName        = functionNode.getName();
169         this.lineNumber          = functionNode.getLineNumber();
170         this.functionFlags       = functionNode.getFlags() | (functionNode.needsCallee() ? FunctionNode.NEEDS_CALLEE : 0);
171         this.functionNodeId      = functionNode.getId();
172         this.source              = functionNode.getSource();
173         this.endParserState      = functionNode.getEndParserState();
174         this.token               = tokenFor(functionNode);
175         this.installer           = installer;
176         this.allocationStrategy  = allocationStrategy;
177         this.nestedFunctions     = smallMap(nestedFunctions);
178         this.externalScopeDepths = smallMap(externalScopeDepths);
179         this.internalSymbols     = smallSet(new HashSet<>(internalSymbols));
180 
181         for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) {
182             assert nfn.getParent() == null;
183             nfn.setParent(this);
184         }
185 
186         createLogger();
187     }
188 
smallMap(final Map<K, V> map)189     private static <K, V> Map<K, V> smallMap(final Map<K, V> map) {
190         if (map == null || map.isEmpty()) {
191             return Collections.emptyMap();
192         } else if (map.size() == 1) {
193             final Map.Entry<K, V> entry = map.entrySet().iterator().next();
194             return Collections.singletonMap(entry.getKey(), entry.getValue());
195         } else {
196             return map;
197         }
198     }
199 
smallSet(final Set<T> set)200     private static <T> Set<T> smallSet(final Set<T> set) {
201         if (set == null || set.isEmpty()) {
202             return Collections.emptySet();
203         } else if (set.size() == 1) {
204             return Collections.singleton(set.iterator().next());
205         } else {
206             return set;
207         }
208     }
209 
210     @Override
getLogger()211     public DebugLogger getLogger() {
212         return log;
213     }
214 
215     @Override
initLogger(final Context ctxt)216     public DebugLogger initLogger(final Context ctxt) {
217         return ctxt.getLogger(this.getClass());
218     }
219 
220     /**
221      * Check if a symbol is internally defined in a function. For example
222      * if "undefined" is internally defined in the outermost program function,
223      * it has not been reassigned or overridden and can be optimized
224      *
225      * @param symbolName symbol name
226      * @return true if symbol is internal to this ScriptFunction
227      */
228 
hasInternalSymbol(final String symbolName)229     public boolean hasInternalSymbol(final String symbolName) {
230         return internalSymbols.contains(symbolName);
231     }
232 
233     /**
234      * Return the external symbol table
235      * @param symbolName symbol name
236      * @return the external symbol table with proto depths
237      */
getExternalSymbolDepth(final String symbolName)238     public int getExternalSymbolDepth(final String symbolName) {
239         final Integer depth = externalScopeDepths.get(symbolName);
240         return depth == null ? -1 : depth;
241     }
242 
243     /**
244      * Returns the names of all external symbols this function uses.
245      * @return the names of all external symbols this function uses.
246      */
getExternalSymbolNames()247     public Set<String> getExternalSymbolNames() {
248         return Collections.unmodifiableSet(externalScopeDepths.keySet());
249     }
250 
251     /**
252      * Returns the opaque object representing the parser state at the end of this function's body, used to
253      * skip parsing this function when reparsing its containing outer function.
254      * @return the object representing the end parser state
255      */
getEndParserState()256     public Object getEndParserState() {
257         return endParserState;
258     }
259 
260     /**
261      * Get the parent of this RecompilableScriptFunctionData. If we are
262      * a nested function, we have a parent. Note that "null" return value
263      * can also mean that we have a parent but it is unknown, so this can
264      * only be used for conservative assumptions.
265      * @return parent data, or null if non exists and also null IF UNKNOWN.
266      */
getParent()267     public RecompilableScriptFunctionData getParent() {
268         return parent;
269     }
270 
setParent(final RecompilableScriptFunctionData parent)271     void setParent(final RecompilableScriptFunctionData parent) {
272         this.parent = parent;
273     }
274 
275     @Override
toSource()276     String toSource() {
277         if (source != null && token != 0) {
278             return source.getString(Token.descPosition(token), Token.descLength(token));
279         }
280 
281         return "function " + (name == null ? "" : name) + "() { [native code] }";
282     }
283 
284     /**
285      * Initialize transient fields on deserialized instances
286      *
287      * @param src source
288      * @param inst code installer
289      */
initTransients(final Source src, final CodeInstaller inst)290     public void initTransients(final Source src, final CodeInstaller inst) {
291         if (this.source == null && this.installer == null) {
292             this.source    = src;
293             this.installer = inst;
294             for (final RecompilableScriptFunctionData nested : nestedFunctions.values()) {
295                 nested.initTransients(src, inst);
296             }
297         } else if (this.source != src || !this.installer.isCompatibleWith(inst)) {
298             // Existing values must be same as those passed as parameters
299             throw new IllegalArgumentException();
300         }
301     }
302 
303     @Override
toString()304     public String toString() {
305         return super.toString() + '@' + functionNodeId;
306     }
307 
308     @Override
toStringVerbose()309     public String toStringVerbose() {
310         final StringBuilder sb = new StringBuilder();
311 
312         sb.append("fnId=").append(functionNodeId).append(' ');
313 
314         if (source != null) {
315             sb.append(source.getName())
316                 .append(':')
317                 .append(lineNumber)
318                 .append(' ');
319         }
320 
321         return sb.toString() + super.toString();
322     }
323 
324     @Override
getFunctionName()325     public String getFunctionName() {
326         return functionName;
327     }
328 
329     @Override
inDynamicContext()330     public boolean inDynamicContext() {
331         return getFunctionFlag(FunctionNode.IN_DYNAMIC_CONTEXT);
332     }
333 
functionName(final FunctionNode fn)334     private static String functionName(final FunctionNode fn) {
335         if (fn.isAnonymous()) {
336             return "";
337         }
338         final FunctionNode.Kind kind = fn.getKind();
339         if (kind == FunctionNode.Kind.GETTER || kind == FunctionNode.Kind.SETTER) {
340             final String name = NameCodec.decode(fn.getIdent().getName());
341             return name.substring(GET_SET_PREFIX_LENGTH);
342         }
343         return fn.getIdent().getName();
344     }
345 
tokenFor(final FunctionNode fn)346     private static long tokenFor(final FunctionNode fn) {
347         final int  position  = Token.descPosition(fn.getFirstToken());
348         final long lastToken = Token.withDelimiter(fn.getLastToken());
349         // EOL uses length field to store the line number
350         final int  length    = Token.descPosition(lastToken) - position + (Token.descType(lastToken) == TokenType.EOL ? 0 : Token.descLength(lastToken));
351 
352         return Token.toDesc(TokenType.FUNCTION, position, length);
353     }
354 
getDataFlags(final FunctionNode functionNode)355     private static int getDataFlags(final FunctionNode functionNode) {
356         int flags = IS_CONSTRUCTOR;
357         if (functionNode.isStrict()) {
358             flags |= IS_STRICT;
359         }
360         if (functionNode.needsCallee()) {
361             flags |= NEEDS_CALLEE;
362         }
363         if (functionNode.usesThis() || functionNode.hasEval()) {
364             flags |= USES_THIS;
365         }
366         if (functionNode.isVarArg()) {
367             flags |= IS_VARIABLE_ARITY;
368         }
369         if (functionNode.getKind() == FunctionNode.Kind.GETTER || functionNode.getKind() == FunctionNode.Kind.SETTER) {
370             flags |= IS_PROPERTY_ACCESSOR;
371         }
372         if (functionNode.isMethod() || functionNode.isClassConstructor()) {
373             flags |= IS_ES6_METHOD;
374         }
375         return flags;
376     }
377 
378     @Override
getAllocatorMap(final ScriptObject prototype)379     PropertyMap getAllocatorMap(final ScriptObject prototype) {
380         return allocationStrategy.getAllocatorMap(prototype);
381     }
382 
383     @Override
allocate(final PropertyMap map)384     ScriptObject allocate(final PropertyMap map) {
385         return allocationStrategy.allocate(map);
386     }
387 
reparse()388     FunctionNode reparse() {
389         final FunctionNode cachedFunction = getCachedAst();
390         if (cachedFunction != null) {
391             assert cachedFunction.isCached();
392             return cachedFunction;
393         }
394 
395         final int descPosition = Token.descPosition(token);
396         final Context context = Context.getContextTrusted();
397         final Parser parser = new Parser(
398             context.getEnv(),
399             source,
400             new Context.ThrowErrorManager(),
401             isStrict(),
402             // source starts at line 0, so even though lineNumber is the correct declaration line, back off
403             // one to make it exclusive
404             lineNumber - 1,
405             context.getLogger(Parser.class));
406 
407         if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) {
408             parser.setFunctionName(functionName);
409         }
410         parser.setReparsedFunction(this);
411 
412         final FunctionNode program = parser.parse(CompilerConstants.PROGRAM.symbolName(), descPosition,
413                 Token.descLength(token), flags);
414         // Parser generates a program AST even if we're recompiling a single function, so when we are only
415         // recompiling a single function, extract it from the program.
416         return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
417     }
418 
getCachedAst()419     private FunctionNode getCachedAst() {
420         final Object lCachedAst = cachedAst;
421         // Are we softly caching the AST?
422         if (lCachedAst instanceof Reference<?>) {
423             final FunctionNode fn = (FunctionNode)((Reference<?>)lCachedAst).get();
424             if (fn != null) {
425                 // Yes we are - this is fast
426                 return cloneSymbols(fn);
427             }
428         // Are we strongly caching a serialized AST (for split functions only)?
429         } else if (lCachedAst instanceof SerializedAst) {
430             final SerializedAst serializedAst = (SerializedAst)lCachedAst;
431             // Even so, are we also softly caching the AST?
432             final FunctionNode cachedFn = serializedAst.cachedAst == null ? null : serializedAst.cachedAst.get();
433             if (cachedFn != null) {
434                 // Yes we are - this is fast
435                 return cloneSymbols(cachedFn);
436             }
437             final FunctionNode deserializedFn = deserialize(serializedAst.serializedAst);
438             // Softly cache after deserialization, maybe next time we won't need to deserialize
439             serializedAst.cachedAst = new SoftReference<>(deserializedFn);
440             return deserializedFn;
441         }
442         // No cached representation; return null for reparsing
443         return null;
444     }
445 
446     /**
447      * Sets the AST to cache in this function
448      * @param astToCache the new AST to cache
449      */
setCachedAst(final FunctionNode astToCache)450     public void setCachedAst(final FunctionNode astToCache) {
451         assert astToCache.getId() == functionNodeId; // same function
452         assert !(cachedAst instanceof SerializedAst); // Can't overwrite serialized AST
453 
454         final boolean isSplit = astToCache.isSplit();
455         // If we're caching a split function, we're doing it in the eager pass, hence there can be no other
456         // cached representation already. In other words, isSplit implies cachedAst == null.
457         assert !isSplit || cachedAst == null; //
458 
459         final FunctionNode symbolClonedAst = cloneSymbols(astToCache);
460         final Reference<FunctionNode> ref = new SoftReference<>(symbolClonedAst);
461         cachedAst = ref;
462 
463         // Asynchronously serialize split functions.
464         if (isSplit) {
465             astSerializerExecutorService.execute(() -> {
466                 cachedAst = new SerializedAst(symbolClonedAst, ref);
467             });
468         }
469     }
470 
471     /**
472      * Creates the AST serializer executor service used for in-memory serialization of split functions' ASTs.
473      * It is created with an unbounded queue (so it can queue any number of pending tasks). Its core and max
474      * threads is the same, but they are all allowed to time out so when there's no work, they can all go
475      * away. The threads will be daemons, and they will time out if idle for a minute. Their priority is also
476      * slightly lower than normal priority as we'd prefer the CPU to keep running the program; serializing
477      * split function is a memory conservation measure (it allows us to release the AST), it can wait a bit.
478      * @return an executor service with above described characteristics.
479      */
createAstSerializerExecutorService()480     private static ExecutorService createAstSerializerExecutorService() {
481         final int threads = Math.max(1, Options.getIntProperty("nashorn.serialize.threads", Runtime.getRuntime().availableProcessors() / 2));
482         final ThreadPoolExecutor service = new ThreadPoolExecutor(threads, threads, 1, TimeUnit.MINUTES, new LinkedBlockingDeque<>(),
483             (r) -> {
484                 final Thread t = new Thread(r, "Nashorn AST Serializer");
485                 t.setDaemon(true);
486                 t.setPriority(Thread.NORM_PRIORITY - 1);
487                 return t;
488             });
489         service.allowCoreThreadTimeOut(true);
490         return service;
491     }
492 
493     /**
494      * A tuple of a serialized AST and a soft reference to a deserialized AST. This is used to cache split
495      * functions. Since split functions are altered from their source form, they can't be reparsed from
496      * source. While we could just use the {@code byte[]} representation in {@link RecompilableScriptFunctionData#cachedAst}
497      * we're using this tuple instead to also keep a deserialized AST around in memory to cut down on
498      * deserialization costs.
499      */
500     private static class SerializedAst implements Serializable {
501         private final byte[] serializedAst;
502         private volatile transient Reference<FunctionNode> cachedAst;
503 
504         private static final long serialVersionUID = 1L;
505 
SerializedAst(final FunctionNode fn, final Reference<FunctionNode> cachedAst)506         SerializedAst(final FunctionNode fn, final Reference<FunctionNode> cachedAst) {
507             this.serializedAst = AstSerializer.serialize(fn);
508             this.cachedAst = cachedAst;
509         }
510     }
511 
deserialize(final byte[] serializedAst)512     private FunctionNode deserialize(final byte[] serializedAst) {
513         final ScriptEnvironment env = installer.getContext().getEnv();
514         final Timing timing = env._timing;
515         final long t1 = System.nanoTime();
516         try {
517             return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace()));
518         } finally {
519             timing.accumulateTime("'Deserialize'", System.nanoTime() - t1);
520         }
521     }
522 
cloneSymbols(final FunctionNode fn)523     private FunctionNode cloneSymbols(final FunctionNode fn) {
524         final IdentityHashMap<Symbol, Symbol> symbolReplacements = new IdentityHashMap<>();
525         final boolean cached = fn.isCached();
526         // blockDefinedSymbols is used to re-mark symbols defined outside the function as global. We only
527         // need to do this when we cache an eagerly parsed function (which currently means a split one, as we
528         // don't cache non-split functions from the eager pass); those already cached, or those not split
529         // don't need this step.
530         final Set<Symbol> blockDefinedSymbols = fn.isSplit() && !cached ? Collections.newSetFromMap(new IdentityHashMap<>()) : null;
531         FunctionNode newFn = (FunctionNode)fn.accept(new SimpleNodeVisitor() {
532             private Symbol getReplacement(final Symbol original) {
533                 if (original == null) {
534                     return null;
535                 }
536                 final Symbol existingReplacement = symbolReplacements.get(original);
537                 if (existingReplacement != null) {
538                     return existingReplacement;
539                 }
540                 final Symbol newReplacement = original.clone();
541                 symbolReplacements.put(original, newReplacement);
542                 return newReplacement;
543             }
544 
545             @Override
546             public Node leaveIdentNode(final IdentNode identNode) {
547                 final Symbol oldSymbol = identNode.getSymbol();
548                 if (oldSymbol != null) {
549                     final Symbol replacement = getReplacement(oldSymbol);
550                     return identNode.setSymbol(replacement);
551                 }
552                 return identNode;
553             }
554 
555             @Override
556             public Node leaveForNode(final ForNode forNode) {
557                 return ensureUniqueLabels(forNode.setIterator(lc, getReplacement(forNode.getIterator())));
558             }
559 
560             @Override
561             public Node leaveSwitchNode(final SwitchNode switchNode) {
562                 return ensureUniqueLabels(switchNode.setTag(lc, getReplacement(switchNode.getTag())));
563             }
564 
565             @Override
566             public Node leaveTryNode(final TryNode tryNode) {
567                 return ensureUniqueLabels(tryNode.setException(lc, getReplacement(tryNode.getException())));
568             }
569 
570             @Override
571             public boolean enterBlock(final Block block) {
572                 for(final Symbol symbol: block.getSymbols()) {
573                     final Symbol replacement = getReplacement(symbol);
574                     if (blockDefinedSymbols != null) {
575                         blockDefinedSymbols.add(replacement);
576                     }
577                 }
578                 return true;
579             }
580 
581             @Override
582             public Node leaveBlock(final Block block) {
583                 return ensureUniqueLabels(block.replaceSymbols(lc, symbolReplacements));
584             }
585 
586             @Override
587             public Node leaveFunctionNode(final FunctionNode functionNode) {
588                 return functionNode.setParameters(lc, functionNode.visitParameters(this));
589             }
590 
591             @Override
592             protected Node leaveDefault(final Node node) {
593                 return ensureUniqueLabels(node);
594             };
595 
596             private Node ensureUniqueLabels(final Node node) {
597                 // If we're returning a cached AST, we must also ensure unique labels
598                 return cached ? node.ensureUniqueLabels(lc) : node;
599             }
600         });
601 
602         if (blockDefinedSymbols != null) {
603             // Mark all symbols not defined in blocks as globals
604             Block newBody = null;
605             for(final Symbol symbol: symbolReplacements.values()) {
606                 if(!blockDefinedSymbols.contains(symbol)) {
607                     assert symbol.isScope(); // must be scope
608                     assert externalScopeDepths.containsKey(symbol.getName()); // must be known to us as an external
609                     // Register it in the function body symbol table as a new global symbol
610                     symbol.setFlags((symbol.getFlags() & ~Symbol.KINDMASK) | Symbol.IS_GLOBAL);
611                     if (newBody == null) {
612                         newBody = newFn.getBody().copyWithNewSymbols();
613                         newFn = newFn.setBody(null, newBody);
614                     }
615                     assert newBody.getExistingSymbol(symbol.getName()) == null; // must not be defined in the body already
616                     newBody.putSymbol(symbol);
617                 }
618             }
619         }
620         return newFn.setCached(null);
621     }
622 
getFunctionFlag(final int flag)623     private boolean getFunctionFlag(final int flag) {
624         return (functionFlags & flag) != 0;
625     }
626 
isProgram()627     private boolean isProgram() {
628         return getFunctionFlag(FunctionNode.IS_PROGRAM);
629     }
630 
typeMap(final MethodType fnCallSiteType)631     TypeMap typeMap(final MethodType fnCallSiteType) {
632         if (fnCallSiteType == null) {
633             return null;
634         }
635 
636         if (CompiledFunction.isVarArgsType(fnCallSiteType)) {
637             return null;
638         }
639 
640         return new TypeMap(functionNodeId, explicitParams(fnCallSiteType), needsCallee());
641     }
642 
newLocals(final ScriptObject runtimeScope)643     private static ScriptObject newLocals(final ScriptObject runtimeScope) {
644         final ScriptObject locals = Global.newEmptyInstance();
645         locals.setProto(runtimeScope);
646         return locals;
647     }
648 
getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope)649     private Compiler getCompiler(final FunctionNode fn, final MethodType actualCallSiteType, final ScriptObject runtimeScope) {
650         return getCompiler(fn, actualCallSiteType, newLocals(runtimeScope), null, null);
651     }
652 
653     /**
654      * Returns a code installer for installing new code. If we're using either optimistic typing or loader-per-compile,
655      * then asks for a code installer with a new class loader; otherwise just uses the current installer. We use
656      * a new class loader with optimistic typing so that deoptimized code can get reclaimed by GC.
657      * @return a code installer for installing new code.
658      */
getInstallerForNewCode()659     private CodeInstaller getInstallerForNewCode() {
660         final ScriptEnvironment env = installer.getContext().getEnv();
661         return env._optimistic_types || env._loader_per_compile ? installer.getOnDemandCompilationInstaller() : installer;
662     }
663 
getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType, final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints, final int[] continuationEntryPoints)664     Compiler getCompiler(final FunctionNode functionNode, final MethodType actualCallSiteType,
665             final ScriptObject runtimeScope, final Map<Integer, Type> invalidatedProgramPoints,
666             final int[] continuationEntryPoints) {
667         final TypeMap typeMap = typeMap(actualCallSiteType);
668         final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
669         final Object typeInformationFile = OptimisticTypesPersistence.getLocationDescriptor(source, functionNodeId, paramTypes);
670         return Compiler.forOnDemandCompilation(
671                 getInstallerForNewCode(),
672                 functionNode.getSource(),  // source
673                 isStrict() | functionNode.isStrict(), // is strict
674                 this,       // compiledFunction, i.e. this RecompilableScriptFunctionData
675                 typeMap,    // type map
676                 getEffectiveInvalidatedProgramPoints(invalidatedProgramPoints, typeInformationFile), // invalidated program points
677                 typeInformationFile,
678                 continuationEntryPoints, // continuation entry points
679                 runtimeScope); // runtime scope
680     }
681 
682     /**
683      * If the function being compiled already has its own invalidated program points map, use it. Otherwise, attempt to
684      * load invalidated program points map from the persistent type info cache.
685      * @param invalidatedProgramPoints the function's current invalidated program points map. Null if the function
686      * doesn't have it.
687      * @param typeInformationFile the object describing the location of the persisted type information.
688      * @return either the existing map, or a loaded map from the persistent type info cache, or a new empty map if
689      * neither an existing map or a persistent cached type info is available.
690      */
691     @SuppressWarnings("unused")
getEffectiveInvalidatedProgramPoints( final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile)692     private static Map<Integer, Type> getEffectiveInvalidatedProgramPoints(
693             final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile) {
694         if(invalidatedProgramPoints != null) {
695             return invalidatedProgramPoints;
696         }
697         final Map<Integer, Type> loadedProgramPoints = OptimisticTypesPersistence.load(typeInformationFile);
698         return loadedProgramPoints != null ? loadedProgramPoints : new TreeMap<Integer, Type>();
699     }
700 
compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist)701     private FunctionInitializer compileTypeSpecialization(final MethodType actualCallSiteType, final ScriptObject runtimeScope, final boolean persist) {
702         // We're creating an empty script object for holding local variables. AssignSymbols will populate it with
703         // explicit Undefined values for undefined local variables (see AssignSymbols#defineSymbol() and
704         // CompilationEnvironment#declareLocalSymbol()).
705 
706         if (log.isEnabled()) {
707             log.info("Parameter type specialization of '", functionName, "' signature: ", actualCallSiteType);
708         }
709 
710         final boolean persistentCache = persist && usePersistentCodeCache();
711         String cacheKey = null;
712         if (persistentCache) {
713             final TypeMap typeMap = typeMap(actualCallSiteType);
714             final Type[] paramTypes = typeMap == null ? null : typeMap.getParameterTypes(functionNodeId);
715             cacheKey = CodeStore.getCacheKey(functionNodeId, paramTypes);
716             final CodeInstaller newInstaller = getInstallerForNewCode();
717             final StoredScript script = newInstaller.loadScript(source, cacheKey);
718 
719             if (script != null) {
720                 Compiler.updateCompilationId(script.getCompilationId());
721                 return script.installFunction(this, newInstaller);
722             }
723         }
724 
725         final FunctionNode fn = reparse();
726         final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
727         final FunctionNode compiledFn = compiler.compile(fn,
728                 fn.isCached() ? CompilationPhases.COMPILE_ALL_CACHED : CompilationPhases.COMPILE_ALL);
729 
730         if (persist && !compiledFn.hasApplyToCallSpecialization()) {
731             compiler.persistClassInfo(cacheKey, compiledFn);
732         }
733         return new FunctionInitializer(compiledFn, compiler.getInvalidatedProgramPoints());
734     }
735 
usePersistentCodeCache()736     boolean usePersistentCodeCache() {
737         return installer != null && installer.getContext().getEnv()._persistent_cache;
738     }
739 
explicitParams(final MethodType callSiteType)740     private MethodType explicitParams(final MethodType callSiteType) {
741         if (CompiledFunction.isVarArgsType(callSiteType)) {
742             return null;
743         }
744 
745         final MethodType noCalleeThisType = callSiteType.dropParameterTypes(0, 2); // (callee, this) is always in call site type
746         final int callSiteParamCount = noCalleeThisType.parameterCount();
747 
748         // Widen parameters of reference types to Object as we currently don't care for specialization among reference
749         // types. E.g. call site saying (ScriptFunction, Object, String) should still link to (ScriptFunction, Object, Object)
750         final Class<?>[] paramTypes = noCalleeThisType.parameterArray();
751         boolean changed = false;
752         for (int i = 0; i < paramTypes.length; ++i) {
753             final Class<?> paramType = paramTypes[i];
754             if (!(paramType.isPrimitive() || paramType == Object.class)) {
755                 paramTypes[i] = Object.class;
756                 changed = true;
757             }
758         }
759         final MethodType generalized = changed ? MethodType.methodType(noCalleeThisType.returnType(), paramTypes) : noCalleeThisType;
760 
761         if (callSiteParamCount < getArity()) {
762             return generalized.appendParameterTypes(Collections.<Class<?>>nCopies(getArity() - callSiteParamCount, Object.class));
763         }
764         return generalized;
765     }
766 
extractFunctionFromScript(final FunctionNode script)767     private FunctionNode extractFunctionFromScript(final FunctionNode script) {
768         final Set<FunctionNode> fns = new HashSet<>();
769         script.getBody().accept(new SimpleNodeVisitor() {
770             @Override
771             public boolean enterFunctionNode(final FunctionNode fn) {
772                 fns.add(fn);
773                 return false;
774             }
775         });
776         assert fns.size() == 1 : "got back more than one method in recompilation";
777         final FunctionNode f = fns.iterator().next();
778         assert f.getId() == functionNodeId;
779         if (!getFunctionFlag(FunctionNode.IS_DECLARED) && f.isDeclared()) {
780             return f.clearFlag(null, FunctionNode.IS_DECLARED);
781         }
782         return f;
783     }
784 
logLookup(final boolean shouldLog, final MethodType targetType)785     private void logLookup(final boolean shouldLog, final MethodType targetType) {
786         if (shouldLog && log.isEnabled()) {
787             log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType);
788         }
789     }
790 
lookup(final FunctionInitializer fnInit, final boolean shouldLog)791     private MethodHandle lookup(final FunctionInitializer fnInit, final boolean shouldLog) {
792         final MethodType type = fnInit.getMethodType();
793         logLookup(shouldLog, type);
794         return lookupCodeMethod(fnInit.getCode(), type);
795     }
796 
lookup(final FunctionNode fn)797     MethodHandle lookup(final FunctionNode fn) {
798         final MethodType type = new FunctionSignature(fn).getMethodType();
799         logLookup(true, type);
800         return lookupCodeMethod(fn.getCompileUnit().getCode(), type);
801     }
802 
lookupCodeMethod(final Class<?> codeClass, final MethodType targetType)803     MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) {
804         return MH.findStatic(LOOKUP, codeClass, functionName, targetType);
805     }
806 
807     /**
808      * Initializes this function data with the eagerly generated version of the code. This method can only be invoked
809      * by the compiler internals in Nashorn and is public for implementation reasons only. Attempting to invoke it
810      * externally will result in an exception.
811      *
812      * @param functionNode FunctionNode for this data
813      */
initializeCode(final FunctionNode functionNode)814     public void initializeCode(final FunctionNode functionNode) {
815         // Since the method is public, we double-check that we aren't invoked with an inappropriate compile unit.
816         if (!code.isEmpty() || functionNode.getId() != functionNodeId || !functionNode.getCompileUnit().isInitializing(this, functionNode)) {
817             throw new IllegalStateException(name);
818         }
819         addCode(lookup(functionNode), null, null, functionNode.getFlags());
820     }
821 
822     /**
823      * Initializes this function with the given function code initializer.
824      * @param initializer function code initializer
825      */
initializeCode(final FunctionInitializer initializer)826     void initializeCode(final FunctionInitializer initializer) {
827         addCode(lookup(initializer, true), null, null, initializer.getFlags());
828     }
829 
addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints, final MethodType callSiteType, final int fnFlags)830     private CompiledFunction addCode(final MethodHandle target, final Map<Integer, Type> invalidatedProgramPoints,
831                                      final MethodType callSiteType, final int fnFlags) {
832         final CompiledFunction cfn = new CompiledFunction(target, this, invalidatedProgramPoints, callSiteType, fnFlags);
833         assert noDuplicateCode(cfn) : "duplicate code";
834         code.add(cfn);
835         return cfn;
836     }
837 
838     /**
839      * Add code with specific call site type. It will adapt the type of the looked up method handle to fit the call site
840      * type. This is necessary because even if we request a specialization that takes an "int" parameter, we might end
841      * up getting one that takes a "double" etc. because of internal function logic causes widening (e.g. assignment of
842      * a wider value to the parameter variable). However, we use the method handle type for matching subsequent lookups
843      * for the same specialization, so we must adapt the handle to the expected type.
844      * @param fnInit the function
845      * @param callSiteType the call site type
846      * @return the compiled function object, with its type matching that of the call site type.
847      */
addCode(final FunctionInitializer fnInit, final MethodType callSiteType)848     private CompiledFunction addCode(final FunctionInitializer fnInit, final MethodType callSiteType) {
849         if (isVariableArity()) {
850             return addCode(lookup(fnInit, true), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
851         }
852 
853         final MethodHandle handle = lookup(fnInit, true);
854         final MethodType fromType = handle.type();
855         MethodType toType = needsCallee(fromType) ? callSiteType.changeParameterType(0, ScriptFunction.class) : callSiteType.dropParameterTypes(0, 1);
856         toType = toType.changeReturnType(fromType.returnType());
857 
858         final int toCount = toType.parameterCount();
859         final int fromCount = fromType.parameterCount();
860         final int minCount = Math.min(fromCount, toCount);
861         for(int i = 0; i < minCount; ++i) {
862             final Class<?> fromParam = fromType.parameterType(i);
863             final Class<?>   toParam =   toType.parameterType(i);
864             // If method has an Object parameter, but call site had String, preserve it as Object. No need to narrow it
865             // artificially. Note that this is related to how CompiledFunction.matchesCallSite() works, specifically
866             // the fact that various reference types compare to equal (see "fnType.isEquivalentTo(csType)" there).
867             if (fromParam != toParam && !fromParam.isPrimitive() && !toParam.isPrimitive()) {
868                 assert fromParam.isAssignableFrom(toParam);
869                 toType = toType.changeParameterType(i, fromParam);
870             }
871         }
872         if (fromCount > toCount) {
873             toType = toType.appendParameterTypes(fromType.parameterList().subList(toCount, fromCount));
874         } else if (fromCount < toCount) {
875             toType = toType.dropParameterTypes(fromCount, toCount);
876         }
877 
878         return addCode(lookup(fnInit, false).asType(toType), fnInit.getInvalidatedProgramPoints(), callSiteType, fnInit.getFlags());
879     }
880 
881     /**
882      * Returns the return type of a function specialization for particular parameter types.<br>
883      * <b>Be aware that the way this is implemented, it forces full materialization (compilation and installation) of
884      * code for that specialization.</b>
885      * @param callSiteType the parameter types at the call site. It must include the mandatory {@code callee} and
886      * {@code this} parameters, so it needs to start with at least {@code ScriptFunction.class} and
887      * {@code Object.class} class. Since the return type of the function is calculated from the code itself, it is
888      * irrelevant and should be set to {@code Object.class}.
889      * @param runtimeScope a current runtime scope. Can be null but when it's present it will be used as a source of
890      * current runtime values that can improve the compiler's type speculations (and thus reduce the need for later
891      * recompilations) if the specialization is not already present and thus needs to be freshly compiled.
892      * @return the return type of the function specialization.
893      */
getReturnType(final MethodType callSiteType, final ScriptObject runtimeScope)894     public Class<?> getReturnType(final MethodType callSiteType, final ScriptObject runtimeScope) {
895         return getBest(callSiteType, runtimeScope, CompiledFunction.NO_FUNCTIONS).type().returnType();
896     }
897 
898     @Override
getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden, final boolean linkLogicOkay)899     synchronized CompiledFunction getBest(final MethodType callSiteType, final ScriptObject runtimeScope, final Collection<CompiledFunction> forbidden, final boolean linkLogicOkay) {
900         assert isValidCallSite(callSiteType) : callSiteType;
901 
902         CompiledFunction existingBest = pickFunction(callSiteType, false);
903         if (existingBest == null) {
904             existingBest = pickFunction(callSiteType, true); // try vararg last
905         }
906         if (existingBest == null) {
907             existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, true), callSiteType);
908         }
909 
910         assert existingBest != null;
911 
912         //if the best one is an apply to call, it has to match the callsite exactly
913         //or we need to regenerate
914         if (existingBest.isApplyToCall()) {
915             final CompiledFunction best = lookupExactApplyToCall(callSiteType);
916             if (best != null) {
917                 return best;
918             }
919 
920             // special case: we had an apply to call, but we failed to make it fit.
921             // Try to generate a specialized one for this callsite. It may
922             // be another apply to call specialization, or it may not, but whatever
923             // it is, it is a specialization that is guaranteed to fit
924             existingBest = addCode(compileTypeSpecialization(callSiteType, runtimeScope, false), callSiteType);
925         }
926 
927         return existingBest;
928     }
929 
930     @Override
needsCallee()931     public boolean needsCallee() {
932         return getFunctionFlag(FunctionNode.NEEDS_CALLEE);
933     }
934 
935     /**
936      * Returns the {@link FunctionNode} flags associated with this function data.
937      * @return the {@link FunctionNode} flags associated with this function data.
938      */
getFunctionFlags()939     public int getFunctionFlags() {
940         return functionFlags;
941     }
942 
943     @Override
getGenericType()944     MethodType getGenericType() {
945         // 2 is for (callee, this)
946         if (isVariableArity()) {
947             return MethodType.genericMethodType(2, true);
948         }
949         return MethodType.genericMethodType(2 + getArity());
950     }
951 
952     /**
953      * Return the function node id.
954      * @return the function node id
955      */
getFunctionNodeId()956     public int getFunctionNodeId() {
957         return functionNodeId;
958     }
959 
960     /**
961      * Get the source for the script
962      * @return source
963      */
getSource()964     public Source getSource() {
965         return source;
966     }
967 
968     /**
969      * Return a script function data based on a function id, either this function if
970      * the id matches or a nested function based on functionId. This goes down into
971      * nested functions until all leaves are exhausted.
972      *
973      * @param functionId function id
974      * @return script function data or null if invalid id
975      */
getScriptFunctionData(final int functionId)976     public RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
977         if (functionId == functionNodeId) {
978             return this;
979         }
980         RecompilableScriptFunctionData data;
981 
982         data = nestedFunctions == null ? null : nestedFunctions.get(functionId);
983         if (data != null) {
984             return data;
985         }
986         for (final RecompilableScriptFunctionData ndata : nestedFunctions.values()) {
987             data = ndata.getScriptFunctionData(functionId);
988             if (data != null) {
989                 return data;
990             }
991         }
992         return null;
993     }
994 
995     /**
996      * Check whether a certain name is a global symbol, i.e. only exists as defined
997      * in outermost scope and not shadowed by being parameter or assignment in inner
998      * scopes
999      *
1000      * @param functionNode function node to check
1001      * @param symbolName symbol name
1002      * @return true if global symbol
1003      */
isGlobalSymbol(final FunctionNode functionNode, final String symbolName)1004     public boolean isGlobalSymbol(final FunctionNode functionNode, final String symbolName) {
1005         RecompilableScriptFunctionData data = getScriptFunctionData(functionNode.getId());
1006         assert data != null;
1007 
1008         do {
1009             if (data.hasInternalSymbol(symbolName)) {
1010                 return false;
1011             }
1012             data = data.getParent();
1013         } while(data != null);
1014 
1015         return true;
1016     }
1017 
1018     /**
1019      * Restores the {@link #getFunctionFlags()} flags to a function node. During on-demand compilation, we might need
1020      * to restore flags to a function node that was otherwise not subjected to a full compile pipeline (e.g. its parse
1021      * was skipped, or it's a nested function of a deserialized function.
1022      * @param lc current lexical context
1023      * @param fn the function node to restore flags onto
1024      * @return the transformed function node
1025      */
restoreFlags(final LexicalContext lc, final FunctionNode fn)1026     public FunctionNode restoreFlags(final LexicalContext lc, final FunctionNode fn) {
1027         assert fn.getId() == functionNodeId;
1028         FunctionNode newFn = fn.setFlags(lc, functionFlags);
1029         // This compensates for missing markEval() in case the function contains an inner function
1030         // that contains eval(), that now we didn't discover since we skipped the inner function.
1031         if (newFn.hasNestedEval()) {
1032             assert newFn.hasScopeBlock();
1033             newFn = newFn.setBody(lc, newFn.getBody().setNeedsScope(null));
1034         }
1035         return newFn;
1036     }
1037 
1038     // Make sure code does not contain a compiled function with the same signature as compiledFunction
noDuplicateCode(final CompiledFunction compiledFunction)1039     private boolean noDuplicateCode(final CompiledFunction compiledFunction) {
1040         for (final CompiledFunction cf : code) {
1041             if (cf.type().equals(compiledFunction.type())) {
1042                 return false;
1043             }
1044         }
1045         return true;
1046     }
1047 
writeObject(final ObjectOutputStream out)1048     private void writeObject(final ObjectOutputStream out) throws IOException {
1049         final Object localCachedAst = cachedAst;
1050         out.defaultWriteObject();
1051         // We need to persist SerializedAst for split functions as they can't reparse the source code.
1052         if (localCachedAst instanceof SerializedAst) {
1053             out.writeObject(localCachedAst);
1054         } else {
1055             out.writeObject(null);
1056         }
1057     }
1058 
readObject(final java.io.ObjectInputStream in)1059     private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
1060         in.defaultReadObject();
1061         cachedAst = in.readObject();
1062         createLogger();
1063     }
1064 
createLogger()1065     private void createLogger() {
1066         log = initLogger(Context.getContextTrusted());
1067     }
1068 }
1069