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