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