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.TimeUnit;
50import java.util.concurrent.atomic.AtomicInteger;
51import java.util.function.Consumer;
52import java.util.logging.Level;
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.linker.NameCodec;
70import jdk.nashorn.internal.runtime.logging.DebugLogger;
71import jdk.nashorn.internal.runtime.logging.Loggable;
72import 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")
81public 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.DECLARE_LOCAL_SYMBOLS_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 retrieving a cached function, all the
193         * way up to (but not including) generating and installing code.
194         */
195        public final static CompilationPhases RECOMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
196                "Recompile cached function up to bytecode",
197                CompilationPhase.REINITIALIZE_CACHED,
198                COMPILE_CACHED_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_CACHED,
215                COMPILE_CACHED_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_CACHED = new CompilationPhases(
231                "Eager compilation from serializaed state",
232                RECOMPILE_CACHED_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_CACHED_RESTOF = new CompilationPhases(
252                "Compile serialized, rest of",
253                RECOMPILE_CACHED_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_CACHED_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     * Creates a new compiler instance for initial compilation of a script.
357     *
358     * @param installer code installer
359     * @param source    source to compile
360     * @param errors    error manager
361     * @param isStrict  is this a strict compilation
362     * @return a new compiler
363     */
364    public static Compiler forInitialCompilation(
365            final CodeInstaller installer,
366            final Source source,
367            final ErrorManager errors,
368            final boolean isStrict) {
369        return new Compiler(installer.getContext(), installer, source, errors, isStrict);
370    }
371
372    /**
373     * Creates a compiler without a code installer. It can only be used to compile code, not install the
374     * generated classes and as such it is useful only for implementation of {@code --compile-only} command
375     * line option.
376     * @param context  the current context
377     * @param source   source to compile
378     * @param isStrict is this a strict compilation
379     * @return a new compiler
380     */
381    public static Compiler forNoInstallerCompilation(
382            final Context context,
383            final Source source,
384            final boolean isStrict) {
385        return new Compiler(context, null, source, context.getErrorManager(), isStrict);
386    }
387
388    /**
389     * Creates a compiler for an on-demand compilation job.
390     *
391     * @param installer                code installer
392     * @param source                   source to compile
393     * @param isStrict                 is this a strict compilation
394     * @param compiledFunction         compiled function, if any
395     * @param types                    parameter and return value type information, if any is known
396     * @param invalidatedProgramPoints invalidated program points for recompilation
397     * @param typeInformationFile      descriptor of the location where type information is persisted
398     * @param continuationEntryPoints  continuation entry points for restof method
399     * @param runtimeScope             runtime scope for recompilation type lookup in {@code TypeEvaluator}
400     * @return a new compiler
401     */
402    public static Compiler forOnDemandCompilation(
403            final CodeInstaller installer,
404            final Source source,
405            final boolean isStrict,
406            final RecompilableScriptFunctionData compiledFunction,
407            final TypeMap types,
408            final Map<Integer, Type> invalidatedProgramPoints,
409            final Object typeInformationFile,
410            final int[] continuationEntryPoints,
411            final ScriptObject runtimeScope) {
412        final Context context = installer.getContext();
413        return new Compiler(context, installer, source, context.getErrorManager(), isStrict, true,
414                compiledFunction, types, invalidatedProgramPoints, typeInformationFile,
415                continuationEntryPoints, runtimeScope);
416    }
417
418    /**
419     * Convenience constructor for non on-demand compiler instances.
420     */
421    private Compiler(
422            final Context context,
423            final CodeInstaller installer,
424            final Source source,
425            final ErrorManager errors,
426            final boolean isStrict) {
427        this(context, installer, source, errors, isStrict, false, null, null, null, null, null, null);
428    }
429
430    private Compiler(
431            final Context context,
432            final CodeInstaller installer,
433            final Source source,
434            final ErrorManager errors,
435            final boolean isStrict,
436            final boolean isOnDemand,
437            final RecompilableScriptFunctionData compiledFunction,
438            final TypeMap types,
439            final Map<Integer, Type> invalidatedProgramPoints,
440            final Object typeInformationFile,
441            final int[] continuationEntryPoints,
442            final ScriptObject runtimeScope) {
443        this.context                  = context;
444        this.env                      = context.getEnv();
445        this.installer                = installer;
446        this.constantData             = new ConstantData();
447        this.compileUnits             = CompileUnit.createCompileUnitSet();
448        this.bytecode                 = new LinkedHashMap<>();
449        this.log                      = initLogger(context);
450        this.source                   = source;
451        this.errors                   = errors;
452        this.sourceName               = FunctionNode.getSourceName(source);
453        this.onDemand                 = isOnDemand;
454        this.compiledFunction         = compiledFunction;
455        this.types                    = types;
456        this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap<>() : invalidatedProgramPoints;
457        this.typeInformationFile      = typeInformationFile;
458        this.continuationEntryPoints  = continuationEntryPoints == null ? null: continuationEntryPoints.clone();
459        this.typeEvaluator            = new TypeEvaluator(this, runtimeScope);
460        this.firstCompileUnitName     = firstCompileUnitName();
461        this.strict                   = isStrict;
462
463        this.optimistic = env._optimistic_types;
464    }
465
466    private String safeSourceName() {
467        String baseName = new File(source.getName()).getName();
468
469        final int index = baseName.lastIndexOf(".js");
470        if (index != -1) {
471            baseName = baseName.substring(0, index);
472        }
473
474        baseName = baseName.replace('.', '_').replace('-', '_');
475        if (!env._loader_per_compile) {
476            baseName += installer.getUniqueScriptId();
477        }
478
479        // ASM's bytecode verifier does not allow JVM allowed safe escapes using '\' as escape char.
480        // While ASM accepts such escapes for method names, field names, it enforces Java identifier
481        // for class names. Workaround that ASM bug here by replacing JVM 'dangerous' chars with '_'
482        // rather than safe encoding using '\'.
483        final String mangled = env._verify_code? replaceDangerChars(baseName) : NameCodec.encode(baseName);
484        return mangled != null ? mangled : baseName;
485    }
486
487    private static final String DANGEROUS_CHARS   = "\\/.;:$[]<>";
488    private static String replaceDangerChars(final String name) {
489        final int len = name.length();
490        final StringBuilder buf = new StringBuilder();
491        for (int i = 0; i < len; i++) {
492            final char ch = name.charAt(i);
493            if (DANGEROUS_CHARS.indexOf(ch) != -1) {
494                buf.append('_');
495            } else {
496                buf.append(ch);
497            }
498        }
499        return buf.toString();
500    }
501
502    private String firstCompileUnitName() {
503        final StringBuilder sb = new StringBuilder(SCRIPTS_PACKAGE).
504                append('/').
505                append(CompilerConstants.DEFAULT_SCRIPT_NAME.symbolName()).
506                append('$');
507
508        if (isOnDemandCompilation()) {
509            sb.append(RecompilableScriptFunctionData.RECOMPILATION_PREFIX);
510        }
511
512        if (compilationId > 0) {
513            sb.append(compilationId).append('$');
514        }
515
516        if (types != null && compiledFunction.getFunctionNodeId() > 0) {
517            sb.append(compiledFunction.getFunctionNodeId());
518            final Type[] paramTypes = types.getParameterTypes(compiledFunction.getFunctionNodeId());
519            for (final Type t : paramTypes) {
520                sb.append(Type.getShortSignatureDescriptor(t));
521            }
522            sb.append('$');
523        }
524
525        sb.append(safeSourceName());
526
527        return sb.toString();
528    }
529
530    void declareLocalSymbol(final String symbolName) {
531        typeEvaluator.declareLocalSymbol(symbolName);
532    }
533
534    void setData(final RecompilableScriptFunctionData data) {
535        assert this.compiledFunction == null : data;
536        this.compiledFunction = data;
537    }
538
539    @Override
540    public DebugLogger getLogger() {
541        return log;
542    }
543
544    @Override
545    public DebugLogger initLogger(final Context ctxt) {
546        final boolean optimisticTypes = env._optimistic_types;
547        final boolean lazyCompilation = env._lazy_compilation;
548
549        return ctxt.getLogger(this.getClass(), new Consumer<DebugLogger>() {
550            @Override
551            public void accept(final DebugLogger newLogger) {
552                if (!lazyCompilation) {
553                    newLogger.warning("WARNING: Running with lazy compilation switched off. This is not a default setting.");
554                }
555                newLogger.warning("Optimistic types are ", optimisticTypes ? "ENABLED." : "DISABLED.");
556            }
557        });
558    }
559
560    ScriptEnvironment getScriptEnvironment() {
561        return env;
562    }
563
564    boolean isOnDemandCompilation() {
565        return onDemand;
566    }
567
568    boolean useOptimisticTypes() {
569        return optimistic;
570    }
571
572    Context getContext() {
573        return context;
574    }
575
576    Type getOptimisticType(final Optimistic node) {
577        return typeEvaluator.getOptimisticType(node);
578    }
579
580    /**
581     * Returns true if the expression can be safely evaluated, and its value is an object known to always use
582     * String as the type of its property names retrieved through
583     * {@link ScriptRuntime#toPropertyIterator(Object)}. It is used to avoid optimistic assumptions about its
584     * property name types.
585     * @param expr the expression to test
586     * @return true if the expression can be safely evaluated, and its value is an object known to always use
587     * String as the type of its property iterators.
588     */
589    boolean hasStringPropertyIterator(final Expression expr) {
590        return typeEvaluator.hasStringPropertyIterator(expr);
591    }
592
593    void addInvalidatedProgramPoint(final int programPoint, final Type type) {
594        invalidatedProgramPoints.put(programPoint, type);
595    }
596
597
598    /**
599     * Returns a copy of this compiler's current mapping of invalidated optimistic program points to their types. The
600     * copy is not live with regard to changes in state in this compiler instance, and is mutable.
601     * @return a copy of this compiler's current mapping of invalidated optimistic program points to their types.
602     */
603    public Map<Integer, Type> getInvalidatedProgramPoints() {
604        return invalidatedProgramPoints.isEmpty() ? null : new TreeMap<>(invalidatedProgramPoints);
605    }
606
607    TypeMap getTypeMap() {
608        return types;
609    }
610
611    MethodType getCallSiteType(final FunctionNode fn) {
612        if (types == null || !isOnDemandCompilation()) {
613            return null;
614        }
615        return types.getCallSiteType(fn);
616    }
617
618    Type getParamType(final FunctionNode fn, final int pos) {
619        return types == null ? null : types.get(fn, pos);
620    }
621
622    /**
623     * Do a compilation job
624     *
625     * @param functionNode function node to compile
626     * @param phases phases of compilation transforms to apply to function
627
628     * @return transformed function
629     *
630     * @throws CompilationException if error occurs during compilation
631     */
632    public FunctionNode compile(final FunctionNode functionNode, final CompilationPhases phases) throws CompilationException {
633        if (log.isEnabled()) {
634            log.info(">> Starting compile job for ", DebugLogger.quote(functionNode.getName()), " phases=", quote(phases.getDesc()));
635            log.indent();
636        }
637
638        final String name = DebugLogger.quote(functionNode.getName());
639
640        FunctionNode newFunctionNode = functionNode;
641
642        for (final String reservedName : RESERVED_NAMES) {
643            newFunctionNode.uniqueName(reservedName);
644        }
645
646        final boolean info = log.isLoggable(Level.INFO);
647
648        final DebugLogger timeLogger = env.isTimingEnabled() ? env._timing.getLogger() : null;
649
650        long time = 0L;
651
652        for (final CompilationPhase phase : phases) {
653            log.fine(phase, " starting for ", name);
654
655            try {
656                newFunctionNode = phase.apply(this, phases, newFunctionNode);
657            } catch (final ParserException error) {
658                errors.error(error);
659                if (env._dump_on_error) {
660                    error.printStackTrace(env.getErr());
661                }
662                return null;
663            }
664
665            log.fine(phase, " done for function ", quote(name));
666
667            if (env._print_mem_usage) {
668                printMemoryUsage(functionNode, phase.toString());
669            }
670
671            time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L);
672        }
673
674        if (typeInformationFile != null && !phases.isRestOfCompilation()) {
675            OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints);
676        }
677
678        log.unindent();
679
680        if (info) {
681            final StringBuilder sb = new StringBuilder("<< Finished compile job for ");
682            sb.append(newFunctionNode.getSource()).
683            append(':').
684            append(quote(newFunctionNode.getName()));
685
686            if (time > 0L && timeLogger != null) {
687                assert env.isTimingEnabled();
688                sb.append(" in ").append(TimeUnit.NANOSECONDS.toMillis(time)).append(" ms");
689            }
690            log.info(sb);
691        }
692
693        return newFunctionNode;
694    }
695
696    Source getSource() {
697        return source;
698    }
699
700    Map<String, byte[]> getBytecode() {
701        return Collections.unmodifiableMap(bytecode);
702    }
703
704    /**
705     * Reset bytecode cache for compiler reuse.
706     */
707    void clearBytecode() {
708        bytecode.clear();
709    }
710
711    CompileUnit getFirstCompileUnit() {
712        assert !compileUnits.isEmpty();
713        return compileUnits.iterator().next();
714    }
715
716    Set<CompileUnit> getCompileUnits() {
717        return compileUnits;
718    }
719
720    ConstantData getConstantData() {
721        return constantData;
722    }
723
724    CodeInstaller getCodeInstaller() {
725        return installer;
726    }
727
728    void addClass(final String name, final byte[] code) {
729        bytecode.put(name, code);
730    }
731
732    String nextCompileUnitName() {
733        final StringBuilder sb = new StringBuilder(COMPILE_UNIT_NAME_BUFFER_SIZE);
734        sb.append(firstCompileUnitName);
735        final int cuid = nextCompileUnitId.getAndIncrement();
736        if (cuid > 0) {
737            sb.append("$cu").append(cuid);
738        }
739
740        return sb.toString();
741    }
742
743    /**
744     * Persist current compilation with the given {@code cacheKey}.
745     * @param cacheKey cache key
746     * @param functionNode function node
747     */
748    public void persistClassInfo(final String cacheKey, final FunctionNode functionNode) {
749        if (cacheKey != null && env._persistent_cache) {
750            // If this is an on-demand compilation create a function initializer for the function being compiled.
751            // Otherwise use function initializer map generated by codegen.
752            final Map<Integer, FunctionInitializer> initializers = new HashMap<>();
753            if (isOnDemandCompilation()) {
754                initializers.put(functionNode.getId(), new FunctionInitializer(functionNode, getInvalidatedProgramPoints()));
755            } else {
756                for (final CompileUnit compileUnit : getCompileUnits()) {
757                    for (final FunctionNode fn : compileUnit.getFunctionNodes()) {
758                        initializers.put(fn.getId(), new FunctionInitializer(fn));
759                    }
760                }
761            }
762            final String mainClassName = getFirstCompileUnit().getUnitClassName();
763            installer.storeScript(cacheKey, source, mainClassName, bytecode, initializers, constantData.toArray(), compilationId);
764        }
765    }
766
767    /**
768     * Make sure the next compilation id is greater than {@code value}.
769     * @param value compilation id value
770     */
771    public static void updateCompilationId(final int value) {
772        if (value >= COMPILATION_ID.get()) {
773            COMPILATION_ID.set(value + 1);
774        }
775    }
776
777    CompileUnit addCompileUnit(final long initialWeight) {
778        final CompileUnit compileUnit = createCompileUnit(initialWeight);
779        compileUnits.add(compileUnit);
780        log.fine("Added compile unit ", compileUnit);
781        return compileUnit;
782    }
783
784    CompileUnit createCompileUnit(final String unitClassName, final long initialWeight) {
785        final ClassEmitter classEmitter = new ClassEmitter(context, sourceName, unitClassName, isStrict());
786        final CompileUnit  compileUnit  = new CompileUnit(unitClassName, classEmitter, initialWeight);
787        classEmitter.begin();
788
789        return compileUnit;
790    }
791
792    private CompileUnit createCompileUnit(final long initialWeight) {
793        return createCompileUnit(nextCompileUnitName(), initialWeight);
794    }
795
796    boolean isStrict() {
797        return strict;
798    }
799
800    void replaceCompileUnits(final Set<CompileUnit> newUnits) {
801        compileUnits.clear();
802        compileUnits.addAll(newUnits);
803    }
804
805    CompileUnit findUnit(final long weight) {
806        for (final CompileUnit unit : compileUnits) {
807            if (unit.canHold(weight)) {
808                unit.addWeight(weight);
809                return unit;
810            }
811        }
812
813        return addCompileUnit(weight);
814    }
815
816    /**
817     * Convert a package/class name to a binary name.
818     *
819     * @param name Package/class name.
820     * @return Binary name.
821     */
822    public static String binaryName(final String name) {
823        return name.replace('/', '.');
824    }
825
826    RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
827        assert compiledFunction != null;
828        final RecompilableScriptFunctionData fn = compiledFunction.getScriptFunctionData(functionId);
829        assert fn != null : functionId;
830        return fn;
831    }
832
833    boolean isGlobalSymbol(final FunctionNode fn, final String name) {
834        return getScriptFunctionData(fn.getId()).isGlobalSymbol(fn, name);
835    }
836
837    int[] getContinuationEntryPoints() {
838        return continuationEntryPoints;
839    }
840
841    Type getInvalidatedProgramPointType(final int programPoint) {
842        return invalidatedProgramPoints.get(programPoint);
843    }
844
845    private void printMemoryUsage(final FunctionNode functionNode, final String phaseName) {
846        if (!log.isEnabled()) {
847            return;
848        }
849
850        log.info(phaseName, "finished. Doing IR size calculation...");
851
852        final ObjectSizeCalculator osc = new ObjectSizeCalculator(ObjectSizeCalculator.getEffectiveMemoryLayoutSpecification());
853        osc.calculateObjectSize(functionNode);
854
855        final List<ClassHistogramElement> list      = osc.getClassHistogram();
856        final StringBuilder               sb        = new StringBuilder();
857        final long                        totalSize = osc.calculateObjectSize(functionNode);
858
859        sb.append(phaseName).
860        append(" Total size = ").
861        append(totalSize / 1024 / 1024).
862        append("MB");
863        log.info(sb);
864
865        Collections.sort(list, new Comparator<ClassHistogramElement>() {
866            @Override
867            public int compare(final ClassHistogramElement o1, final ClassHistogramElement o2) {
868                final long diff = o1.getBytes() - o2.getBytes();
869                if (diff < 0) {
870                    return 1;
871                } else if (diff > 0) {
872                    return -1;
873                } else {
874                    return 0;
875                }
876            }
877        });
878        for (final ClassHistogramElement e : list) {
879            final String line = String.format("    %-48s %10d bytes (%8d instances)", e.getClazz(), e.getBytes(), e.getInstances());
880            log.info(line);
881            if (e.getBytes() < totalSize / 200) {
882                log.info("    ...");
883                break; // never mind, so little memory anyway
884            }
885        }
886    }
887}
888