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