1 /*
2  * Copyright (c) 2010, 2013, 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.codegen;
27 
28 import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
29 import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
30 import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
31 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
32 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
33 import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
34 import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
35 
36 import java.io.File;
37 import java.lang.invoke.MethodType;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Collections;
41 import java.util.Comparator;
42 import java.util.HashMap;
43 import java.util.Iterator;
44 import java.util.LinkedHashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Set;
48 import java.util.TreeMap;
49 import java.util.concurrent.TimeUnit;
50 import java.util.concurrent.atomic.AtomicInteger;
51 import java.util.function.Consumer;
52 import java.util.logging.Level;
53 import jdk.internal.dynalink.support.NameCodec;
54 import jdk.nashorn.internal.codegen.types.Type;
55 import jdk.nashorn.internal.ir.Expression;
56 import jdk.nashorn.internal.ir.FunctionNode;
57 import jdk.nashorn.internal.ir.Optimistic;
58 import jdk.nashorn.internal.ir.debug.ClassHistogramElement;
59 import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator;
60 import jdk.nashorn.internal.runtime.CodeInstaller;
61 import jdk.nashorn.internal.runtime.Context;
62 import jdk.nashorn.internal.runtime.ErrorManager;
63 import jdk.nashorn.internal.runtime.FunctionInitializer;
64 import jdk.nashorn.internal.runtime.ParserException;
65 import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
66 import jdk.nashorn.internal.runtime.ScriptEnvironment;
67 import jdk.nashorn.internal.runtime.ScriptObject;
68 import jdk.nashorn.internal.runtime.ScriptRuntime;
69 import jdk.nashorn.internal.runtime.Source;
70 import jdk.nashorn.internal.runtime.logging.DebugLogger;
71 import jdk.nashorn.internal.runtime.logging.Loggable;
72 import jdk.nashorn.internal.runtime.logging.Logger;
73 
74 /**
75  * Responsible for converting JavaScripts to java byte code. Main entry
76  * point for code generator. The compiler may also install classes given some
77  * predefined Code installation policy, given to it at construction time.
78  * @see CodeInstaller
79  */
80 @Logger(name="compiler")
81 public final class Compiler implements Loggable {
82 
83     /** Name of the scripts package */
84     public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts";
85 
86     /** Name of the objects package */
87     public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects";
88 
89     private final ScriptEnvironment env;
90 
91     private final Source source;
92 
93     private final String sourceName;
94 
95     private final ErrorManager errors;
96 
97     private final boolean optimistic;
98 
99     private final Map<String, byte[]> bytecode;
100 
101     private final Set<CompileUnit> compileUnits;
102 
103     private final ConstantData constantData;
104 
105     private final CodeInstaller installer;
106 
107     /** logger for compiler, trampolines and related code generation events
108      *  that affect classes */
109     private final DebugLogger log;
110 
111     private final Context context;
112 
113     private final TypeMap types;
114 
115     // Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
116     // optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
117     private final TypeEvaluator typeEvaluator;
118 
119     private final boolean strict;
120 
121     private final boolean onDemand;
122 
123     /**
124      * If this is a recompilation, this is how we pass in the invalidations, e.g. programPoint=17, Type == int means
125      * that using whatever was at program point 17 as an int failed.
126      */
127     private final Map<Integer, Type> invalidatedProgramPoints;
128 
129     /**
130      * Descriptor of the location where we write the type information after compilation.
131      */
132     private final Object typeInformationFile;
133 
134     /**
135      * Compile unit name of first compile unit - this prefix will be used for all
136      * classes that a compilation generates.
137      */
138     private final String firstCompileUnitName;
139 
140     /**
141      * Contains the program point that should be used as the continuation entry point, as well as all previous
142      * continuation entry points executed as part of a single logical invocation of the function. In practical terms, if
143      * we execute a rest-of method from the program point 17, but then we hit deoptimization again during it at program
144      * point 42, and execute a rest-of method from the program point 42, and then we hit deoptimization again at program
145      * point 57 and are compiling a rest-of method for it, the values in the array will be [57, 42, 17]. This is only
146      * set when compiling a rest-of method. If this method is a rest-of for a non-rest-of method, the array will have
147      * one element. If it is a rest-of for a rest-of, the array will have two elements, and so on.
148      */
149     private final int[] continuationEntryPoints;
150 
151     /**
152      * ScriptFunction data for what is being compile, where applicable.
153      * TODO: make this immutable, propagate it through the CompilationPhases
154      */
155     private RecompilableScriptFunctionData compiledFunction;
156 
157     /**
158      * Most compile unit names are longer than the default StringBuilder buffer,
159      * worth startup performance when massive class generation is going on to increase
160      * this
161      */
162     private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32;
163 
164     /**
165      * Compilation phases that a compilation goes through
166      */
167     public static class CompilationPhases implements Iterable<CompilationPhase> {
168 
169         /**
170          * Singleton that describes compilation up to the phase where a function can be cached.
171          */
172         private final static CompilationPhases COMPILE_UPTO_CACHED = new CompilationPhases(
173                 "Common initial phases",
174                 CompilationPhase.CONSTANT_FOLDING_PHASE,
175                 CompilationPhase.LOWERING_PHASE,
176                 CompilationPhase.APPLY_SPECIALIZATION_PHASE,
177                 CompilationPhase.SPLITTING_PHASE,
178                 CompilationPhase.PROGRAM_POINT_PHASE,
179                 CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
180                 CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
181                 CompilationPhase.CACHE_AST_PHASE
182                 );
183 
184         private final static CompilationPhases COMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
185                 "After common phases, before bytecode generator",
186                 CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
187                 CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE
188                 );
189 
190         /**
191          * Singleton that describes additional steps to be taken after retrieving a cached function, all the
192          * way up to (but not including) generating and installing code.
193          */
194         public final static CompilationPhases RECOMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
195                 "Recompile cached function up to bytecode",
196                 CompilationPhase.REINITIALIZE_CACHED,
197                 COMPILE_CACHED_UPTO_BYTECODE
198                 );
199 
200         /**
201          * Singleton that describes back end of method generation, given that we have generated the normal
202          * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
203          */
204         public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL = new CompilationPhases(
205                 "Generate bytecode and install",
206                 CompilationPhase.BYTECODE_GENERATION_PHASE,
207                 CompilationPhase.INSTALL_PHASE
208                 );
209 
210         /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
211         public final static CompilationPhases COMPILE_UPTO_BYTECODE = new CompilationPhases(
212                 "Compile upto bytecode",
213                 COMPILE_UPTO_CACHED,
214                 COMPILE_CACHED_UPTO_BYTECODE);
215 
216         /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
217         public final static CompilationPhases COMPILE_ALL_NO_INSTALL = new CompilationPhases(
218                 "Compile without install",
219                 COMPILE_UPTO_BYTECODE,
220                 CompilationPhase.BYTECODE_GENERATION_PHASE);
221 
222         /** Singleton that describes a standard eager compilation - this includes code installation */
223         public final static CompilationPhases COMPILE_ALL = new CompilationPhases(
224                 "Full eager compilation",
225                 COMPILE_UPTO_BYTECODE,
226                 GENERATE_BYTECODE_AND_INSTALL);
227 
228         /** Singleton that describes a full compilation - this includes code installation - from serialized state*/
229         public final static CompilationPhases COMPILE_ALL_CACHED = new CompilationPhases(
230                 "Eager compilation from serializaed state",
231                 RECOMPILE_CACHED_UPTO_BYTECODE,
232                 GENERATE_BYTECODE_AND_INSTALL);
233 
234         /**
235          * Singleton that describes restOf method generation, given that we have generated the normal
236          * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
237          */
238         public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL_RESTOF = new CompilationPhases(
239                 "Generate bytecode and install - RestOf method",
240                 CompilationPhase.REUSE_COMPILE_UNITS_PHASE,
241                 GENERATE_BYTECODE_AND_INSTALL);
242 
243         /** Compile all for a rest of method */
244         public final static CompilationPhases COMPILE_ALL_RESTOF = new CompilationPhases(
245                 "Compile all, rest of",
246                 COMPILE_UPTO_BYTECODE,
247                 GENERATE_BYTECODE_AND_INSTALL_RESTOF);
248 
249         /** Compile from serialized for a rest of method */
250         public final static CompilationPhases COMPILE_CACHED_RESTOF = new CompilationPhases(
251                 "Compile serialized, rest of",
252                 RECOMPILE_CACHED_UPTO_BYTECODE,
253                 GENERATE_BYTECODE_AND_INSTALL_RESTOF);
254 
255         private final List<CompilationPhase> phases;
256 
257         private final String desc;
258 
CompilationPhases(final String desc, final CompilationPhase... phases)259         private CompilationPhases(final String desc, final CompilationPhase... phases) {
260             this(desc, Arrays.asList(phases));
261         }
262 
CompilationPhases(final String desc, final CompilationPhases base, final CompilationPhase... phases)263         private CompilationPhases(final String desc, final CompilationPhases base, final CompilationPhase... phases) {
264             this(desc, concat(base.phases, Arrays.asList(phases)));
265         }
266 
CompilationPhases(final String desc, final CompilationPhase first, final CompilationPhases rest)267         private CompilationPhases(final String desc, final CompilationPhase first, final CompilationPhases rest) {
268             this(desc, concat(Collections.singletonList(first), rest.phases));
269         }
270 
CompilationPhases(final String desc, final CompilationPhases base)271         private CompilationPhases(final String desc, final CompilationPhases base) {
272             this(desc, base.phases);
273         }
274 
CompilationPhases(final String desc, final CompilationPhases... bases)275         private CompilationPhases(final String desc, final CompilationPhases... bases) {
276             this(desc, concatPhases(bases));
277         }
278 
CompilationPhases(final String desc, final List<CompilationPhase> phases)279         private CompilationPhases(final String desc, final List<CompilationPhase> phases) {
280             this.desc = desc;
281             this.phases = phases;
282         }
283 
concatPhases(final CompilationPhases[] bases)284         private static List<CompilationPhase> concatPhases(final CompilationPhases[] bases) {
285             final ArrayList<CompilationPhase> l = new ArrayList<>();
286             for(final CompilationPhases base: bases) {
287                 l.addAll(base.phases);
288             }
289             l.trimToSize();
290             return l;
291         }
292 
concat(final List<T> l1, final List<T> l2)293         private static <T> List<T> concat(final List<T> l1, final List<T> l2) {
294             final ArrayList<T> l = new ArrayList<>(l1);
295             l.addAll(l2);
296             l.trimToSize();
297             return l;
298         }
299 
300         @Override
toString()301         public String toString() {
302             return "'" + desc + "' " + phases.toString();
303         }
304 
contains(final CompilationPhase phase)305         boolean contains(final CompilationPhase phase) {
306             return phases.contains(phase);
307         }
308 
309         @Override
iterator()310         public Iterator<CompilationPhase> iterator() {
311             return phases.iterator();
312         }
313 
isRestOfCompilation()314         boolean isRestOfCompilation() {
315             return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_CACHED_RESTOF;
316         }
317 
getDesc()318         String getDesc() {
319             return desc;
320         }
321 
toString(final String prefix)322         String toString(final String prefix) {
323             final StringBuilder sb = new StringBuilder();
324             for (final CompilationPhase phase : phases) {
325                 sb.append(prefix).append(phase).append('\n');
326             }
327             return sb.toString();
328         }
329     }
330 
331     /**
332      * This array contains names that need to be reserved at the start
333      * of a compile, to avoid conflict with variable names later introduced.
334      * See {@link CompilerConstants} for special names used for structures
335      * during a compile.
336      */
337     private static String[] RESERVED_NAMES = {
338         SCOPE.symbolName(),
339         THIS.symbolName(),
340         RETURN.symbolName(),
341         CALLEE.symbolName(),
342         VARARGS.symbolName(),
343         ARGUMENTS.symbolName()
344     };
345 
346     // per instance
347     private final int compilationId = COMPILATION_ID.getAndIncrement();
348 
349     // per instance
350     private final AtomicInteger nextCompileUnitId = new AtomicInteger(0);
351 
352     private static final AtomicInteger COMPILATION_ID = new AtomicInteger(0);
353 
354     /**
355      * Creates a new compiler instance for initial compilation of a script.
356      *
357      * @param installer code installer
358      * @param source    source to compile
359      * @param errors    error manager
360      * @param isStrict  is this a strict compilation
361      * @return a new compiler
362      */
forInitialCompilation( final CodeInstaller installer, final Source source, final ErrorManager errors, final boolean isStrict)363     public static Compiler forInitialCompilation(
364             final CodeInstaller installer,
365             final Source source,
366             final ErrorManager errors,
367             final boolean isStrict) {
368         return new Compiler(installer.getContext(), installer, source, errors, isStrict);
369     }
370 
371     /**
372      * Creates a compiler without a code installer. It can only be used to compile code, not install the
373      * generated classes and as such it is useful only for implementation of {@code --compile-only} command
374      * line option.
375      * @param context  the current context
376      * @param source   source to compile
377      * @param isStrict is this a strict compilation
378      * @return a new compiler
379      */
forNoInstallerCompilation( final Context context, final Source source, final boolean isStrict)380     public static Compiler forNoInstallerCompilation(
381             final Context context,
382             final Source source,
383             final boolean isStrict) {
384         return new Compiler(context, null, source, context.getErrorManager(), isStrict);
385     }
386 
387     /**
388      * Creates a compiler for an on-demand compilation job.
389      *
390      * @param installer                code installer
391      * @param source                   source to compile
392      * @param isStrict                 is this a strict compilation
393      * @param compiledFunction         compiled function, if any
394      * @param types                    parameter and return value type information, if any is known
395      * @param invalidatedProgramPoints invalidated program points for recompilation
396      * @param typeInformationFile      descriptor of the location where type information is persisted
397      * @param continuationEntryPoints  continuation entry points for restof method
398      * @param runtimeScope             runtime scope for recompilation type lookup in {@code TypeEvaluator}
399      * @return a new compiler
400      */
forOnDemandCompilation( final CodeInstaller installer, final Source source, final boolean isStrict, final RecompilableScriptFunctionData compiledFunction, final TypeMap types, final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile, final int[] continuationEntryPoints, final ScriptObject runtimeScope)401     public static Compiler forOnDemandCompilation(
402             final CodeInstaller installer,
403             final Source source,
404             final boolean isStrict,
405             final RecompilableScriptFunctionData compiledFunction,
406             final TypeMap types,
407             final Map<Integer, Type> invalidatedProgramPoints,
408             final Object typeInformationFile,
409             final int[] continuationEntryPoints,
410             final ScriptObject runtimeScope) {
411         final Context context = installer.getContext();
412         return new Compiler(context, installer, source, context.getErrorManager(), isStrict, true,
413                 compiledFunction, types, invalidatedProgramPoints, typeInformationFile,
414                 continuationEntryPoints, runtimeScope);
415     }
416 
417     /**
418      * Convenience constructor for non on-demand compiler instances.
419      */
Compiler( final Context context, final CodeInstaller installer, final Source source, final ErrorManager errors, final boolean isStrict)420     private Compiler(
421             final Context context,
422             final CodeInstaller installer,
423             final Source source,
424             final ErrorManager errors,
425             final boolean isStrict) {
426         this(context, installer, source, errors, isStrict, false, null, null, null, null, null, null);
427     }
428 
Compiler( final Context context, final CodeInstaller installer, final Source source, final ErrorManager errors, final boolean isStrict, final boolean isOnDemand, final RecompilableScriptFunctionData compiledFunction, final TypeMap types, final Map<Integer, Type> invalidatedProgramPoints, final Object typeInformationFile, final int[] continuationEntryPoints, final ScriptObject runtimeScope)429     private Compiler(
430             final Context context,
431             final CodeInstaller installer,
432             final Source source,
433             final ErrorManager errors,
434             final boolean isStrict,
435             final boolean isOnDemand,
436             final RecompilableScriptFunctionData compiledFunction,
437             final TypeMap types,
438             final Map<Integer, Type> invalidatedProgramPoints,
439             final Object typeInformationFile,
440             final int[] continuationEntryPoints,
441             final ScriptObject runtimeScope) {
442         this.context                  = context;
443         this.env                      = context.getEnv();
444         this.installer                = installer;
445         this.constantData             = new ConstantData();
446         this.compileUnits             = CompileUnit.createCompileUnitSet();
447         this.bytecode                 = new LinkedHashMap<>();
448         this.log                      = initLogger(context);
449         this.source                   = source;
450         this.errors                   = errors;
451         this.sourceName               = FunctionNode.getSourceName(source);
452         this.onDemand                 = isOnDemand;
453         this.compiledFunction         = compiledFunction;
454         this.types                    = types;
455         this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap<Integer, Type>() : invalidatedProgramPoints;
456         this.typeInformationFile      = typeInformationFile;
457         this.continuationEntryPoints  = continuationEntryPoints == null ? null: continuationEntryPoints.clone();
458         this.typeEvaluator            = new TypeEvaluator(this, runtimeScope);
459         this.firstCompileUnitName     = firstCompileUnitName();
460         this.strict                   = isStrict;
461 
462         this.optimistic = env._optimistic_types;
463     }
464 
safeSourceName()465     private String safeSourceName() {
466         String baseName = new File(source.getName()).getName();
467 
468         final int index = baseName.lastIndexOf(".js");
469         if (index != -1) {
470             baseName = baseName.substring(0, index);
471         }
472 
473         baseName = baseName.replace('.', '_').replace('-', '_');
474         if (!env._loader_per_compile) {
475             baseName = baseName + installer.getUniqueScriptId();
476         }
477 
478         // ASM's bytecode verifier does not allow JVM allowed safe escapes using '\' as escape char.
479         // While ASM accepts such escapes for method names, field names, it enforces Java identifier
480         // for class names. Workaround that ASM bug here by replacing JVM 'dangerous' chars with '_'
481         // rather than safe encoding using '\'.
482         final String mangled = env._verify_code? replaceDangerChars(baseName) : NameCodec.encode(baseName);
483         return mangled != null ? mangled : baseName;
484     }
485 
486     private static final String DANGEROUS_CHARS   = "\\/.;:$[]<>";
replaceDangerChars(final String name)487     private static String replaceDangerChars(final String name) {
488         final int len = name.length();
489         final StringBuilder buf = new StringBuilder();
490         for (int i = 0; i < len; i++) {
491             final char ch = name.charAt(i);
492             if (DANGEROUS_CHARS.indexOf(ch) != -1) {
493                 buf.append('_');
494             } else {
495                 buf.append(ch);
496             }
497         }
498         return buf.toString();
499     }
500 
firstCompileUnitName()501     private String firstCompileUnitName() {
502         final StringBuilder sb = new StringBuilder(SCRIPTS_PACKAGE).
503                 append('/').
504                 append(CompilerConstants.DEFAULT_SCRIPT_NAME.symbolName()).
505                 append('$');
506 
507         if (isOnDemandCompilation()) {
508             sb.append(RecompilableScriptFunctionData.RECOMPILATION_PREFIX);
509         }
510 
511         if (compilationId > 0) {
512             sb.append(compilationId).append('$');
513         }
514 
515         if (types != null && compiledFunction.getFunctionNodeId() > 0) {
516             sb.append(compiledFunction.getFunctionNodeId());
517             final Type[] paramTypes = types.getParameterTypes(compiledFunction.getFunctionNodeId());
518             for (final Type t : paramTypes) {
519                 sb.append(Type.getShortSignatureDescriptor(t));
520             }
521             sb.append('$');
522         }
523 
524         sb.append(safeSourceName());
525 
526         return sb.toString();
527     }
528 
declareLocalSymbol(final String symbolName)529     void declareLocalSymbol(final String symbolName) {
530         typeEvaluator.declareLocalSymbol(symbolName);
531     }
532 
setData(final RecompilableScriptFunctionData data)533     void setData(final RecompilableScriptFunctionData data) {
534         assert this.compiledFunction == null : data;
535         this.compiledFunction = data;
536     }
537 
538     @Override
getLogger()539     public DebugLogger getLogger() {
540         return log;
541     }
542 
543     @Override
initLogger(final Context ctxt)544     public DebugLogger initLogger(final Context ctxt) {
545         final boolean optimisticTypes = env._optimistic_types;
546         final boolean lazyCompilation = env._lazy_compilation;
547 
548         return ctxt.getLogger(this.getClass(), new Consumer<DebugLogger>() {
549             @Override
550             public void accept(final DebugLogger newLogger) {
551                 if (!lazyCompilation) {
552                     newLogger.warning("WARNING: Running with lazy compilation switched off. This is not a default setting.");
553                 }
554                 newLogger.warning("Optimistic types are ", optimisticTypes ? "ENABLED." : "DISABLED.");
555             }
556         });
557     }
558 
559     ScriptEnvironment getScriptEnvironment() {
560         return env;
561     }
562 
563     boolean isOnDemandCompilation() {
564         return onDemand;
565     }
566 
567     boolean useOptimisticTypes() {
568         return optimistic;
569     }
570 
571     Context getContext() {
572         return context;
573     }
574 
575     Type getOptimisticType(final Optimistic node) {
576         return typeEvaluator.getOptimisticType(node);
577     }
578 
579     /**
580      * Returns true if the expression can be safely evaluated, and its value is an object known to always use
581      * String as the type of its property names retrieved through
582      * {@link ScriptRuntime#toPropertyIterator(Object)}. It is used to avoid optimistic assumptions about its
583      * property name types.
584      * @param expr the expression to test
585      * @return true if the expression can be safely evaluated, and its value is an object known to always use
586      * String as the type of its property iterators.
587      */
588     boolean hasStringPropertyIterator(final Expression expr) {
589         return typeEvaluator.hasStringPropertyIterator(expr);
590     }
591 
592     void addInvalidatedProgramPoint(final int programPoint, final Type type) {
593         invalidatedProgramPoints.put(programPoint, type);
594     }
595 
596 
597     /**
598      * Returns a copy of this compiler's current mapping of invalidated optimistic program points to their types. The
599      * copy is not live with regard to changes in state in this compiler instance, and is mutable.
600      * @return a copy of this compiler's current mapping of invalidated optimistic program points to their types.
601      */
602     public Map<Integer, Type> getInvalidatedProgramPoints() {
603         return invalidatedProgramPoints.isEmpty() ? null : new TreeMap<>(invalidatedProgramPoints);
604     }
605 
606     TypeMap getTypeMap() {
607         return types;
608     }
609 
610     MethodType getCallSiteType(final FunctionNode fn) {
611         if (types == null || !isOnDemandCompilation()) {
612             return null;
613         }
614         return types.getCallSiteType(fn);
615     }
616 
617     Type getParamType(final FunctionNode fn, final int pos) {
618         return types == null ? null : types.get(fn, pos);
619     }
620 
621     /**
622      * Do a compilation job
623      *
624      * @param functionNode function node to compile
625      * @param phases phases of compilation transforms to apply to function
626 
627      * @return transformed function
628      *
629      * @throws CompilationException if error occurs during compilation
630      */
631     public FunctionNode compile(final FunctionNode functionNode, final CompilationPhases phases) throws CompilationException {
632         if (log.isEnabled()) {
633             log.info(">> Starting compile job for ", DebugLogger.quote(functionNode.getName()), " phases=", quote(phases.getDesc()));
634             log.indent();
635         }
636 
637         final String name = DebugLogger.quote(functionNode.getName());
638 
639         FunctionNode newFunctionNode = functionNode;
640 
641         for (final String reservedName : RESERVED_NAMES) {
642             newFunctionNode.uniqueName(reservedName);
643         }
644 
645         final boolean info = log.isLoggable(Level.INFO);
646 
647         final DebugLogger timeLogger = env.isTimingEnabled() ? env._timing.getLogger() : null;
648 
649         long time = 0L;
650 
651         for (final CompilationPhase phase : phases) {
652             log.fine(phase, " starting for ", name);
653 
654             try {
655                 newFunctionNode = phase.apply(this, phases, newFunctionNode);
656             } catch (final ParserException error) {
657                 errors.error(error);
658                 if (env._dump_on_error) {
659                     error.printStackTrace(env.getErr());
660                 }
661                 return null;
662             }
663 
664             log.fine(phase, " done for function ", quote(name));
665 
666             if (env._print_mem_usage) {
667                 printMemoryUsage(functionNode, phase.toString());
668             }
669 
670             time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L);
671         }
672 
673         if (typeInformationFile != null && !phases.isRestOfCompilation()) {
674             OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints);
675         }
676 
677         log.unindent();
678 
679         if (info) {
680             final StringBuilder sb = new StringBuilder("<< Finished compile job for ");
681             sb.append(newFunctionNode.getSource()).
682             append(':').
683             append(quote(newFunctionNode.getName()));
684 
685             if (time > 0L && timeLogger != null) {
686                 assert env.isTimingEnabled();
687                 sb.append(" in ").append(TimeUnit.NANOSECONDS.toMillis(time)).append(" ms");
688             }
689             log.info(sb);
690         }
691 
692         return newFunctionNode;
693     }
694 
695     Source getSource() {
696         return source;
697     }
698 
699     Map<String, byte[]> getBytecode() {
700         return Collections.unmodifiableMap(bytecode);
701     }
702 
703     /**
704      * Reset bytecode cache for compiler reuse.
705      */
706     void clearBytecode() {
707         bytecode.clear();
708     }
709 
710     CompileUnit getFirstCompileUnit() {
711         assert !compileUnits.isEmpty();
712         return compileUnits.iterator().next();
713     }
714 
715     Set<CompileUnit> getCompileUnits() {
716         return compileUnits;
717     }
718 
719     ConstantData getConstantData() {
720         return constantData;
721     }
722 
723     CodeInstaller getCodeInstaller() {
724         return installer;
725     }
726 
727     void addClass(final String name, final byte[] code) {
728         bytecode.put(name, code);
729     }
730 
731     String nextCompileUnitName() {
732         final StringBuilder sb = new StringBuilder(COMPILE_UNIT_NAME_BUFFER_SIZE);
733         sb.append(firstCompileUnitName);
734         final int cuid = nextCompileUnitId.getAndIncrement();
735         if (cuid > 0) {
736             sb.append("$cu").append(cuid);
737         }
738 
739         return sb.toString();
740     }
741 
742     /**
743      * Persist current compilation with the given {@code cacheKey}.
744      * @param cacheKey cache key
745      * @param functionNode function node
746      */
747     public void persistClassInfo(final String cacheKey, final FunctionNode functionNode) {
748         if (cacheKey != null && env._persistent_cache) {
749             // If this is an on-demand compilation create a function initializer for the function being compiled.
750             // Otherwise use function initializer map generated by codegen.
751             final Map<Integer, FunctionInitializer> initializers = new HashMap<>();
752             if (isOnDemandCompilation()) {
753                 initializers.put(functionNode.getId(), new FunctionInitializer(functionNode, getInvalidatedProgramPoints()));
754             } else {
755                 for (final CompileUnit compileUnit : getCompileUnits()) {
756                     for (final FunctionNode fn : compileUnit.getFunctionNodes()) {
757                         initializers.put(fn.getId(), new FunctionInitializer(fn));
758                     }
759                 }
760             }
761             final String mainClassName = getFirstCompileUnit().getUnitClassName();
762             installer.storeScript(cacheKey, source, mainClassName, bytecode, initializers, constantData.toArray(), compilationId);
763         }
764     }
765 
766     /**
767      * Make sure the next compilation id is greater than {@code value}.
768      * @param value compilation id value
769      */
770     public static void updateCompilationId(final int value) {
771         if (value >= COMPILATION_ID.get()) {
772             COMPILATION_ID.set(value + 1);
773         }
774     }
775 
776     CompileUnit addCompileUnit(final long initialWeight) {
777         final CompileUnit compileUnit = createCompileUnit(initialWeight);
778         compileUnits.add(compileUnit);
779         log.fine("Added compile unit ", compileUnit);
780         return compileUnit;
781     }
782 
783     CompileUnit createCompileUnit(final String unitClassName, final long initialWeight) {
784         final ClassEmitter classEmitter = new ClassEmitter(context, sourceName, unitClassName, isStrict());
785         final CompileUnit  compileUnit  = new CompileUnit(unitClassName, classEmitter, initialWeight);
786         classEmitter.begin();
787 
788         return compileUnit;
789     }
790 
791     private CompileUnit createCompileUnit(final long initialWeight) {
792         return createCompileUnit(nextCompileUnitName(), initialWeight);
793     }
794 
795     boolean isStrict() {
796         return strict;
797     }
798 
799     void replaceCompileUnits(final Set<CompileUnit> newUnits) {
800         compileUnits.clear();
801         compileUnits.addAll(newUnits);
802     }
803 
804     CompileUnit findUnit(final long weight) {
805         for (final CompileUnit unit : compileUnits) {
806             if (unit.canHold(weight)) {
807                 unit.addWeight(weight);
808                 return unit;
809             }
810         }
811 
812         return addCompileUnit(weight);
813     }
814 
815     /**
816      * Convert a package/class name to a binary name.
817      *
818      * @param name Package/class name.
819      * @return Binary name.
820      */
821     public static String binaryName(final String name) {
822         return name.replace('/', '.');
823     }
824 
825     RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
826         assert compiledFunction != null;
827         final RecompilableScriptFunctionData fn = compiledFunction.getScriptFunctionData(functionId);
828         assert fn != null : functionId;
829         return fn;
830     }
831 
832     boolean isGlobalSymbol(final FunctionNode fn, final String name) {
833         return getScriptFunctionData(fn.getId()).isGlobalSymbol(fn, name);
834     }
835 
836     int[] getContinuationEntryPoints() {
837         return continuationEntryPoints;
838     }
839 
840     Type getInvalidatedProgramPointType(final int programPoint) {
841         return invalidatedProgramPoints.get(programPoint);
842     }
843 
844     private void printMemoryUsage(final FunctionNode functionNode, final String phaseName) {
845         if (!log.isEnabled()) {
846             return;
847         }
848 
849         log.info(phaseName, "finished. Doing IR size calculation...");
850 
851         final ObjectSizeCalculator osc = new ObjectSizeCalculator(ObjectSizeCalculator.getEffectiveMemoryLayoutSpecification());
852         osc.calculateObjectSize(functionNode);
853 
854         final List<ClassHistogramElement> list      = osc.getClassHistogram();
855         final StringBuilder               sb        = new StringBuilder();
856         final long                        totalSize = osc.calculateObjectSize(functionNode);
857 
858         sb.append(phaseName).
859         append(" Total size = ").
860         append(totalSize / 1024 / 1024).
861         append("MB");
862         log.info(sb);
863 
864         Collections.sort(list, new Comparator<ClassHistogramElement>() {
865             @Override
866             public int compare(final ClassHistogramElement o1, final ClassHistogramElement o2) {
867                 final long diff = o1.getBytes() - o2.getBytes();
868                 if (diff < 0) {
869                     return 1;
870                 } else if (diff > 0) {
871                     return -1;
872                 } else {
873                     return 0;
874                 }
875             }
876         });
877         for (final ClassHistogramElement e : list) {
878             final String line = String.format("    %-48s %10d bytes (%8d instances)", e.getClazz(), e.getBytes(), e.getInstances());
879             log.info(line);
880             if (e.getBytes() < totalSize / 200) {
881                 log.info("    ...");
882                 break; // never mind, so little memory anyway
883             }
884         }
885     }
886 }
887