src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java
changeset 47216 71c04702a3d5
parent 33889 d12616b2b375
child 47276 bfa048898f11
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,887 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.codegen;
+
+import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
+import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
+import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
+import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
+import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
+import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
+import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
+
+import java.io.File;
+import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.Expression;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.Optimistic;
+import jdk.nashorn.internal.ir.debug.ClassHistogramElement;
+import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator;
+import jdk.nashorn.internal.runtime.CodeInstaller;
+import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ErrorManager;
+import jdk.nashorn.internal.runtime.FunctionInitializer;
+import jdk.nashorn.internal.runtime.ParserException;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+import jdk.nashorn.internal.runtime.ScriptEnvironment;
+import jdk.nashorn.internal.runtime.ScriptObject;
+import jdk.nashorn.internal.runtime.ScriptRuntime;
+import jdk.nashorn.internal.runtime.Source;
+import jdk.nashorn.internal.runtime.linker.NameCodec;
+import jdk.nashorn.internal.runtime.logging.DebugLogger;
+import jdk.nashorn.internal.runtime.logging.Loggable;
+import jdk.nashorn.internal.runtime.logging.Logger;
+
+/**
+ * Responsible for converting JavaScripts to java byte code. Main entry
+ * point for code generator. The compiler may also install classes given some
+ * predefined Code installation policy, given to it at construction time.
+ * @see CodeInstaller
+ */
+@Logger(name="compiler")
+public final class Compiler implements Loggable {
+
+    /** Name of the scripts package */
+    public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts";
+
+    /** Name of the objects package */
+    public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects";
+
+    private final ScriptEnvironment env;
+
+    private final Source source;
+
+    private final String sourceName;
+
+    private final ErrorManager errors;
+
+    private final boolean optimistic;
+
+    private final Map<String, byte[]> bytecode;
+
+    private final Set<CompileUnit> compileUnits;
+
+    private final ConstantData constantData;
+
+    private final CodeInstaller installer;
+
+    /** logger for compiler, trampolines and related code generation events
+     *  that affect classes */
+    private final DebugLogger log;
+
+    private final Context context;
+
+    private final TypeMap types;
+
+    // Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
+    // optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
+    private final TypeEvaluator typeEvaluator;
+
+    private final boolean strict;
+
+    private final boolean onDemand;
+
+    /**
+     * If this is a recompilation, this is how we pass in the invalidations, e.g. programPoint=17, Type == int means
+     * that using whatever was at program point 17 as an int failed.
+     */
+    private final Map<Integer, Type> invalidatedProgramPoints;
+
+    /**
+     * Descriptor of the location where we write the type information after compilation.
+     */
+    private final Object typeInformationFile;
+
+    /**
+     * Compile unit name of first compile unit - this prefix will be used for all
+     * classes that a compilation generates.
+     */
+    private final String firstCompileUnitName;
+
+    /**
+     * Contains the program point that should be used as the continuation entry point, as well as all previous
+     * continuation entry points executed as part of a single logical invocation of the function. In practical terms, if
+     * we execute a rest-of method from the program point 17, but then we hit deoptimization again during it at program
+     * point 42, and execute a rest-of method from the program point 42, and then we hit deoptimization again at program
+     * point 57 and are compiling a rest-of method for it, the values in the array will be [57, 42, 17]. This is only
+     * set when compiling a rest-of method. If this method is a rest-of for a non-rest-of method, the array will have
+     * one element. If it is a rest-of for a rest-of, the array will have two elements, and so on.
+     */
+    private final int[] continuationEntryPoints;
+
+    /**
+     * ScriptFunction data for what is being compile, where applicable.
+     * TODO: make this immutable, propagate it through the CompilationPhases
+     */
+    private RecompilableScriptFunctionData compiledFunction;
+
+    /**
+     * Most compile unit names are longer than the default StringBuilder buffer,
+     * worth startup performance when massive class generation is going on to increase
+     * this
+     */
+    private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32;
+
+    /**
+     * Compilation phases that a compilation goes through
+     */
+    public static class CompilationPhases implements Iterable<CompilationPhase> {
+
+        /**
+         * Singleton that describes compilation up to the phase where a function can be cached.
+         */
+        private final static CompilationPhases COMPILE_UPTO_CACHED = new CompilationPhases(
+                "Common initial phases",
+                CompilationPhase.CONSTANT_FOLDING_PHASE,
+                CompilationPhase.LOWERING_PHASE,
+                CompilationPhase.APPLY_SPECIALIZATION_PHASE,
+                CompilationPhase.SPLITTING_PHASE,
+                CompilationPhase.PROGRAM_POINT_PHASE,
+                CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
+                CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
+                CompilationPhase.CACHE_AST_PHASE
+                );
+
+        private final static CompilationPhases COMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
+                "After common phases, before bytecode generator",
+                CompilationPhase.DECLARE_LOCAL_SYMBOLS_PHASE,
+                CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
+                CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE
+                );
+
+        /**
+         * Singleton that describes additional steps to be taken after retrieving a cached function, all the
+         * way up to (but not including) generating and installing code.
+         */
+        public final static CompilationPhases RECOMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
+                "Recompile cached function up to bytecode",
+                CompilationPhase.REINITIALIZE_CACHED,
+                COMPILE_CACHED_UPTO_BYTECODE
+                );
+
+        /**
+         * Singleton that describes back end of method generation, given that we have generated the normal
+         * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
+         */
+        public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL = new CompilationPhases(
+                "Generate bytecode and install",
+                CompilationPhase.BYTECODE_GENERATION_PHASE,
+                CompilationPhase.INSTALL_PHASE
+                );
+
+        /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
+        public final static CompilationPhases COMPILE_UPTO_BYTECODE = new CompilationPhases(
+                "Compile upto bytecode",
+                COMPILE_UPTO_CACHED,
+                COMPILE_CACHED_UPTO_BYTECODE);
+
+        /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
+        public final static CompilationPhases COMPILE_ALL_NO_INSTALL = new CompilationPhases(
+                "Compile without install",
+                COMPILE_UPTO_BYTECODE,
+                CompilationPhase.BYTECODE_GENERATION_PHASE);
+
+        /** Singleton that describes a standard eager compilation - this includes code installation */
+        public final static CompilationPhases COMPILE_ALL = new CompilationPhases(
+                "Full eager compilation",
+                COMPILE_UPTO_BYTECODE,
+                GENERATE_BYTECODE_AND_INSTALL);
+
+        /** Singleton that describes a full compilation - this includes code installation - from serialized state*/
+        public final static CompilationPhases COMPILE_ALL_CACHED = new CompilationPhases(
+                "Eager compilation from serializaed state",
+                RECOMPILE_CACHED_UPTO_BYTECODE,
+                GENERATE_BYTECODE_AND_INSTALL);
+
+        /**
+         * Singleton that describes restOf method generation, given that we have generated the normal
+         * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
+         */
+        public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL_RESTOF = new CompilationPhases(
+                "Generate bytecode and install - RestOf method",
+                CompilationPhase.REUSE_COMPILE_UNITS_PHASE,
+                GENERATE_BYTECODE_AND_INSTALL);
+
+        /** Compile all for a rest of method */
+        public final static CompilationPhases COMPILE_ALL_RESTOF = new CompilationPhases(
+                "Compile all, rest of",
+                COMPILE_UPTO_BYTECODE,
+                GENERATE_BYTECODE_AND_INSTALL_RESTOF);
+
+        /** Compile from serialized for a rest of method */
+        public final static CompilationPhases COMPILE_CACHED_RESTOF = new CompilationPhases(
+                "Compile serialized, rest of",
+                RECOMPILE_CACHED_UPTO_BYTECODE,
+                GENERATE_BYTECODE_AND_INSTALL_RESTOF);
+
+        private final List<CompilationPhase> phases;
+
+        private final String desc;
+
+        private CompilationPhases(final String desc, final CompilationPhase... phases) {
+            this(desc, Arrays.asList(phases));
+        }
+
+        private CompilationPhases(final String desc, final CompilationPhases base, final CompilationPhase... phases) {
+            this(desc, concat(base.phases, Arrays.asList(phases)));
+        }
+
+        private CompilationPhases(final String desc, final CompilationPhase first, final CompilationPhases rest) {
+            this(desc, concat(Collections.singletonList(first), rest.phases));
+        }
+
+        private CompilationPhases(final String desc, final CompilationPhases base) {
+            this(desc, base.phases);
+        }
+
+        private CompilationPhases(final String desc, final CompilationPhases... bases) {
+            this(desc, concatPhases(bases));
+        }
+
+        private CompilationPhases(final String desc, final List<CompilationPhase> phases) {
+            this.desc = desc;
+            this.phases = phases;
+        }
+
+        private static List<CompilationPhase> concatPhases(final CompilationPhases[] bases) {
+            final ArrayList<CompilationPhase> l = new ArrayList<>();
+            for(final CompilationPhases base: bases) {
+                l.addAll(base.phases);
+            }
+            l.trimToSize();
+            return l;
+        }
+
+        private static <T> List<T> concat(final List<T> l1, final List<T> l2) {
+            final ArrayList<T> l = new ArrayList<>(l1);
+            l.addAll(l2);
+            l.trimToSize();
+            return l;
+        }
+
+        @Override
+        public String toString() {
+            return "'" + desc + "' " + phases.toString();
+        }
+
+        boolean contains(final CompilationPhase phase) {
+            return phases.contains(phase);
+        }
+
+        @Override
+        public Iterator<CompilationPhase> iterator() {
+            return phases.iterator();
+        }
+
+        boolean isRestOfCompilation() {
+            return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_CACHED_RESTOF;
+        }
+
+        String getDesc() {
+            return desc;
+        }
+
+        String toString(final String prefix) {
+            final StringBuilder sb = new StringBuilder();
+            for (final CompilationPhase phase : phases) {
+                sb.append(prefix).append(phase).append('\n');
+            }
+            return sb.toString();
+        }
+    }
+
+    /**
+     * This array contains names that need to be reserved at the start
+     * of a compile, to avoid conflict with variable names later introduced.
+     * See {@link CompilerConstants} for special names used for structures
+     * during a compile.
+     */
+    private static String[] RESERVED_NAMES = {
+        SCOPE.symbolName(),
+        THIS.symbolName(),
+        RETURN.symbolName(),
+        CALLEE.symbolName(),
+        VARARGS.symbolName(),
+        ARGUMENTS.symbolName()
+    };
+
+    // per instance
+    private final int compilationId = COMPILATION_ID.getAndIncrement();
+
+    // per instance
+    private final AtomicInteger nextCompileUnitId = new AtomicInteger(0);
+
+    private static final AtomicInteger COMPILATION_ID = new AtomicInteger(0);
+
+    /**
+     * Creates a new compiler instance for initial compilation of a script.
+     *
+     * @param installer code installer
+     * @param source    source to compile
+     * @param errors    error manager
+     * @param isStrict  is this a strict compilation
+     * @return a new compiler
+     */
+    public static Compiler forInitialCompilation(
+            final CodeInstaller installer,
+            final Source source,
+            final ErrorManager errors,
+            final boolean isStrict) {
+        return new Compiler(installer.getContext(), installer, source, errors, isStrict);
+    }
+
+    /**
+     * Creates a compiler without a code installer. It can only be used to compile code, not install the
+     * generated classes and as such it is useful only for implementation of {@code --compile-only} command
+     * line option.
+     * @param context  the current context
+     * @param source   source to compile
+     * @param isStrict is this a strict compilation
+     * @return a new compiler
+     */
+    public static Compiler forNoInstallerCompilation(
+            final Context context,
+            final Source source,
+            final boolean isStrict) {
+        return new Compiler(context, null, source, context.getErrorManager(), isStrict);
+    }
+
+    /**
+     * Creates a compiler for an on-demand compilation job.
+     *
+     * @param installer                code installer
+     * @param source                   source to compile
+     * @param isStrict                 is this a strict compilation
+     * @param compiledFunction         compiled function, if any
+     * @param types                    parameter and return value type information, if any is known
+     * @param invalidatedProgramPoints invalidated program points for recompilation
+     * @param typeInformationFile      descriptor of the location where type information is persisted
+     * @param continuationEntryPoints  continuation entry points for restof method
+     * @param runtimeScope             runtime scope for recompilation type lookup in {@code TypeEvaluator}
+     * @return a new compiler
+     */
+    public static Compiler forOnDemandCompilation(
+            final CodeInstaller installer,
+            final Source source,
+            final boolean isStrict,
+            final RecompilableScriptFunctionData compiledFunction,
+            final TypeMap types,
+            final Map<Integer, Type> invalidatedProgramPoints,
+            final Object typeInformationFile,
+            final int[] continuationEntryPoints,
+            final ScriptObject runtimeScope) {
+        final Context context = installer.getContext();
+        return new Compiler(context, installer, source, context.getErrorManager(), isStrict, true,
+                compiledFunction, types, invalidatedProgramPoints, typeInformationFile,
+                continuationEntryPoints, runtimeScope);
+    }
+
+    /**
+     * Convenience constructor for non on-demand compiler instances.
+     */
+    private Compiler(
+            final Context context,
+            final CodeInstaller installer,
+            final Source source,
+            final ErrorManager errors,
+            final boolean isStrict) {
+        this(context, installer, source, errors, isStrict, false, null, null, null, null, null, null);
+    }
+
+    private Compiler(
+            final Context context,
+            final CodeInstaller installer,
+            final Source source,
+            final ErrorManager errors,
+            final boolean isStrict,
+            final boolean isOnDemand,
+            final RecompilableScriptFunctionData compiledFunction,
+            final TypeMap types,
+            final Map<Integer, Type> invalidatedProgramPoints,
+            final Object typeInformationFile,
+            final int[] continuationEntryPoints,
+            final ScriptObject runtimeScope) {
+        this.context                  = context;
+        this.env                      = context.getEnv();
+        this.installer                = installer;
+        this.constantData             = new ConstantData();
+        this.compileUnits             = CompileUnit.createCompileUnitSet();
+        this.bytecode                 = new LinkedHashMap<>();
+        this.log                      = initLogger(context);
+        this.source                   = source;
+        this.errors                   = errors;
+        this.sourceName               = FunctionNode.getSourceName(source);
+        this.onDemand                 = isOnDemand;
+        this.compiledFunction         = compiledFunction;
+        this.types                    = types;
+        this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap<>() : invalidatedProgramPoints;
+        this.typeInformationFile      = typeInformationFile;
+        this.continuationEntryPoints  = continuationEntryPoints == null ? null: continuationEntryPoints.clone();
+        this.typeEvaluator            = new TypeEvaluator(this, runtimeScope);
+        this.firstCompileUnitName     = firstCompileUnitName();
+        this.strict                   = isStrict;
+
+        this.optimistic = env._optimistic_types;
+    }
+
+    private String safeSourceName() {
+        String baseName = new File(source.getName()).getName();
+
+        final int index = baseName.lastIndexOf(".js");
+        if (index != -1) {
+            baseName = baseName.substring(0, index);
+        }
+
+        baseName = baseName.replace('.', '_').replace('-', '_');
+        if (!env._loader_per_compile) {
+            baseName += installer.getUniqueScriptId();
+        }
+
+        // ASM's bytecode verifier does not allow JVM allowed safe escapes using '\' as escape char.
+        // While ASM accepts such escapes for method names, field names, it enforces Java identifier
+        // for class names. Workaround that ASM bug here by replacing JVM 'dangerous' chars with '_'
+        // rather than safe encoding using '\'.
+        final String mangled = env._verify_code? replaceDangerChars(baseName) : NameCodec.encode(baseName);
+        return mangled != null ? mangled : baseName;
+    }
+
+    private static final String DANGEROUS_CHARS   = "\\/.;:$[]<>";
+    private static String replaceDangerChars(final String name) {
+        final int len = name.length();
+        final StringBuilder buf = new StringBuilder();
+        for (int i = 0; i < len; i++) {
+            final char ch = name.charAt(i);
+            if (DANGEROUS_CHARS.indexOf(ch) != -1) {
+                buf.append('_');
+            } else {
+                buf.append(ch);
+            }
+        }
+        return buf.toString();
+    }
+
+    private String firstCompileUnitName() {
+        final StringBuilder sb = new StringBuilder(SCRIPTS_PACKAGE).
+                append('/').
+                append(CompilerConstants.DEFAULT_SCRIPT_NAME.symbolName()).
+                append('$');
+
+        if (isOnDemandCompilation()) {
+            sb.append(RecompilableScriptFunctionData.RECOMPILATION_PREFIX);
+        }
+
+        if (compilationId > 0) {
+            sb.append(compilationId).append('$');
+        }
+
+        if (types != null && compiledFunction.getFunctionNodeId() > 0) {
+            sb.append(compiledFunction.getFunctionNodeId());
+            final Type[] paramTypes = types.getParameterTypes(compiledFunction.getFunctionNodeId());
+            for (final Type t : paramTypes) {
+                sb.append(Type.getShortSignatureDescriptor(t));
+            }
+            sb.append('$');
+        }
+
+        sb.append(safeSourceName());
+
+        return sb.toString();
+    }
+
+    void declareLocalSymbol(final String symbolName) {
+        typeEvaluator.declareLocalSymbol(symbolName);
+    }
+
+    void setData(final RecompilableScriptFunctionData data) {
+        assert this.compiledFunction == null : data;
+        this.compiledFunction = data;
+    }
+
+    @Override
+    public DebugLogger getLogger() {
+        return log;
+    }
+
+    @Override
+    public DebugLogger initLogger(final Context ctxt) {
+        final boolean optimisticTypes = env._optimistic_types;
+        final boolean lazyCompilation = env._lazy_compilation;
+
+        return ctxt.getLogger(this.getClass(), new Consumer<DebugLogger>() {
+            @Override
+            public void accept(final DebugLogger newLogger) {
+                if (!lazyCompilation) {
+                    newLogger.warning("WARNING: Running with lazy compilation switched off. This is not a default setting.");
+                }
+                newLogger.warning("Optimistic types are ", optimisticTypes ? "ENABLED." : "DISABLED.");
+            }
+        });
+    }
+
+    ScriptEnvironment getScriptEnvironment() {
+        return env;
+    }
+
+    boolean isOnDemandCompilation() {
+        return onDemand;
+    }
+
+    boolean useOptimisticTypes() {
+        return optimistic;
+    }
+
+    Context getContext() {
+        return context;
+    }
+
+    Type getOptimisticType(final Optimistic node) {
+        return typeEvaluator.getOptimisticType(node);
+    }
+
+    /**
+     * Returns true if the expression can be safely evaluated, and its value is an object known to always use
+     * String as the type of its property names retrieved through
+     * {@link ScriptRuntime#toPropertyIterator(Object)}. It is used to avoid optimistic assumptions about its
+     * property name types.
+     * @param expr the expression to test
+     * @return true if the expression can be safely evaluated, and its value is an object known to always use
+     * String as the type of its property iterators.
+     */
+    boolean hasStringPropertyIterator(final Expression expr) {
+        return typeEvaluator.hasStringPropertyIterator(expr);
+    }
+
+    void addInvalidatedProgramPoint(final int programPoint, final Type type) {
+        invalidatedProgramPoints.put(programPoint, type);
+    }
+
+
+    /**
+     * Returns a copy of this compiler's current mapping of invalidated optimistic program points to their types. The
+     * copy is not live with regard to changes in state in this compiler instance, and is mutable.
+     * @return a copy of this compiler's current mapping of invalidated optimistic program points to their types.
+     */
+    public Map<Integer, Type> getInvalidatedProgramPoints() {
+        return invalidatedProgramPoints.isEmpty() ? null : new TreeMap<>(invalidatedProgramPoints);
+    }
+
+    TypeMap getTypeMap() {
+        return types;
+    }
+
+    MethodType getCallSiteType(final FunctionNode fn) {
+        if (types == null || !isOnDemandCompilation()) {
+            return null;
+        }
+        return types.getCallSiteType(fn);
+    }
+
+    Type getParamType(final FunctionNode fn, final int pos) {
+        return types == null ? null : types.get(fn, pos);
+    }
+
+    /**
+     * Do a compilation job
+     *
+     * @param functionNode function node to compile
+     * @param phases phases of compilation transforms to apply to function
+
+     * @return transformed function
+     *
+     * @throws CompilationException if error occurs during compilation
+     */
+    public FunctionNode compile(final FunctionNode functionNode, final CompilationPhases phases) throws CompilationException {
+        if (log.isEnabled()) {
+            log.info(">> Starting compile job for ", DebugLogger.quote(functionNode.getName()), " phases=", quote(phases.getDesc()));
+            log.indent();
+        }
+
+        final String name = DebugLogger.quote(functionNode.getName());
+
+        FunctionNode newFunctionNode = functionNode;
+
+        for (final String reservedName : RESERVED_NAMES) {
+            newFunctionNode.uniqueName(reservedName);
+        }
+
+        final boolean info = log.isLoggable(Level.INFO);
+
+        final DebugLogger timeLogger = env.isTimingEnabled() ? env._timing.getLogger() : null;
+
+        long time = 0L;
+
+        for (final CompilationPhase phase : phases) {
+            log.fine(phase, " starting for ", name);
+
+            try {
+                newFunctionNode = phase.apply(this, phases, newFunctionNode);
+            } catch (final ParserException error) {
+                errors.error(error);
+                if (env._dump_on_error) {
+                    error.printStackTrace(env.getErr());
+                }
+                return null;
+            }
+
+            log.fine(phase, " done for function ", quote(name));
+
+            if (env._print_mem_usage) {
+                printMemoryUsage(functionNode, phase.toString());
+            }
+
+            time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L);
+        }
+
+        if (typeInformationFile != null && !phases.isRestOfCompilation()) {
+            OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints);
+        }
+
+        log.unindent();
+
+        if (info) {
+            final StringBuilder sb = new StringBuilder("<< Finished compile job for ");
+            sb.append(newFunctionNode.getSource()).
+            append(':').
+            append(quote(newFunctionNode.getName()));
+
+            if (time > 0L && timeLogger != null) {
+                assert env.isTimingEnabled();
+                sb.append(" in ").append(TimeUnit.NANOSECONDS.toMillis(time)).append(" ms");
+            }
+            log.info(sb);
+        }
+
+        return newFunctionNode;
+    }
+
+    Source getSource() {
+        return source;
+    }
+
+    Map<String, byte[]> getBytecode() {
+        return Collections.unmodifiableMap(bytecode);
+    }
+
+    /**
+     * Reset bytecode cache for compiler reuse.
+     */
+    void clearBytecode() {
+        bytecode.clear();
+    }
+
+    CompileUnit getFirstCompileUnit() {
+        assert !compileUnits.isEmpty();
+        return compileUnits.iterator().next();
+    }
+
+    Set<CompileUnit> getCompileUnits() {
+        return compileUnits;
+    }
+
+    ConstantData getConstantData() {
+        return constantData;
+    }
+
+    CodeInstaller getCodeInstaller() {
+        return installer;
+    }
+
+    void addClass(final String name, final byte[] code) {
+        bytecode.put(name, code);
+    }
+
+    String nextCompileUnitName() {
+        final StringBuilder sb = new StringBuilder(COMPILE_UNIT_NAME_BUFFER_SIZE);
+        sb.append(firstCompileUnitName);
+        final int cuid = nextCompileUnitId.getAndIncrement();
+        if (cuid > 0) {
+            sb.append("$cu").append(cuid);
+        }
+
+        return sb.toString();
+    }
+
+    /**
+     * Persist current compilation with the given {@code cacheKey}.
+     * @param cacheKey cache key
+     * @param functionNode function node
+     */
+    public void persistClassInfo(final String cacheKey, final FunctionNode functionNode) {
+        if (cacheKey != null && env._persistent_cache) {
+            // If this is an on-demand compilation create a function initializer for the function being compiled.
+            // Otherwise use function initializer map generated by codegen.
+            final Map<Integer, FunctionInitializer> initializers = new HashMap<>();
+            if (isOnDemandCompilation()) {
+                initializers.put(functionNode.getId(), new FunctionInitializer(functionNode, getInvalidatedProgramPoints()));
+            } else {
+                for (final CompileUnit compileUnit : getCompileUnits()) {
+                    for (final FunctionNode fn : compileUnit.getFunctionNodes()) {
+                        initializers.put(fn.getId(), new FunctionInitializer(fn));
+                    }
+                }
+            }
+            final String mainClassName = getFirstCompileUnit().getUnitClassName();
+            installer.storeScript(cacheKey, source, mainClassName, bytecode, initializers, constantData.toArray(), compilationId);
+        }
+    }
+
+    /**
+     * Make sure the next compilation id is greater than {@code value}.
+     * @param value compilation id value
+     */
+    public static void updateCompilationId(final int value) {
+        if (value >= COMPILATION_ID.get()) {
+            COMPILATION_ID.set(value + 1);
+        }
+    }
+
+    CompileUnit addCompileUnit(final long initialWeight) {
+        final CompileUnit compileUnit = createCompileUnit(initialWeight);
+        compileUnits.add(compileUnit);
+        log.fine("Added compile unit ", compileUnit);
+        return compileUnit;
+    }
+
+    CompileUnit createCompileUnit(final String unitClassName, final long initialWeight) {
+        final ClassEmitter classEmitter = new ClassEmitter(context, sourceName, unitClassName, isStrict());
+        final CompileUnit  compileUnit  = new CompileUnit(unitClassName, classEmitter, initialWeight);
+        classEmitter.begin();
+
+        return compileUnit;
+    }
+
+    private CompileUnit createCompileUnit(final long initialWeight) {
+        return createCompileUnit(nextCompileUnitName(), initialWeight);
+    }
+
+    boolean isStrict() {
+        return strict;
+    }
+
+    void replaceCompileUnits(final Set<CompileUnit> newUnits) {
+        compileUnits.clear();
+        compileUnits.addAll(newUnits);
+    }
+
+    CompileUnit findUnit(final long weight) {
+        for (final CompileUnit unit : compileUnits) {
+            if (unit.canHold(weight)) {
+                unit.addWeight(weight);
+                return unit;
+            }
+        }
+
+        return addCompileUnit(weight);
+    }
+
+    /**
+     * Convert a package/class name to a binary name.
+     *
+     * @param name Package/class name.
+     * @return Binary name.
+     */
+    public static String binaryName(final String name) {
+        return name.replace('/', '.');
+    }
+
+    RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
+        assert compiledFunction != null;
+        final RecompilableScriptFunctionData fn = compiledFunction.getScriptFunctionData(functionId);
+        assert fn != null : functionId;
+        return fn;
+    }
+
+    boolean isGlobalSymbol(final FunctionNode fn, final String name) {
+        return getScriptFunctionData(fn.getId()).isGlobalSymbol(fn, name);
+    }
+
+    int[] getContinuationEntryPoints() {
+        return continuationEntryPoints;
+    }
+
+    Type getInvalidatedProgramPointType(final int programPoint) {
+        return invalidatedProgramPoints.get(programPoint);
+    }
+
+    private void printMemoryUsage(final FunctionNode functionNode, final String phaseName) {
+        if (!log.isEnabled()) {
+            return;
+        }
+
+        log.info(phaseName, "finished. Doing IR size calculation...");
+
+        final ObjectSizeCalculator osc = new ObjectSizeCalculator(ObjectSizeCalculator.getEffectiveMemoryLayoutSpecification());
+        osc.calculateObjectSize(functionNode);
+
+        final List<ClassHistogramElement> list      = osc.getClassHistogram();
+        final StringBuilder               sb        = new StringBuilder();
+        final long                        totalSize = osc.calculateObjectSize(functionNode);
+
+        sb.append(phaseName).
+        append(" Total size = ").
+        append(totalSize / 1024 / 1024).
+        append("MB");
+        log.info(sb);
+
+        Collections.sort(list, new Comparator<ClassHistogramElement>() {
+            @Override
+            public int compare(final ClassHistogramElement o1, final ClassHistogramElement o2) {
+                final long diff = o1.getBytes() - o2.getBytes();
+                if (diff < 0) {
+                    return 1;
+                } else if (diff > 0) {
+                    return -1;
+                } else {
+                    return 0;
+                }
+            }
+        });
+        for (final ClassHistogramElement e : list) {
+            final String line = String.format("    %-48s %10d bytes (%8d instances)", e.getClazz(), e.getBytes(), e.getInstances());
+            log.info(line);
+            if (e.getBytes() < totalSize / 200) {
+                log.info("    ...");
+                break; // never mind, so little memory anyway
+            }
+        }
+    }
+}